Pluggable storage

Started by Alvaro Herreraover 9 years ago138 messages
#1Alvaro Herrera
alvherre@2ndQuadrant.com

Many have expressed their interest in this topic, but I haven't seen any
design of how it should work. Here's my attempt; I've been playing with
this for some time now and I think what I propose here is a good initial
plan. This will allow us to write permanent table storage that works
differently than heapam.c. At this stage, I haven't throught through
whether this is going to allow extensions to define new storage modules;
I am focusing on AMs that can coexist with heapam in core.

The design starts with a new row type in pg_am, of type "s" (for "storage").
The handler function returns a struct of node StorageAmRoutine. This
contains functions for 1) scans (beginscan, getnext, endscan) 2) tuples
(tuple_insert/update/delete/lock, as well as set_oid, get_xmin and the
like), and operations on tuples that are part of slots (tuple_deform,
materialize).

To support this, we introduce StorageTuple and StorageScanDesc.
StorageTuples represent a physical tuple coming from some storage AM.
It is necessary to have a pointer to a StorageAmRoutine in order to
manipulate the tuple. For heapam.c, a StorageTuple is just a HeapTuple.

RelationData gains ->rd_stamroutine which is a pointer to the
StorageAmRoutine for the relation in question. Similarly,
TupleTableSlot is augmented with a link to the StorageAmRoutine to
handle the StorageTuple it contains (probably in most cases it's set at
the same time as the tupdesc). This implies that routines such as
ExecAssignScanType need to pass down the StorageAmRoutine from the
relation to the slot.

The executor is modified so that instead of calling heap_insert etc
directly, it uses rel->rd_stamroutine to call these methods. The
executor is still in charge of dealing with indexes, constraints, and
any other thing that's not the tuple storage itself (this is one major
point in which this differs from FDWs). This all looks simple enough,
with one exception and a few notes:

exception a) ExecMaterializeSlot needs special consideration. This is
used in two different ways: a1) is the stated "make tuple independent
from any underlying storage" point, which is handled by
ExecMaterializeSlot itself and calling a method from the storage AM to
do any byte copying as needed. ExecMaterializeSlot no longer returns a
HeapTuple, because there might not be any. The second usage pattern a2)
is to create a HeapTuple that's passed to other modules which only deal
with HT and not slots (triggers are the main case I noticed, but I think
there are others such as the executor itself wanting tuples as Datum for
some reason). For the moment I'm handling this by having a new
ExecHeapifyTuple which creates a HeapTuple from a slot, regardless of
the original tuple format.

note b) EvalPlanQual currently maintains an array of HeapTuple in
EState->es_epqTuple. I think it works to replace that with an array of
StorageTuples; EvalPlanQualFetch needs to call the StorageAmRoutine
methods in order to interact with it. Other than those changes, it
seems okay.

note c) nodeSubplan has curTuple as a HeapTuple. It seems simple
to replace this with an independent slot-based tuple.

note d) grp_firstTuple in nodeAgg / nodeSetOp. These are less
simple than the above, but replacing the HeapTuple with a slot-based
tuple seems doable too.

note e) nodeLockRows uses lr_curtuples to feed EvalPlanQual.
TupleTableSlot also seems a good replacement. This has fallout in other
users of EvalPlanQual, too.

note f) More widespread, MinimalTuples currently use a tweaked HeapTuple
format. In the long run, it may be possible to replace them with a
separate storage module that's specifically designed to handle tuples
meant for tuplestores etc. That may simplify TupleTableSlot and
execTuples. For the moment we keep the tts_mintuple as it is. Whenever
a tuple is not already in heap format, we heapify it in order to put in
the store.

The current heapam.c routines need some changes. Currently, practice is
that heap_insert, heap_multi_insert, heap_fetch, heap_update scribble on
their input tuples to set the resulting ItemPointer in tuple->t_self.
This is messy if we want StorageTuples to be abstract. I'm changing
this so that the resulting ItemPointer is returned in a separate output
argument; the tuple itself is left alone. This is somewhat messy in the
case of heap_multi_insert because it returns several items; I think it's
acceptable to return an array of ItemPointers in the same order as the
input tuples. This works fine for the only caller, which is COPY in
batch mode. For the other routines, they don't really care where the
TID is returned AFAICS.

Additional noteworthy items:

i) Speculative insertion: the speculative insertion token is no longer
installed directly in the heap tuple by the executor (of course).
Instead, the token becomes part of the slot. When the tuple_insert
method is called, the insertion routine is in charge of setting the
token from the slot into the storage tuple. Executor is in charge of
calling method->speculative_finish() / abort() once the insertion has
been confirmed by the indexes.

ii) execTuples has additional accessors for tuples-in-slot, such as
ExecFetchSlotTuple and friends. I expect to have some of them to return
abstract StorageTuples, others HeapTuple or MinimalTuples (possibly
wrapped in Datum), depending on callers. We might be able to cut down
on these later; my first cut will try to avoid API changes to keep
fallout to a minimum.

iii) All tuples need to be identifiable by ItemPointers. Storages that
have different requirements will need careful additional thought across
the board.

iv) System catalogs cannot use pluggable storage. We continue to use
heap_open etc in the DDL code, in order not to make this more invasive
that it already is. We may lift this restriction later for specific
catalogs, as needed.

v) Currently, one Buffer may be associated with one HeapTuple living in a
slot; when the slot is cleared, the buffer pin is released. My current
patch moves the buffer pin to inside the heapam-based storage AM and the
buffer is released by the ->slot_clear_tuple method. The rationale for
doing this is that some storage AMs might want to keep several buffers
pinned at once, for example, and must not to release those pins
individually but in batches as the scan moves forwards (say a batch of
tuples in a columnar storage AM has column values spread across many
buffers; they must all be kept pinned until the scan has moved past the
whole set of tuples). But I'm not really sure that this is a great
design.

I welcome comments on these ideas. My patch for this is nowhere near
completion yet; expect things to change for items that I've overlooked,
but I hope I didn't overlook any major. If things are handwavy, it is
probably because I haven't fully figured them out yet.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#2Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alvaro Herrera (#1)
Re: Pluggable storage

On Sat, Aug 13, 2016 at 2:15 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

Many have expressed their interest in this topic, but I haven't seen any
design of how it should work.

That's it. My design presented at PGCon was very sketchy, and I didn't
deliver any prototype yet.

Here's my attempt; I've been playing with
this for some time now and I think what I propose here is a good initial
plan.

Great! It's nice to see you're working in this direction!

This will allow us to write permanent table storage that works
differently than heapam.c. At this stage, I haven't throught through
whether this is going to allow extensions to define new storage modules;
I am focusing on AMs that can coexist with heapam in core.

So, as I get you're proposing extendability to replace heap with something
another
but compatible with heap (with executor nodes, index access methods and so
on).
That's good, but what alternative storage access methods could be?
AFAICS, we can't fit here, for instance, another MVCC implementation (undo
log) or
in-memory storage or columnar storage. However, it seems that we would be
able to make compressed heap or alternative HOT/WARM tuples mechanism.
Correct me if I'm wrong.

ISTM you're proposing something quite orthogonal to my view
of pluggable storage engines. My idea, in general, was to extend FDW
mechanism
to let it be something manageable inside database (support for VACUUM,
defining
indexes and so on). But imperative was that it should have its own
executor nodes,
and it doesn't have to compatible with existing index access methods.

Therefore, I think my design presented at PGCon and your current proposal
are
about orthogonal features which could coexist.

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

#3Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#1)
Re: Pluggable storage

On Fri, Aug 12, 2016 at 7:15 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Many have expressed their interest in this topic, but I haven't seen any
design of how it should work. Here's my attempt; I've been playing with
this for some time now and I think what I propose here is a good initial
plan. This will allow us to write permanent table storage that works
differently than heapam.c. At this stage, I haven't throught through
whether this is going to allow extensions to define new storage modules;
I am focusing on AMs that can coexist with heapam in core.

Thanks for taking a stab at this. I'd like to throw out a few concerns.

One, I'm worried that adding an additional layer of pointer-jumping is
going to slow things down and make Andres' work to speed up the
executor more difficult. I don't know that there is a problem there,
and if there is a problem I don't know what to do about it, but I
think it's something we need to consider. I am somewhat inclined to
believe that we need to restructure the executor in a bigger way so
that it passes around datums instead of tuples; I'm inclined to
believe that the current tuple-centric model is probably not optimal
even for the existing storage format. It seems even less likely to be
right for a data format in which fetching columns is more expensive
than currently, such as a columnar store.

Two, I think that we really need to think very hard about how the
query planner will interact with new heap storage formats. For
example, suppose cstore_fdw were rewritten as a new heap storage
format. Because ORC contains internal indexing structures with
characteristics somewhat similar to BRIN, many scans can be executed
much more efficiently than for our current heap storage format. If it
can be seen that an entire chunk will fail to match the quals, we can
skip the whole chunk. Some operations may permit additional
optimizations: for example, given SELECT count(*) FROM thing WHERE
quals, we may be able to push the COUNT(*) down into the heap access
layer. If it can be verified that EVERY tuple in a chunk will match
the quals, we can just increment the count by that number without
visiting each tuple individually. This could be really fast. These
kinds of query planner issues are generally why I have favored trying
to do something like this through the FDW interface, which already has
the right APIs for this kind of thing, even if we're not using them
all yet. I don't say that's the only way to crack this problem, but I
think we're going to find that a heap storage API that doesn't include
adequate query planner integration is not a very exciting thing.

Three, with respect to this limitation:

iii) All tuples need to be identifiable by ItemPointers. Storages that
have different requirements will need careful additional thought across
the board.

I think it's a good idea for a first patch in this area to ignore (or
mostly ignore) this problem - e.g. maybe allow such storage formats
but refuse to create indexes on them. But eventually I think we're
going to want/need to do something about it. There are an awful lot
of interesting ideas that we can't pursue without addressing this.

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Anastasia Lubennikova
a.lubennikova@postgrespro.ru
In reply to: Alvaro Herrera (#1)
Re: Pluggable storage

13.08.2016 02:15, Alvaro Herrera:

Many have expressed their interest in this topic, but I haven't seen any
design of how it should work. Here's my attempt; I've been playing with
this for some time now and I think what I propose here is a good initial
plan. This will allow us to write permanent table storage that works
differently than heapam.c. At this stage, I haven't throught through
whether this is going to allow extensions to define new storage modules;
I am focusing on AMs that can coexist with heapam in core.

Hello,
thank you for starting this topic.

I am working on a very similar proposal in another thread.
https://commitfest.postgresql.org/10/700/

At first glance our drafts are very similar. I hope it means that we are
moving in the right direction. It's great that your proposal is focused on
interactions with executor while mine are mostly about internals of new
StorageAm interface and interactions with a buffer and storage management.

Please read and comment https://wiki.postgresql.org/wiki/HeapamRefactoring

I'll leave more concrete comments tomorrow.
Thank you for the explicit description of issues.

The design starts with a new row type in pg_am, of type "s" (for "storage").
The handler function returns a struct of node StorageAmRoutine. This
contains functions for 1) scans (beginscan, getnext, endscan) 2) tuples
(tuple_insert/update/delete/lock, as well as set_oid, get_xmin and the
like), and operations on tuples that are part of slots (tuple_deform,
materialize).
To support this, we introduce StorageTuple and StorageScanDesc.
StorageTuples represent a physical tuple coming from some storage AM.
It is necessary to have a pointer to a StorageAmRoutine in order to
manipulate the tuple. For heapam.c, a StorageTuple is just a HeapTuple.

RelationData gains ->rd_stamroutine which is a pointer to the
StorageAmRoutine for the relation in question. Similarly,
TupleTableSlot is augmented with a link to the StorageAmRoutine to
handle the StorageTuple it contains (probably in most cases it's set at
the same time as the tupdesc). This implies that routines such as
ExecAssignScanType need to pass down the StorageAmRoutine from the
relation to the slot.

The executor is modified so that instead of calling heap_insert etc
directly, it uses rel->rd_stamroutine to call these methods. The
executor is still in charge of dealing with indexes, constraints, and
any other thing that's not the tuple storage itself (this is one major
point in which this differs from FDWs). This all looks simple enough,
with one exception and a few notes:

exception a) ExecMaterializeSlot needs special consideration. This is
used in two different ways: a1) is the stated "make tuple independent
from any underlying storage" point, which is handled by
ExecMaterializeSlot itself and calling a method from the storage AM to
do any byte copying as needed. ExecMaterializeSlot no longer returns a
HeapTuple, because there might not be any. The second usage pattern a2)
is to create a HeapTuple that's passed to other modules which only deal
with HT and not slots (triggers are the main case I noticed, but I think
there are others such as the executor itself wanting tuples as Datum for
some reason). For the moment I'm handling this by having a new
ExecHeapifyTuple which creates a HeapTuple from a slot, regardless of
the original tuple format.

note b) EvalPlanQual currently maintains an array of HeapTuple in
EState->es_epqTuple. I think it works to replace that with an array of
StorageTuples; EvalPlanQualFetch needs to call the StorageAmRoutine
methods in order to interact with it. Other than those changes, it
seems okay.

note c) nodeSubplan has curTuple as a HeapTuple. It seems simple
to replace this with an independent slot-based tuple.

note d) grp_firstTuple in nodeAgg / nodeSetOp. These are less
simple than the above, but replacing the HeapTuple with a slot-based
tuple seems doable too.

note e) nodeLockRows uses lr_curtuples to feed EvalPlanQual.
TupleTableSlot also seems a good replacement. This has fallout in other
users of EvalPlanQual, too.

note f) More widespread, MinimalTuples currently use a tweaked HeapTuple
format. In the long run, it may be possible to replace them with a
separate storage module that's specifically designed to handle tuples
meant for tuplestores etc. That may simplify TupleTableSlot and
execTuples. For the moment we keep the tts_mintuple as it is. Whenever
a tuple is not already in heap format, we heapify it in order to put in
the store.

The current heapam.c routines need some changes. Currently, practice is
that heap_insert, heap_multi_insert, heap_fetch, heap_update scribble on
their input tuples to set the resulting ItemPointer in tuple->t_self.
This is messy if we want StorageTuples to be abstract. I'm changing
this so that the resulting ItemPointer is returned in a separate output
argument; the tuple itself is left alone. This is somewhat messy in the
case of heap_multi_insert because it returns several items; I think it's
acceptable to return an array of ItemPointers in the same order as the
input tuples. This works fine for the only caller, which is COPY in
batch mode. For the other routines, they don't really care where the
TID is returned AFAICS.

Additional noteworthy items:

i) Speculative insertion: the speculative insertion token is no longer
installed directly in the heap tuple by the executor (of course).
Instead, the token becomes part of the slot. When the tuple_insert
method is called, the insertion routine is in charge of setting the
token from the slot into the storage tuple. Executor is in charge of
calling method->speculative_finish() / abort() once the insertion has
been confirmed by the indexes.

ii) execTuples has additional accessors for tuples-in-slot, such as
ExecFetchSlotTuple and friends. I expect to have some of them to return
abstract StorageTuples, others HeapTuple or MinimalTuples (possibly
wrapped in Datum), depending on callers. We might be able to cut down
on these later; my first cut will try to avoid API changes to keep
fallout to a minimum.

iii) All tuples need to be identifiable by ItemPointers. Storages that
have different requirements will need careful additional thought across
the board.

iv) System catalogs cannot use pluggable storage. We continue to use
heap_open etc in the DDL code, in order not to make this more invasive
that it already is. We may lift this restriction later for specific
catalogs, as needed.

v) Currently, one Buffer may be associated with one HeapTuple living in a
slot; when the slot is cleared, the buffer pin is released. My current
patch moves the buffer pin to inside the heapam-based storage AM and the
buffer is released by the ->slot_clear_tuple method. The rationale for
doing this is that some storage AMs might want to keep several buffers
pinned at once, for example, and must not to release those pins
individually but in batches as the scan moves forwards (say a batch of
tuples in a columnar storage AM has column values spread across many
buffers; they must all be kept pinned until the scan has moved past the
whole set of tuples). But I'm not really sure that this is a great
design.

I welcome comments on these ideas. My patch for this is nowhere near
completion yet; expect things to change for items that I've overlooked,
but I hope I didn't overlook any major. If things are handwavy, it is
probably because I haven't fully figured them out yet.

--
Anastasia Lubennikova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#5Andres Freund
andres@anarazel.de
In reply to: Robert Haas (#3)
Re: Pluggable storage

On 2016-08-15 12:02:18 -0400, Robert Haas wrote:

Thanks for taking a stab at this. I'd like to throw out a few concerns.

One, I'm worried that adding an additional layer of pointer-jumping is
going to slow things down and make Andres' work to speed up the
executor more difficult. I don't know that there is a problem there,
and if there is a problem I don't know what to do about it, but I
think it's something we need to consider.

I'm quite concerned about that as well.

I am somewhat inclined to
believe that we need to restructure the executor in a bigger way so
that it passes around datums instead of tuples; I'm inclined to
believe that the current tuple-centric model is probably not optimal
even for the existing storage format.

I actually prototyped that, and it's not an easy win so far. Column
extraction cost, even after significant optimization, is still often a
significant portion of the runtime. And e.g. projection only extracting
all columns, after evaluating a restrictive qual referring to an "early"
column, can be a significant win. We'd definitely have to give up on
extracting columns 0..n when accessing later columns... Hm.

Greetings,

Andres Freund

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#6Anastasia Lubennikova
a.lubennikova@postgrespro.ru
In reply to: Alvaro Herrera (#1)
Re: Pluggable storage

13.08.2016 02:15, Alvaro Herrera:

Many have expressed their interest in this topic, but I haven't seen any
design of how it should work. Here's my attempt; I've been playing with
this for some time now and I think what I propose here is a good initial
plan. This will allow us to write permanent table storage that works
differently than heapam.c. At this stage, I haven't throught through
whether this is going to allow extensions to define new storage modules;
I am focusing on AMs that can coexist with heapam in core.

The design starts with a new row type in pg_am, of type "s" (for "storage").
The handler function returns a struct of node StorageAmRoutine. This
contains functions for 1) scans (beginscan, getnext, endscan) 2) tuples
(tuple_insert/update/delete/lock, as well as set_oid, get_xmin and the
like), and operations on tuples that are part of slots (tuple_deform,
materialize).

To support this, we introduce StorageTuple and StorageScanDesc.
StorageTuples represent a physical tuple coming from some storage AM.
It is necessary to have a pointer to a StorageAmRoutine in order to
manipulate the tuple. For heapam.c, a StorageTuple is just a HeapTuple.

StorageTuples concept looks really cool. I've got some questions on
details of implementation.

Do StorageTuples have fields common to all implementations?
Or StorageTuple is totally abstract structure that has nothing to do
with data, except pointing to it?

I mean, now we already have HeapTupleData structure, which is a pretty
good candidate to replace with StorageTuple.
It's already widely used in executor and moreover, it's the only structure
(except MinimalTuples and all those crazy optimizations) that works with
tuples, both extracted from the page or created on-the-fly in executor node.

typedef struct HeapTupleData
{
uint32 t_len; /* length of *t_data */
ItemPointerData t_self; /* SelfItemPointer */
Oid t_tableOid; /* table the tuple came from */
HeapTupleHeader t_data; /* -> tuple header and data */
} HeapTupleData;

We can simply change t_data type from HeapTupleHeader to Pointer.
And maybe add a "t_handler" field that points out to handler functions.
I don't sure if it will be a name of StorageAm, or its OID, or maybe the
main function itself. Although, If I'm not mistaken, we always have
RelationData when we want to operate the tuple, so having t_handler
in the StorageTuple is excessive.

typedef struct StorageTupleData
{
uint32 t_len; /* length of *t_data */
ItemPointerData t_self; /* SelfItemPointer */
Oid t_tableOid; /* table the tuple came from */
Pointer t_data; /* -> tuple header and data
* This field should never be
accessed directly,
* only via StorageAm handler
functions,
* because we don't know
underlying data structure.
*/
??? t_handler; /* StorageAm that knows what to do
with the tuple */
} StorageTupleData
;

This approach allows to minimize code changes and ensure that we
won't miss any function that handles tuples.

Do you see any weak points of the suggestion?
What design do you use in your prototype?

RelationData gains ->rd_stamroutine which is a pointer to the
StorageAmRoutine for the relation in question. Similarly,
TupleTableSlot is augmented with a link to the StorageAmRoutine to
handle the StorageTuple it contains (probably in most cases it's set at
the same time as the tupdesc). This implies that routines such as
ExecAssignScanType need to pass down the StorageAmRoutine from the
relation to the slot.

If we already have this pointer in t_handler as described below,
we don't need to pass it between functions and slots.

The executor is modified so that instead of calling heap_insert etc
directly, it uses rel->rd_stamroutine to call these methods. The
executor is still in charge of dealing with indexes, constraints, and
any other thing that's not the tuple storage itself (this is one major
point in which this differs from FDWs). This all looks simple enough,
with one exception and a few notes:

That is exactly what I tried to describe in my proposal.
Chapter "Relation management". I'm sure, you've already noticed
that it will require huge source code cleaning. I've carefully read
the sources and found "violators" of abstraction in src/backend/commands.
The list is attached to the wiki page
https://wiki.postgresql.org/wiki/HeapamRefactoring.

Except these, there are some pretty strange and unrelated functions in
src/backend/catalog.
I'm willing to fix them, but I'd like to synchronize our efforts.

exception a) ExecMaterializeSlot needs special consideration. This is
used in two different ways: a1) is the stated "make tuple independent
from any underlying storage" point, which is handled by
ExecMaterializeSlot itself and calling a method from the storage AM to
do any byte copying as needed. ExecMaterializeSlot no longer returns a
HeapTuple, because there might not be any. The second usage pattern a2)
is to create a HeapTuple that's passed to other modules which only deal
with HT and not slots (triggers are the main case I noticed, but I think
there are others such as the executor itself wanting tuples as Datum for
some reason). For the moment I'm handling this by having a new
ExecHeapifyTuple which creates a HeapTuple from a slot, regardless of
the original tuple format.

Yes, triggers are a very special case. Thank you for the explanation.

That still goes well with my suggestion of a format.
Nothing to do, just substitute t_data with proper HeapTupleHeader
representation. I think it's a job for StorageAm. Let's say each StorageAm
must have stam_to_heaptuple() function and opposite function
stam_from_heaptuple().

note b) EvalPlanQual currently maintains an array of HeapTuple in
EState->es_epqTuple. I think it works to replace that with an array of
StorageTuples; EvalPlanQualFetch needs to call the StorageAmRoutine
methods in order to interact with it. Other than those changes, it
seems okay.

note c) nodeSubplan has curTuple as a HeapTuple. It seems simple
to replace this with an independent slot-based tuple.

note d) grp_firstTuple in nodeAgg / nodeSetOp. These are less
simple than the above, but replacing the HeapTuple with a slot-based
tuple seems doable too.

note e) nodeLockRows uses lr_curtuples to feed EvalPlanQual.
TupleTableSlot also seems a good replacement. This has fallout in other
users of EvalPlanQual, too.

note f) More widespread, MinimalTuples currently use a tweaked HeapTuple
format. In the long run, it may be possible to replace them with a
separate storage module that's specifically designed to handle tuples
meant for tuplestores etc. That may simplify TupleTableSlot and
execTuples. For the moment we keep the tts_mintuple as it is. Whenever
a tuple is not already in heap format, we heapify it in order to put in
the store.

I wonder, do we really need MinimalTuples to support all formats?

The current heapam.c routines need some changes. Currently, practice is
that heap_insert, heap_multi_insert, heap_fetch, heap_update scribble on
their input tuples to set the resulting ItemPointer in tuple->t_self.
This is messy if we want StorageTuples to be abstract. I'm changing
this so that the resulting ItemPointer is returned in a separate output
argument; the tuple itself is left alone. This is somewhat messy in the
case of heap_multi_insert because it returns several items; I think it's
acceptable to return an array of ItemPointers in the same order as the
input tuples. This works fine for the only caller, which is COPY in
batch mode. For the other routines, they don't really care where the
TID is returned AFAICS.

Additional noteworthy items:

i) Speculative insertion: the speculative insertion token is no longer
installed directly in the heap tuple by the executor (of course).
Instead, the token becomes part of the slot. When the tuple_insert
method is called, the insertion routine is in charge of setting the
token from the slot into the storage tuple. Executor is in charge of
calling method->speculative_finish() / abort() once the insertion has
been confirmed by the indexes.

ii) execTuples has additional accessors for tuples-in-slot, such as
ExecFetchSlotTuple and friends. I expect to have some of them to return
abstract StorageTuples, others HeapTuple or MinimalTuples (possibly
wrapped in Datum), depending on callers. We might be able to cut down
on these later; my first cut will try to avoid API changes to keep
fallout to a minimum.

I'd suggest replacing all occurrences of HeapTuple with StorageTuple.
Do you see any problems with it?

iii) All tuples need to be identifiable by ItemPointers. Storages that
have different requirements will need careful additional thought across
the board.

For a start, we can simply deny secondary indexes for these storages
or require a function that converts tuple identifier inside the storage to
ItemPointer suitable for an index.

iv) System catalogs cannot use pluggable storage. We continue to use
heap_open etc in the DDL code, in order not to make this more invasive
that it already is. We may lift this restriction later for specific
catalogs, as needed.

+1

v) Currently, one Buffer may be associated with one HeapTuple living in a
slot; when the slot is cleared, the buffer pin is released. My current
patch moves the buffer pin to inside the heapam-based storage AM and the
buffer is released by the ->slot_clear_tuple method. The rationale for
doing this is that some storage AMs might want to keep several buffers
pinned at once, for example, and must not to release those pins
individually but in batches as the scan moves forwards (say a batch of
tuples in a columnar storage AM has column values spread across many
buffers; they must all be kept pinned until the scan has moved past the
whole set of tuples). But I'm not really sure that this is a great
design.

Frankly, I doubt that it's real to implement columnar storage just as
a variant of pluggable storage. It requires a lot of changes in executor
and optimizer and so on, which are hardly compatible with existing
tuple-oriented model. However I'm not so good in this area, so if you
feel that it's possible, go ahead.

I welcome comments on these ideas. My patch for this is nowhere near
completion yet; expect things to change for items that I've overlooked,
but I hope I didn't overlook any major. If things are handwavy, it is
probably because I haven't fully figured them out yet.

Thank you again for beginning the big project.
Looking forward to the prototype. I think it will make the discussion
more concrete and useful.

--
Anastasia Lubennikova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#7Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Anastasia Lubennikova (#6)
Re: Pluggable storage

Anastasia Lubennikova wrote:

13.08.2016 02:15, Alvaro Herrera:

To support this, we introduce StorageTuple and StorageScanDesc.
StorageTuples represent a physical tuple coming from some storage AM.
It is necessary to have a pointer to a StorageAmRoutine in order to
manipulate the tuple. For heapam.c, a StorageTuple is just a HeapTuple.

StorageTuples concept looks really cool. I've got some questions on
details of implementation.

Do StorageTuples have fields common to all implementations?
Or StorageTuple is totally abstract structure that has nothing to do
with data, except pointing to it?

I mean, now we already have HeapTupleData structure, which is a pretty
good candidate to replace with StorageTuple.

I was planning to replace all uses of HeapTuple in the executor with
StorageTuple, actually. But the main reason I would like to avoid
HeapTupleData itself is that it contains an assumption that there is a
single palloc chunk that contains the tuple (t_len and t_data). This
might not be true in representations that split the tuple, for example
in columnar storage where you have one column in page A and another
column in page B, for the same tuple. I suppose there might be some
point to keeping t_tableOid and t_self, though.

And maybe add a "t_handler" field that points out to handler functions.
I don't sure if it will be a name of StorageAm, or its OID, or maybe the
main function itself. Although, If I'm not mistaken, we always have
RelationData when we want to operate the tuple, so having t_handler
in the StorageTuple is excessive.

Yeah, I think the RelationData (or more precisely the StorageAmRoutine)
is going to be available always, so I don't think we need a pointer in
the tuple itself.

This approach allows to minimize code changes and ensure that we
won't miss any function that handles tuples.

Do you see any weak points of the suggestion?
What design do you use in your prototype?

It's currently a "void *" pointer in my prototype.

RelationData gains ->rd_stamroutine which is a pointer to the
StorageAmRoutine for the relation in question. Similarly,
TupleTableSlot is augmented with a link to the StorageAmRoutine to
handle the StorageTuple it contains (probably in most cases it's set at
the same time as the tupdesc). This implies that routines such as
ExecAssignScanType need to pass down the StorageAmRoutine from the
relation to the slot.

If we already have this pointer in t_handler as described below,
we don't need to pass it between functions and slots.

I think it's better to have it in slots, so you can install multiple
tuples in the slot without having to change the routine pointers each
time.

The executor is modified so that instead of calling heap_insert etc
directly, it uses rel->rd_stamroutine to call these methods. The
executor is still in charge of dealing with indexes, constraints, and
any other thing that's not the tuple storage itself (this is one major
point in which this differs from FDWs). This all looks simple enough,
with one exception and a few notes:

That is exactly what I tried to describe in my proposal.
Chapter "Relation management". I'm sure, you've already noticed
that it will require huge source code cleaning. I've carefully read
the sources and found "violators" of abstraction in src/backend/commands.
The list is attached to the wiki page
https://wiki.postgresql.org/wiki/HeapamRefactoring.

Except these, there are some pretty strange and unrelated functions in
src/backend/catalog.
I'm willing to fix them, but I'd like to synchronize our efforts.

I very much would like to stay away from touching src/backend/catalog,
which are the functions that deal with system catalogs. We can simply
say that system catalogs are hardcoded to use heapam.c storage for now.
If we later see a need to enable some particular catalog using a
different storage implementation, we can change the code for that
specific catalog in src/backend/catalog and everywhere else, to use the
abstract API instead of hardcoding heap_insert etc. But that can be
left for a second pass. (This is my point "iv" further below, to which
you said "+1").

Nothing to do, just substitute t_data with proper HeapTupleHeader
representation. I think it's a job for StorageAm. Let's say each StorageAm
must have stam_to_heaptuple() function and opposite function
stam_from_heaptuple().

Hmm, yeah, that also works. We'd have to check again whether it's more
convenient to start as a slot rather than a StorageTuple. AFAICS the
trigger.c code is all starting from a slot, so it makes sense to have
the conversion use the slot code -- that way, there's no need for each
storageAM to re-implement conversion to HeapTuple.

note f) More widespread, MinimalTuples currently use a tweaked HeapTuple
format. In the long run, it may be possible to replace them with a
separate storage module that's specifically designed to handle tuples
meant for tuplestores etc. That may simplify TupleTableSlot and
execTuples. For the moment we keep the tts_mintuple as it is. Whenever
a tuple is not already in heap format, we heapify it in order to put in
the store.

I wonder, do we really need MinimalTuples to support all formats?

Sure. I wouldn't want to say "you can create table in columnar storage
format, but if you do, these tables cannot use hash join".

ii) execTuples has additional accessors for tuples-in-slot, such as
ExecFetchSlotTuple and friends. I expect to have some of them to return
abstract StorageTuples, others HeapTuple or MinimalTuples (possibly
wrapped in Datum), depending on callers. We might be able to cut down
on these later; my first cut will try to avoid API changes to keep
fallout to a minimum.

I'd suggest replacing all occurrences of HeapTuple with StorageTuple.
Do you see any problems with it?

The HeapTuple-in-datum representation, as I recall, is used in the SQL
function manager; maybe other places too. Maybe there's a way to fix
that layer so that it uses StorageTuple instead, but I prefer not to
touch it in the first phase. We can fix it later. This is already a
big enough patch ...

iii) All tuples need to be identifiable by ItemPointers. Storages that
have different requirements will need careful additional thought across
the board.

For a start, we can simply deny secondary indexes for these storages
or require a function that converts tuple identifier inside the storage to
ItemPointer suitable for an index.

Umm. I don't think rejecting secondary indexes would work very well. I
think we can lift this limitation later; we just need to change the
IndexTuple abstraction so that it doesn't rely on ItemPointer as
currently.

v) Currently, one Buffer may be associated with one HeapTuple living in a
slot; when the slot is cleared, the buffer pin is released. My current
patch moves the buffer pin to inside the heapam-based storage AM and the
buffer is released by the ->slot_clear_tuple method. The rationale for
doing this is that some storage AMs might want to keep several buffers
pinned at once, for example, and must not to release those pins
individually but in batches as the scan moves forwards (say a batch of
tuples in a columnar storage AM has column values spread across many
buffers; they must all be kept pinned until the scan has moved past the
whole set of tuples). But I'm not really sure that this is a great
design.

Frankly, I doubt that it's real to implement columnar storage just as
a variant of pluggable storage. It requires a lot of changes in executor
and optimizer and so on, which are hardly compatible with existing
tuple-oriented model. However I'm not so good in this area, so if you
feel that it's possible, go ahead.

Well, not *just* as a variant of pluggable storage. This thread is just
one sub-project inside the greater project to enable column-oriented
storage; that includes further changes to executor, too, but I haven't
discussed those in this proposal. I mentioned all this in Brussels'
developer meeting earlier this year. (There I mostly talked about
vertical partitioning, which is a different subproject that I've put
aside for the moment, but really it's all part of the same thing.)
https://wiki.postgresql.org/wiki/Future_of_storage

Thanks for reading!

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#8Simon Riggs
simon@2ndquadrant.com
In reply to: Andres Freund (#5)
Re: Pluggable storage

On 16 August 2016 at 19:46, Andres Freund <andres@anarazel.de> wrote:

On 2016-08-15 12:02:18 -0400, Robert Haas wrote:

Thanks for taking a stab at this. I'd like to throw out a few concerns.

One, I'm worried that adding an additional layer of pointer-jumping is
going to slow things down and make Andres' work to speed up the
executor more difficult. I don't know that there is a problem there,
and if there is a problem I don't know what to do about it, but I
think it's something we need to consider.

I'm quite concerned about that as well.

This objection would apply to all other proposals as well, FDW etc..

Do you see some way to add flexibility yet without adding a branch
point in the code?

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#9Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Simon Riggs (#8)
Re: Pluggable storage

On Thu, Aug 18, 2016 at 10:58 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

On 16 August 2016 at 19:46, Andres Freund <andres@anarazel.de> wrote:

On 2016-08-15 12:02:18 -0400, Robert Haas wrote:

Thanks for taking a stab at this. I'd like to throw out a few concerns.

One, I'm worried that adding an additional layer of pointer-jumping is
going to slow things down and make Andres' work to speed up the
executor more difficult. I don't know that there is a problem there,
and if there is a problem I don't know what to do about it, but I
think it's something we need to consider.

I'm quite concerned about that as well.

This objection would apply to all other proposals as well, FDW etc..

Do you see some way to add flexibility yet without adding a branch
point in the code?

It's impossible without branch point in code. The question is where this
branch should be located.
In particular, be can put this branch point into planner by defining
distinct executor nodes for each pluggable storage. In this case, each
storage would have own optimized executor nodes.

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

#10Amit Kapila
amit.kapila16@gmail.com
In reply to: Alvaro Herrera (#7)
Re: Pluggable storage

On Wed, Aug 17, 2016 at 10:33 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Anastasia Lubennikova wrote:

Except these, there are some pretty strange and unrelated functions in
src/backend/catalog.
I'm willing to fix them, but I'd like to synchronize our efforts.

I very much would like to stay away from touching src/backend/catalog,
which are the functions that deal with system catalogs. We can simply
say that system catalogs are hardcoded to use heapam.c storage for now.

Does this mean that if any storage needs to access system catalog
information, they need to be aware of HeapTuple and other required
stuff like syscache? Again, if they need to update some stats or
something like that, they need to be aware of heap tuple format.

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#11Ants Aasma
ants.aasma@eesti.ee
In reply to: Alvaro Herrera (#1)
Re: Pluggable storage

On Tue, Aug 16, 2016 at 9:46 PM, Andres Freund <andres@anarazel.de> wrote:

On 2016-08-15 12:02:18 -0400, Robert Haas wrote:

I am somewhat inclined to
believe that we need to restructure the executor in a bigger way so
that it passes around datums instead of tuples; I'm inclined to
believe that the current tuple-centric model is probably not optimal
even for the existing storage format.

I actually prototyped that, and it's not an easy win so far. Column
extraction cost, even after significant optimization, is still often a
significant portion of the runtime. And e.g. projection only extracting
all columns, after evaluating a restrictive qual referring to an "early"
column, can be a significant win. We'd definitely have to give up on
extracting columns 0..n when accessing later columns... Hm.

What about going even further than [1]/messages/by-id/20160714011850.bd5zhu35szle3n3c@alap3.anarazel.de in converting the executor to
being opcode based and merging projection and qual evaluation to a
single pass? Optimizer would then have some leeway about how to order
column extraction and qual evaluation. Might even be worth it to
special case some functions as separate opcodes (e.g. int4eq,
timestamp_lt).

Regards,
Ants Aasma

[1]: /messages/by-id/20160714011850.bd5zhu35szle3n3c@alap3.anarazel.de

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#12Andres Freund
andres@anarazel.de
In reply to: Ants Aasma (#11)
Re: Pluggable storage

On August 18, 2016 7:44:50 AM PDT, Ants Aasma <ants.aasma@eesti.ee> wrote:

On Tue, Aug 16, 2016 at 9:46 PM, Andres Freund <andres@anarazel.de>
wrote:

On 2016-08-15 12:02:18 -0400, Robert Haas wrote:

I am somewhat inclined to
believe that we need to restructure the executor in a bigger way so
that it passes around datums instead of tuples; I'm inclined to
believe that the current tuple-centric model is probably not optimal
even for the existing storage format.

I actually prototyped that, and it's not an easy win so far. Column
extraction cost, even after significant optimization, is still often

a

significant portion of the runtime. And e.g. projection only

extracting

all columns, after evaluating a restrictive qual referring to an

"early"

column, can be a significant win. We'd definitely have to give up on
extracting columns 0..n when accessing later columns... Hm.

What about going even further than [1] in converting the executor to
being opcode based and merging projection and qual evaluation to a
single pass? Optimizer would then have some leeway about how to order
column extraction and qual evaluation. Might even be worth it to
special case some functions as separate opcodes (e.g. int4eq,
timestamp_lt).

Regards,
Ants Aasma

[1]
/messages/by-id/20160714011850.bd5zhu35szle3n3c@alap3.anarazel.de

Good question. I think I have a reasonable answer, but lets discuss that in the other thread.

Andres
--
Sent from my Android device with K-9 Mail. Please excuse my brevity.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#13Andres Freund
andres@anarazel.de
In reply to: Simon Riggs (#8)
Re: Pluggable storage

On 2016-08-18 08:58:11 +0100, Simon Riggs wrote:

On 16 August 2016 at 19:46, Andres Freund <andres@anarazel.de> wrote:

On 2016-08-15 12:02:18 -0400, Robert Haas wrote:

Thanks for taking a stab at this. I'd like to throw out a few concerns.

One, I'm worried that adding an additional layer of pointer-jumping is
going to slow things down and make Andres' work to speed up the
executor more difficult. I don't know that there is a problem there,
and if there is a problem I don't know what to do about it, but I
think it's something we need to consider.

I'm quite concerned about that as well.

This objection would apply to all other proposals as well, FDW etc..

Not really. The place you draw your boundary significantly influences
where and how much of a price you pay. Having another indirection
inside HeapTuple - which is accessed in many many places, is something
different from having a seqscan equivalent, which returns you a batch of
already deformed tuples in array form. In the latter case there's one
additional indirection per batch of tuples, in the former there's many
for each tuple.

Do you see some way to add flexibility yet without adding a branch
point in the code?

I'm not even saying that the approach of doing the indirection inside
the HeapTuple replacement is a no-go, just that it concerns me. I do
think that working on only lugging arround values/isnull arrays is
something that I could see working better, if some problems are
addressed beforehand.

Greetings,

Andres Freund

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#14Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#1)
Re: Pluggable storage

Alvaro Herrera wrote:

Many have expressed their interest in this topic, but I haven't seen any
design of how it should work. Here's my attempt; I've been playing with
this for some time now and I think what I propose here is a good initial
plan.

I regret to announce that I'll have to stay away from this topic for a
little while, as I have another project taking priority. I expect to
return to this shortly thereafter, hopefully in time to get it done for
pg10.

If anyone is interested in helping with the (currently not compilable)
patch I have, please mail me privately and we can discuss.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#15Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#14)
Re: Pluggable storage

I have sent the partial patch I have to Hari Babu Kommi. We expect that
he will be able to further this goal some more.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#16Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Alvaro Herrera (#15)
10 attachment(s)
Re: Pluggable storage

On Fri, Oct 14, 2016 at 7:26 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

I have sent the partial patch I have to Hari Babu Kommi. We expect that
he will be able to further this goal some more.

Thanks Alvaro for sharing your development patch.

Most of the patch design is same as described by Alvaro in the first mail
[1]: /messages/by-id/20160812231527. GA690404%40alvherre.pgsql
I will detail the modifications, pending items and open items (needs
discussion)
to implement proper pluggable storage.

Here I attached WIP patches to support pluggable storage. The patch series
are may not work individually. Still so many things are under development.
These patches are just to share the approach of the current development.

Some notable changes that I did to make the patch work:

1. Added storageam handler to the slot, this is because not all places
the relation is not available in handy.
2. Retained the minimal Tuple in the slot, as this is used in HASH join.
As per the first version, I feel it is fine to allow creating HeapTuple
format data.

Thanks everyone for sharing their ideas in the developer's unconference at
PGCon Ottawa.

Pending items:

1. Replacement of Tuple with slot in Trigger functionality
2. Replacement of Tuple with Slot from storage handler functions.
3. Remove/minimize the use of HeapTuple as a Datum.
4. Replace all references of HeapScanDesc with StorageScanDesc
5. Planner changes to consider the relation storage during the planning.
6. Any planner changes based on the discussion of open items?
7. some Executor changes to consider the storage advantages?

Open Items:

1. The BitmapHeapScan and TableSampleScan are tightly coupled with
HeapTuple and HeapScanDesc, So these scans are directly operating
on those structures and providing the result.

These scan types may not be applicable to different storage formats.
So how to handle them?

Currently my goal to provide a basic infrastructure of pluggable storage as
a first step and later improve it further to improve the performance by
taking the advantage of storage.

[1]: /messages/by-id/20160812231527. GA690404%40alvherre.pgsql
GA690404%40alvherre.pgsql

Regards,
Hari Babu
Fujitsu Australia

Attachments:

0005-Adding-storageam-handler-to-slot.patchapplication/octet-stream; name=0005-Adding-storageam-handler-to-slot.patchDownload
From 8bc00e8dc5e12caa6995b62346fd4c9f555eaba0 Mon Sep 17 00:00:00 2001
From: Hari <haribabuk@fast.au.fujitsu.com>
Date: Thu, 1 Jun 2017 22:01:38 +1000
Subject: [PATCH 05/10] Adding storageam handler to slot

Storage am handler to slot and also changing
the slot structure according to new access methods
---
 src/backend/access/common/heaptuple.c |  25 +++--
 src/backend/executor/execTuples.c     | 172 +++++++++++++++++-----------------
 src/include/access/htup_details.h     |  12 +++
 src/include/executor/tuptable.h       |  50 ++++++----
 src/include/nodes/nodes.h             |   1 +
 5 files changed, 145 insertions(+), 115 deletions(-)

diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index c0086de..f04c870 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -1033,7 +1033,8 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 static void
 slot_deform_tuple(TupleTableSlot *slot, int natts)
 {
-	HeapTuple	tuple = slot->tts_tuple;
+	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;
@@ -1060,8 +1061,8 @@ slot_deform_tuple(TupleTableSlot *slot, int natts)
 	else
 	{
 		/* Restore state from previous execution */
-		off = slot->tts_off;
-		slow = slot->tts_slow;
+		off = stuple->hst_off;
+		slow = stuple->hst_slow;
 	}
 
 	tp = (char *) tup + tup->t_hoff;
@@ -1121,8 +1122,8 @@ slot_deform_tuple(TupleTableSlot *slot, int natts)
 	 * Save state for next execution
 	 */
 	slot->tts_nvalid = attnum;
-	slot->tts_off = off;
-	slot->tts_slow = slow;
+	stuple->hst_off = off;
+	stuple->hst_slow = slow;
 }
 
 /*
@@ -1140,7 +1141,8 @@ slot_deform_tuple(TupleTableSlot *slot, int natts)
 Datum
 slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
 {
-	HeapTuple	tuple = slot->tts_tuple;
+	HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage;
+	HeapTuple	tuple = stuple ? stuple->hst_heaptuple : NULL;
 	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
 	HeapTupleHeader tup;
 
@@ -1236,6 +1238,7 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
 void
 slot_getallattrs(TupleTableSlot *slot)
 {
+	HeapamTuple	*stuple;
 	int			tdesc_natts = slot->tts_tupleDescriptor->natts;
 	int			attnum;
 	HeapTuple	tuple;
@@ -1248,7 +1251,8 @@ slot_getallattrs(TupleTableSlot *slot)
 	 * otherwise we had better have a physical tuple (tts_nvalid should equal
 	 * natts in all virtual-tuple cases)
 	 */
-	tuple = slot->tts_tuple;
+	stuple = (HeapamTuple *) slot->tts_storage;
+	tuple = stuple->hst_heaptuple;
 	if (tuple == NULL)			/* internal error */
 		elog(ERROR, "cannot extract attribute from empty tuple slot");
 
@@ -1280,6 +1284,7 @@ slot_getallattrs(TupleTableSlot *slot)
 void
 slot_getsomeattrs(TupleTableSlot *slot, int attnum)
 {
+	HeapamTuple *stuple;
 	HeapTuple	tuple;
 	int			attno;
 
@@ -1295,7 +1300,8 @@ slot_getsomeattrs(TupleTableSlot *slot, int attnum)
 	 * otherwise we had better have a physical tuple (tts_nvalid should equal
 	 * natts in all virtual-tuple cases)
 	 */
-	tuple = slot->tts_tuple;
+	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");
 
@@ -1327,7 +1333,8 @@ slot_getsomeattrs(TupleTableSlot *slot, int attnum)
 bool
 slot_attisnull(TupleTableSlot *slot, int attnum)
 {
-	HeapTuple	tuple = slot->tts_tuple;
+	HeapamTuple *stuple = slot->tts_storage;
+	HeapTuple	tuple = stuple ? stuple->hst_heaptuple : NULL;
 	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
 
 	/*
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index c4a9553..6912163 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -82,8 +82,10 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/storageamapi.h"
 #include "access/tuptoaster.h"
 #include "funcapi.h"
+#include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "nodes/nodeFuncs.h"
 #include "storage/bufmgr.h"
@@ -113,16 +115,17 @@ 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_storageam = GetStorageAmRoutine(HEAPAM_STORAGE_AM_HANDLER_OID);
+	slot->tts_storage = NULL;
 
 	return slot;
 }
@@ -247,6 +250,7 @@ void
 ExecSetSlotDescriptor(TupleTableSlot *slot,		/* slot to change */
 					  TupleDesc tupdesc)		/* new tuple descriptor */
 {
+
 	/* For safety, make sure slot is empty before changing it */
 	ExecClearTuple(slot);
 
@@ -279,6 +283,30 @@ ExecSetSlotDescriptor(TupleTableSlot *slot,		/* slot to change */
 }
 
 /* --------------------------------
+ *		ExecSetSlotStorageRoutine
+ *
+ *		This function sets the storage AM routine pointer for the slot.
+ *
+ *		XXX should this be part of ExecSetSlotDescriptor?
+ * --------------------------------
+ */
+void
+ExecSetSlotStorageRoutine(TupleTableSlot *slot,
+						  StorageAmRoutine *routine)
+{
+	if (slot->tts_storageam)
+	{
+		/* XXX do we need any cleanup here? */
+	}
+
+	/*
+	 * XXX maybe we need refcounts for StorageAmRoutine objects?
+	 * or perhaps we need to copy here?
+	 */
+	slot->tts_storageam = routine;
+}
+
+/* --------------------------------
  *		ExecStoreTuple
  *
  *		This function is used to store a physical tuple into a specified
@@ -294,19 +322,6 @@ ExecSetSlotDescriptor(TupleTableSlot *slot,		/* slot to change */
  * on the buffer which is held until the slot is cleared, so that the tuple
  * won't go away on us.
  *
- * shouldFree is normally set 'true' for tuples constructed on-the-fly.
- * It must always be 'false' for tuples that are stored in disk pages,
- * since we don't want to try to pfree those.
- *
- * Another case where it is 'false' is when the referenced tuple is held
- * in a tuple table slot belonging to a lower-level executor Proc node.
- * In this case the lower-level slot retains ownership and responsibility
- * for eventually releasing the tuple.  When this method is used, we must
- * be certain that the upper-level Proc node will lose interest in the tuple
- * sooner than the lower-level one does!  If you're not certain, copy the
- * lower-level tuple with heap_copytuple and let the upper-level table
- * slot assume ownership of the copy!
- *
  * Return value is just the passed-in slot pointer.
  *
  * NOTE: before PostgreSQL 8.1, this function would accept a NULL tuple
@@ -317,7 +332,7 @@ ExecSetSlotDescriptor(TupleTableSlot *slot,		/* slot to change */
  * --------------------------------
  */
 TupleTableSlot *
-ExecStoreTuple(HeapTuple tuple,
+ExecStoreTuple(void *tuple,
 			   TupleTableSlot *slot,
 			   Buffer buffer,
 			   bool shouldFree)
@@ -328,47 +343,29 @@ ExecStoreTuple(HeapTuple tuple,
 	Assert(tuple != NULL);
 	Assert(slot != NULL);
 	Assert(slot->tts_tupleDescriptor != NULL);
+	Assert(slot->tts_storageam != 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_storageam->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_storageam->slot_store_tuple(slot, tuple, shouldFree);
+
 	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,10 +392,7 @@ 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);
+	slot->tts_storageam->slot_clear_tuple(slot);	/* XXX ?? */
 
 	/*
 	 * Drop the pin on the referenced buffer, if there is one.
@@ -412,9 +406,15 @@ ExecStoreMinimalTuple(MinimalTuple mtup,
 	 * 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;
+
+	/*
+	 * 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_storageam->slot_store_tuple(slot, &slot->tts_minhdr, false);
+
 	slot->tts_mintuple = mtup;
 
 	slot->tts_minhdr.t_len = mtup->t_len + MINIMAL_TUPLE_OFFSET;
@@ -444,25 +444,20 @@ 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;
+	slot->tts_storageam->slot_clear_tuple(slot);
 
 	/*
 	 * Drop the pin on the referenced buffer, if there is one.
+	 *
+	 * XXX probably this belongs in the storage AM rather than here.
 	 */
 	if (BufferIsValid(slot->tts_buffer))
 		ReleaseBuffer(slot->tts_buffer);
 
 	slot->tts_buffer = InvalidBuffer;
+	slot->tts_mintuple = NULL;
 
 	/*
 	 * Mark it empty.
@@ -541,7 +536,7 @@ ExecStoreAllNullTuple(TupleTableSlot *slot)
  *		however the "system columns" of the result will not be meaningful.
  * --------------------------------
  */
-HeapTuple
+StorageTuple
 ExecCopySlotTuple(TupleTableSlot *slot)
 {
 	/*
@@ -553,8 +548,8 @@ ExecCopySlotTuple(TupleTableSlot *slot)
 	/*
 	 * 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_storage && slot->tts_storageam->slot_is_physical_tuple(slot))
+		return slot->tts_storageam->slot_copy_tuple(slot);
 	if (slot->tts_mintuple)
 		return heap_tuple_from_minimal_tuple(slot->tts_mintuple);
 
@@ -588,8 +583,9 @@ ExecCopySlotMinimalTuple(TupleTableSlot *slot)
 	 */
 	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);
+
+	if (slot->tts_storage)
+		return slot->tts_storageam->slot_copy_min_tuple(slot);
 
 	/*
 	 * Otherwise we need to build a tuple from the Datum array.
@@ -614,7 +610,7 @@ ExecCopySlotMinimalTuple(TupleTableSlot *slot)
  * Hence, the result must be treated as read-only.
  * --------------------------------
  */
-HeapTuple
+StorageTuple
 ExecFetchSlotTuple(TupleTableSlot *slot)
 {
 	/*
@@ -624,15 +620,9 @@ ExecFetchSlotTuple(TupleTableSlot *slot)
 	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;
-
-	/*
 	 * Otherwise materialize the slot...
 	 */
-	return ExecMaterializeSlot(slot);
+	return ExecHeapifySlot(slot);
 }
 
 /* --------------------------------
@@ -697,7 +687,7 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 Datum
 ExecFetchSlotTupleDatum(TupleTableSlot *slot)
 {
-	HeapTuple	tup;
+	StorageTuple	tup;
 	TupleDesc	tupdesc;
 
 	/* Fetch slot's contents in regular-physical-tuple form */
@@ -713,18 +703,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;
+	StorageTuple tuple;
 
 	/*
 	 * sanity checks
@@ -732,12 +723,14 @@ 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_storage &&
+			slot->tts_storageam->slot_is_physical_tuple(slot))
+		return;
 
 	/*
 	 * Otherwise, copy or build a physical tuple, and store it into the slot.
@@ -747,8 +740,8 @@ ExecMaterializeSlot(TupleTableSlot *slot)
 	 * anyway.
 	 */
 	oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
-	slot->tts_tuple = ExecCopySlotTuple(slot);
-	slot->tts_shouldFree = true;
+	tuple = ExecCopySlotTuple(slot);
+	slot->tts_storageam->slot_store_tuple(slot, tuple, true);
 	MemoryContextSwitchTo(oldContext);
 
 	/*
@@ -777,8 +770,15 @@ ExecMaterializeSlot(TupleTableSlot *slot)
 	 */
 	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_storageam->slot_get_tuple(slot);
 }
 
 /* --------------------------------
@@ -794,7 +794,7 @@ ExecMaterializeSlot(TupleTableSlot *slot)
 TupleTableSlot *
 ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
 {
-	HeapTuple	newTuple;
+	StorageTuple	newTuple;
 	MemoryContext oldContext;
 
 	/*
@@ -1107,11 +1107,11 @@ TupleDescGetAttInMetadata(TupleDesc tupdesc)
 }
 
 /*
- * BuildTupleFromCStrings - build a HeapTuple given user data in C string form.
+ * BuildTupleFromCStrings - build a StorageTuple given user data in C string form.
  * 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;
@@ -1119,7 +1119,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));
@@ -1175,7 +1175,7 @@ BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)
  * for a composite Datum.  However, we now require that composite Datums not
  * contain any external TOAST pointers.  We do not want heap_form_tuple itself
  * to enforce that; more specifically, the rule applies only to actual Datums
- * and not to HeapTuple structures.  Therefore, HeapTupleHeaderGetDatum is
+ * and not to StorageTuple structures.  Therefore, HeapTupleHeaderGetDatum is
  * now a function that detects whether there are externally-toasted fields
  * and constructs a new tuple with inlined fields if so.  We still need
  * heap_form_tuple to insert the Datum header fields, because otherwise this
@@ -1183,7 +1183,7 @@ BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)
  *
  * Note that if we do build a new tuple, it's palloc'd in the current
  * memory context.  Beware of code that changes context between the initial
- * heap_form_tuple/etc call and calling HeapTuple(Header)GetDatum.
+ * heap_form_tuple/etc call and calling StorageTuple(Header)GetDatum.
  *
  * For performance-critical callers, it could be worthwhile to take extra
  * steps to ensure that there aren't TOAST pointers in the output of
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index e365f4f..a8ab945 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -21,6 +21,18 @@
 #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;
+	bool		hst_shouldFree;
+	long		hst_off;
+} 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
  * a tuple, plus the size of the null-values bitmap (at 1 bit per column),
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index 32489ef..7521739 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 StorageAmRoutine;
+
+/*
+ * 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
@@ -79,15 +96,6 @@
  * mechanism to do more.  However, the slot will increment the tupdesc
  * reference count if a reference-counted tupdesc is supplied.)
  *
- * 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
  * to the descriptor's natts.  When the slot is holding a physical tuple
@@ -114,24 +122,22 @@ 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 */
 	TupleDesc	tts_tupleDescriptor;	/* slot's tuple descriptor */
+	Oid			tts_tableOid;	/* XXX describe */
+	Oid			tts_tupleOid;	/* XXX describe */
+	Buffer		tts_buffer;
 	MemoryContext tts_mcxt;		/* slot itself is in this context */
-	Buffer		tts_buffer;		/* tuple's buffer, or InvalidBuffer */
 	int			tts_nvalid;		/* # of valid values in tts_values */
+	uint32		tts_speculativeToken;	/* XXX describe */
 	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 StorageAmRoutine *tts_storageam;	/* 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?
  */
@@ -144,8 +150,11 @@ extern TupleTableSlot *ExecAllocTableSlot(List **tupleTable);
 extern void ExecResetTupleTable(List *tupleTable, bool shouldFree);
 extern TupleTableSlot *MakeSingleTupleTableSlot(TupleDesc tupdesc);
 extern void ExecDropSingleTupleTableSlot(TupleTableSlot *slot);
-extern void ExecSetSlotDescriptor(TupleTableSlot *slot, TupleDesc tupdesc);
-extern TupleTableSlot *ExecStoreTuple(HeapTuple tuple,
+extern void ExecSetSlotDescriptor(TupleTableSlot *slot,
+								  TupleDesc tupdesc);
+extern void ExecSetSlotStorageRoutine(TupleTableSlot *slot,
+						  struct StorageAmRoutine *routine);
+extern TupleTableSlot *ExecStoreTuple(void *tuple,
 			   TupleTableSlot *slot,
 			   Buffer buffer,
 			   bool shouldFree);
@@ -161,6 +170,7 @@ extern HeapTuple ExecFetchSlotTuple(TupleTableSlot *slot);
 extern MinimalTuple ExecFetchSlotMinimalTuple(TupleTableSlot *slot);
 extern Datum ExecFetchSlotTupleDatum(TupleTableSlot *slot);
 extern HeapTuple ExecMaterializeSlot(TupleTableSlot *slot);
+extern StorageTuple ExecHeapifySlot(TupleTableSlot *slot);
 extern TupleTableSlot *ExecCopySlot(TupleTableSlot *dstslot,
 			 TupleTableSlot *srcslot);
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index f59d719..ac3375e 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -496,6 +496,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.7.4.windows.1

0006-HeapTuple-replace-with-StorageTuple.patchapplication/octet-stream; name=0006-HeapTuple-replace-with-StorageTuple.patchDownload
From 3c993d1f59e89a1b0e537b4bc02e62d482bfe028 Mon Sep 17 00:00:00 2001
From: Hari <haribabuk@fast.au.fujitsu.com>
Date: Fri, 9 Jun 2017 11:07:32 +1000
Subject: [PATCH 06/10] HeapTuple replace with StorageTuple

Replace most of the executor flow of HeapTuple
with StorageTuple and the necessary code around it.
---
 src/backend/commands/trigger.c            |  39 +++---
 src/backend/executor/execAmi.c            |   2 +-
 src/backend/executor/execExprInterp.c     |  16 +--
 src/backend/executor/execIndexing.c       |  10 +-
 src/backend/executor/execMain.c           | 113 ++++++++---------
 src/backend/executor/execReplication.c    |  50 ++++----
 src/backend/executor/functions.c          |   4 +-
 src/backend/executor/nodeAgg.c            |   4 +-
 src/backend/executor/nodeBitmapHeapscan.c |  18 ++-
 src/backend/executor/nodeForeignscan.c    |   7 +-
 src/backend/executor/nodeGather.c         |   8 +-
 src/backend/executor/nodeGatherMerge.c    |  12 +-
 src/backend/executor/nodeIndexonlyscan.c  |   4 +-
 src/backend/executor/nodeIndexscan.c      |  16 +--
 src/backend/executor/nodeLockRows.c       |  22 ++--
 src/backend/executor/nodeModifyTable.c    | 193 ++++++++++++++++--------------
 src/backend/executor/nodeSamplescan.c     |  27 +++--
 src/backend/executor/nodeSeqscan.c        |  13 +-
 src/backend/executor/nodeTidscan.c        |  16 +--
 src/backend/executor/nodeWindowAgg.c      |   4 +-
 src/backend/executor/spi.c                |  20 ++--
 src/backend/executor/tqueue.c             |  22 ++--
 src/include/access/htup_details.h         |   2 +-
 src/include/access/relscan.h              |   3 +-
 src/include/commands/trigger.h            |   2 +-
 src/include/executor/executor.h           |   7 +-
 src/include/executor/functions.h          |   2 +-
 src/include/executor/spi.h                |  10 +-
 src/include/executor/tqueue.h             |   2 +-
 src/include/executor/tuptable.h           |   6 +-
 src/include/funcapi.h                     |   2 +-
 src/include/nodes/execnodes.h             |   4 +-
 32 files changed, 337 insertions(+), 323 deletions(-)

diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 0271788..8fa42d5 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -17,6 +17,7 @@
 #include "access/heapam.h"
 #include "access/sysattr.h"
 #include "access/htup_details.h"
+#include "access/storageamapi.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
@@ -2200,7 +2201,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;
@@ -2263,14 +2264,18 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 
 void
 ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
-					 HeapTuple trigtuple, List *recheckIndexes)
+					 TupleTableSlot *slot, List *recheckIndexes)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
 	if (trigdesc &&
 		(trigdesc->trig_insert_after_row || trigdesc->trig_insert_new_table))
+	{
+		HeapTuple	trigtuple = ExecHeapifySlot(slot);
+
 		AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT,
 							  true, NULL, trigtuple, recheckIndexes, NULL);
+	}
 }
 
 TupleTableSlot *
@@ -2278,7 +2283,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;
@@ -2621,7 +2626,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;
@@ -2663,7 +2668,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 	if (newSlot != NULL)
 	{
 		slot = ExecFilterJunk(relinfo->ri_junkFilter, newSlot);
-		slottuple = ExecMaterializeSlot(slot);
+		slottuple = ExecHeapifySlot(slot);
 		newtuple = slottuple;
 	}
 
@@ -2768,7 +2773,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;
@@ -2920,7 +2925,8 @@ GetTupleForTrigger(EState *estate,
 		 */
 ltrmark:;
 		tuple.t_self = *tid;
-		test = heap_lock_tuple(relation, &tuple,
+		test = relation->rd_stamroutine->tuple_lock(relation, tid,
+							   &tuple,
 							   estate->es_output_cid,
 							   lockmode, LockWaitBlock,
 							   false, &buffer, &hufd);
@@ -3742,8 +3748,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;
@@ -3804,14 +3810,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;
@@ -3819,10 +3824,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 (!rel->rd_stamroutine->tuple_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
@@ -3836,10 +3840,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 (!rel->rd_stamroutine->tuple_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/execAmi.c b/src/backend/executor/execAmi.c
index 7337d21..953284c 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -539,7 +539,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/execExprInterp.c b/src/backend/executor/execExprInterp.c
index fed0052..1217304 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2057,7 +2057,7 @@ ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op,
 								 econtext);
 
 	/*
-	 * heap_attisnull needs a HeapTuple not a bare HeapTupleHeader.
+	 * heap_attisnull needs a StorageTuple not a bare HeapTupleHeader.
 	 */
 	tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
 	tmptup.t_data = tuple;
@@ -2368,7 +2368,7 @@ ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op)
 void
 ExecEvalRow(ExprState *state, ExprEvalStep *op)
 {
-	HeapTuple	tuple;
+	HeapTuple	tuple; //hari
 
 	/* build tuple from evaluated field values */
 	tuple = heap_form_tuple(op->d.row.tupdesc,
@@ -2497,7 +2497,7 @@ ExecEvalFieldSelect(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 						   format_type_be(attr->atttypid),
 						   format_type_be(op->d.fieldselect.resulttype))));
 
-	/* heap_getattr needs a HeapTuple not a bare HeapTupleHeader */
+	/* heap_getattr needs a StorageTuple not a bare HeapTupleHeader */
 	tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
 	tmptup.t_data = tuple;
 
@@ -2540,7 +2540,7 @@ ExecEvalFieldStoreDeForm(ExprState *state, ExprEvalStep *op, ExprContext *econte
 	else
 	{
 		/*
-		 * heap_deform_tuple needs a HeapTuple not a bare HeapTupleHeader. We
+		 * heap_deform_tuple needs a StorageTuple not a bare HeapTupleHeader. We
 		 * set all the fields in the struct just in case.
 		 */
 		Datum		tupDatum = *op->resvalue;
@@ -2566,7 +2566,7 @@ ExecEvalFieldStoreDeForm(ExprState *state, ExprEvalStep *op, ExprContext *econte
 void
 ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 {
-	HeapTuple	tuple;
+	HeapTuple	tuple; //hari
 
 	/* argdesc should already be valid from the DeForm step */
 	tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
@@ -2781,7 +2781,7 @@ void
 ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 {
 	ConvertRowtypeExpr *convert = op->d.convert_rowtype.convert;
-	HeapTuple	result;
+	HeapTuple	result; //hari
 	Datum		tupDatum;
 	HeapTupleHeader tuple;
 	HeapTupleData tmptup;
@@ -2840,7 +2840,7 @@ ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op, ExprContext *econtext
 		MemoryContextSwitchTo(old_cxt);
 	}
 
-	/* Following steps need a HeapTuple not a bare HeapTupleHeader */
+	/* Following steps need a StorageTuple not a bare HeapTupleHeader */
 	tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
 	tmptup.t_data = tuple;
 
@@ -3345,7 +3345,7 @@ ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 	TupleDesc	output_tupdesc;
 	MemoryContext oldcontext;
 	HeapTupleHeader dtuple;
-	HeapTuple	tuple;
+	HeapTuple	tuple; //hari
 
 	/* This was checked by ExecInitExpr */
 	Assert(variable->varattno == InvalidAttrNumber);
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 108060a..fe4c873 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;
@@ -659,6 +659,7 @@ check_exclusion_or_unique_constraint(Relation heap, Relation index,
 	ExprContext *econtext;
 	TupleTableSlot *existing_slot;
 	TupleTableSlot *save_scantuple;
+	StorageAmRoutine *method;
 
 	if (indexInfo->ii_ExclusionOps)
 	{
@@ -711,6 +712,7 @@ check_exclusion_or_unique_constraint(Relation heap, Relation index,
 	econtext = GetPerTupleExprContext(estate);
 	save_scantuple = econtext->ecxt_scantuple;
 	econtext->ecxt_scantuple = existing_slot;
+	method = heap->rd_stamroutine;
 
 	/*
 	 * May have to restart scan from this point if a potential conflict is
@@ -737,7 +739,7 @@ retry:
 		 * Ignore the entry for the tuple we're trying to check.
 		 */
 		if (ItemPointerIsValid(tupleid) &&
-			ItemPointerEquals(tupleid, &tup->t_self))
+			ItemPointerEquals(tupleid, method->tuple_get_itempointer(tup)))
 		{
 			if (found_self)		/* should not happen */
 				elog(ERROR, "found self tuple multiple times in index \"%s\"",
@@ -785,7 +787,7 @@ retry:
 			  DirtySnapshot.speculativeToken &&
 			  TransactionIdPrecedes(GetCurrentTransactionId(), xwait))))
 		{
-			ctid_wait = tup->t_data->t_ctid;
+			ctid_wait = method->tuple_get_ctid(tup);
 			reason_wait = indexInfo->ii_ExclusionOps ?
 				XLTW_RecheckExclusionConstr : XLTW_InsertIndex;
 			index_endscan(index_scan);
@@ -805,7 +807,7 @@ retry:
 		{
 			conflict = true;
 			if (conflictTid)
-				*conflictTid = tup->t_self;
+				*conflictTid = *(method->tuple_get_itempointer(tup));
 			break;
 		}
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4a899f1..3614513 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1918,7 +1918,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				 */
 				if (resultRelInfo->ri_PartitionRoot)
 				{
-					HeapTuple	tuple = ExecFetchSlotTuple(slot);
+					StorageTuple	tuple = ExecFetchSlotTuple(slot);
 					TupleConversionMap *map;
 
 					rel = resultRelInfo->ri_PartitionRoot;
@@ -1964,7 +1964,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;
 
@@ -2006,7 +2006,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;
 
@@ -2437,7 +2437,8 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
 			 ItemPointer tid, TransactionId priorXmax)
 {
 	TupleTableSlot *slot;
-	HeapTuple	copyTuple;
+	StorageTuple copyTuple;
+	StorageAmRoutine *method;
 
 	Assert(rti > 0);
 
@@ -2450,11 +2451,14 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
 	if (copyTuple == NULL)
 		return NULL;
 
+	method = relation->rd_stamroutine;
+
 	/*
 	 * For UPDATE/DELETE we have to return tid of actual row we're executing
 	 * PQ for.
 	 */
-	*tid = copyTuple->t_self;
+
+	*tid = *(method->tuple_get_itempointer(copyTuple));
 
 	/*
 	 * Need to run a recheck subquery.  Initialize or reinitialize EPQ state.
@@ -2485,7 +2489,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
@@ -2518,13 +2522,13 @@ 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;
+	StorageAmRoutine *method = relation->rd_stamroutine;
+	StorageTuple tuple = NULL;
 	SnapshotData SnapshotDirty;
 
 	/*
@@ -2533,12 +2537,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 (method->tuple_fetch(relation, tid, &SnapshotDirty, &tuple, &buffer, true, NULL))
 		{
 			HTSU_Result test;
 			HeapUpdateFailureData hufd;
@@ -2552,8 +2556,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),
-									 priorXmax))
+			if (!TransactionIdEquals(method->tuple_get_xmin(tuple), priorXmax))
 			{
 				ReleaseBuffer(buffer);
 				return NULL;
@@ -2574,7 +2577,8 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 				{
 					case LockWaitBlock:
 						XactLockTableWait(SnapshotDirty.xmax,
-										  relation, &tuple.t_self,
+										  relation,
+										  tid,
 										  XLTW_FetchUpdated);
 						break;
 					case LockWaitSkip:
@@ -2604,7 +2608,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 			 * doing HeapTupleHeaderGetXmin again.
 			 */
 			if (TransactionIdIsCurrentTransactionId(priorXmax) &&
-				HeapTupleHeaderGetCmin(tuple.t_data) >= estate->es_output_cid)
+				method->tuple_get_cmin(tuple) >= estate->es_output_cid)
 			{
 				ReleaseBuffer(buffer);
 				return NULL;
@@ -2613,10 +2617,10 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 			/*
 			 * 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 = method->tuple_lock(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);
 
@@ -2652,12 +2656,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
+					if (!ItemPointerEquals(&hufd.ctid, method->tuple_get_itempointer(tuple)))
 					{
 						/* 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;
@@ -2679,10 +2686,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;
 		}
@@ -2691,7 +2694,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;
@@ -2700,7 +2703,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(method->tuple_get_xmin(tuple),
 								 priorXmax))
 		{
 			ReleaseBuffer(buffer);
@@ -2719,7 +2722,8 @@ 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))
+		ctid = method->tuple_get_ctid(tuple);
+		if (ItemPointerEquals(tid, &ctid))
 		{
 			/* deleted, so forget about it */
 			ReleaseBuffer(buffer);
@@ -2727,17 +2731,18 @@ 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);
+		priorXmax = method->tuple_get_updated_xid(tuple);
 		ReleaseBuffer(buffer);
 		/* loop back to fetch next in chain */
 	}
 
 	/*
-	 * Return the copied tuple
+	 * Return the tuple
 	 */
-	return copyTuple;
+	return tuple;
 }
 
 /*
@@ -2783,7 +2788,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;
 
@@ -2802,7 +2807,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;
@@ -2830,7 +2835,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");
@@ -2861,8 +2866,6 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 
 		if (erm->markType == ROW_MARK_REFERENCE)
 		{
-			HeapTuple	copyTuple;
-
 			Assert(erm->relation != NULL);
 
 			/* fetch the tuple's ctid */
@@ -2886,11 +2889,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,
+				tuple = fdwroutine->RefetchForeignRow(epqstate->estate,
 														  erm,
 														  datum,
 														  &updated);
-				if (copyTuple == NULL)
+				if (tuple == NULL)
 					elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck");
 
 				/*
@@ -2904,23 +2907,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,
+				if (!erm->relation->rd_stamroutine->tuple_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 */
@@ -2930,19 +2928,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 = erm->relation->rd_stamroutine->tuple_from_datum(datum, erm->relid);
+
+			/* store tuple */
+			EvalPlanQualSetTuple(epqstate, erm->rti, tuple);
+
 		}
 	}
 }
@@ -3108,8 +3099,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 c6a66b6..206238a 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -116,7 +116,7 @@ RelationFindReplTupleByIndex(Relation rel, Oid idxoid,
 							 TupleTableSlot *searchslot,
 							 TupleTableSlot *outslot)
 {
-	HeapTuple	scantuple;
+	StorageTuple	scantuple;
 	ScanKeyData skey[INDEX_MAX_KEYS];
 	IndexScanDesc scan;
 	SnapshotData snap;
@@ -169,12 +169,14 @@ retry:
 		HeapUpdateFailureData hufd;
 		HTSU_Result res;
 		HeapTupleData locktup;
+		ItemPointer tid;
 
-		ItemPointerCopy(&outslot->tts_tuple->t_self, &locktup.t_self);
+		tid = outslot->tts_storageam->tuple_get_itempointer(outslot->tts_storage);
+		ItemPointerCopy(tid, &locktup.t_self);
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
-		res = heap_lock_tuple(rel, &locktup, GetCurrentCommandId(false),
+		res = rel->rd_stamroutine->tuple_lock(rel, tid, &locktup, GetCurrentCommandId(false),
 							  lockmode,
 							  LockWaitBlock,
 							  false /* don't follow updates */ ,
@@ -219,7 +221,7 @@ retry:
  * to use.
  */
 static bool
-tuple_equals_slot(TupleDesc desc, HeapTuple tup, TupleTableSlot *slot)
+tuple_equals_slot(TupleDesc desc, StorageTuple tup, TupleTableSlot *slot)
 {
 	Datum		values[MaxTupleAttributeNumber];
 	bool		isnull[MaxTupleAttributeNumber];
@@ -267,7 +269,7 @@ bool
 RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 						 TupleTableSlot *searchslot, TupleTableSlot *outslot)
 {
-	HeapTuple	scantuple;
+    StorageTuple            scantuple;
 	HeapScanDesc scan;
 	SnapshotData snap;
 	TransactionId xwait;
@@ -316,12 +318,14 @@ retry:
 		HeapUpdateFailureData hufd;
 		HTSU_Result res;
 		HeapTupleData locktup;
+		ItemPointer tid;
 
-		ItemPointerCopy(&outslot->tts_tuple->t_self, &locktup.t_self);
+		tid = outslot->tts_storageam->tuple_get_itempointer(outslot->tts_storage);
+		ItemPointerCopy(tid, &locktup.t_self);
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
-		res = heap_lock_tuple(rel, &locktup, GetCurrentCommandId(false),
+		res = rel->rd_stamroutine->tuple_lock(rel, tid, &locktup, GetCurrentCommandId(false),
 							  lockmode,
 							  LockWaitBlock,
 							  false /* don't follow updates */ ,
@@ -364,7 +368,7 @@ void
 ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 {
 	bool		skip_tuple = false;
-	HeapTuple	tuple;
+	StorageTuple	tuple;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 
@@ -392,18 +396,18 @@ 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);
 
 		if (resultRelInfo->ri_NumIndices > 0)
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+			recheckIndexes = ExecInsertIndexTuples(slot, rel->rd_stamroutine->tuple_get_itempointer(tuple),
 												   estate, false, NULL,
 												   NIL);
 
 		/* AFTER ROW INSERT Triggers */
-		ExecARInsertTriggers(estate, resultRelInfo, tuple,
+		ExecARInsertTriggers(estate, resultRelInfo, slot,
 							 recheckIndexes);
 
 		list_free(recheckIndexes);
@@ -421,9 +425,10 @@ 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_storageam->tuple_get_itempointer(searchslot->tts_storage);
 
 	/* For now we support only tables. */
 	Assert(rel->rd_rel->relkind == RELKIND_RELATION);
@@ -435,7 +440,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" */
@@ -451,21 +456,19 @@ 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),
+		if ((resultRelInfo->ri_NumIndices > 0) && rel->rd_stamroutine->tuple_is_heaponly(tuple))
+			recheckIndexes = ExecInsertIndexTuples(slot, rel->rd_stamroutine->tuple_get_itempointer(tuple),
 												   estate, false, NULL,
 												   NIL);
 
 		/* AFTER ROW UPDATE Triggers */
 		ExecARUpdateTriggers(estate, resultRelInfo,
-							 &searchslot->tts_tuple->t_self,
+							 tid,
 							 NULL, tuple, recheckIndexes);
 
 		list_free(recheckIndexes);
@@ -485,6 +488,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_storageam->tuple_get_itempointer(searchslot->tts_storage);
 
 	/* For now we support only tables. */
 	Assert(rel->rd_rel->relkind == RELKIND_RELATION);
@@ -496,7 +500,7 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
 		resultRelInfo->ri_TrigDesc->trig_update_before_row)
 	{
 		skip_tuple = !ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
-										   &searchslot->tts_tuple->t_self,
+											tid,
 										   NULL);
 	}
 
@@ -505,11 +509,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);
+				tid, NULL);
 
 		list_free(recheckIndexes);
 	}
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index a35ba32..e2ab776 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)
 {
@@ -596,7 +596,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 7eeda95..a412402 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -3084,7 +3084,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,
@@ -3199,7 +3199,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 c453362..f8a4e7d 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -215,6 +215,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			 * least AccessShareLock on the table before performing any of the
 			 * indexscans, but let's be safe.)
 			 */
+			//hari --> need a fix to change the scan
 			if (tbmres->blockno >= scan->rs_nblocks)
 			{
 				node->tbmres = tbmres = NULL;
@@ -234,6 +235,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			/*
 			 * Set rs_cindex to first slot to examine
 			 */
+			//hari
 			scan->rs_cindex = 0;
 
 			/* Adjust the prefetch target */
@@ -244,6 +246,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			/*
 			 * Continuing in previously obtained page; advance rs_cindex
 			 */
+			//hari
 			scan->rs_cindex++;
 
 #ifdef USE_PREFETCH
@@ -271,6 +274,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 		/*
 		 * Out of range?  If so, nothing more to look at on this page
 		 */
+		//hari
 		if (scan->rs_cindex < 0 || scan->rs_cindex >= scan->rs_ntuples)
 		{
 			node->tbmres = tbmres = NULL;
@@ -289,6 +293,8 @@ BitmapHeapNext(BitmapHeapScanState *node)
 		/*
 		 * Okay to fetch the tuple
 		 */
+		//hari
+#if 1
 		targoffset = scan->rs_vistuples[scan->rs_cindex];
 		dp = (Page) BufferGetPage(scan->rs_cbuf);
 		lp = PageGetItemId(dp, targoffset);
@@ -309,7 +315,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 					   slot,
 					   scan->rs_cbuf,
 					   false);
-
+#endif
 		/*
 		 * If we are using lossy info, we have to recheck the qual conditions
 		 * at every tuple.
@@ -352,7 +358,8 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 	Buffer		buffer;
 	Snapshot	snapshot;
 	int			ntup;
-
+	//hari
+#if 1
 	/*
 	 * Acquire pin on the target heap page, trading in any pin we held before.
 	 */
@@ -394,11 +401,11 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 		{
 			OffsetNumber offnum = tbmres->offsets[curslot];
 			ItemPointerData tid;
-			HeapTupleData heapTuple;
+			HeapTupleData StorageTuple;
 
 			ItemPointerSet(&tid, page, offnum);
 			if (heap_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
-									   &heapTuple, NULL, true))
+									   &StorageTuple, NULL, true))
 				scan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
 		}
 	}
@@ -440,6 +447,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 
 	Assert(ntup <= MaxHeapTuplesPerPage);
 	scan->rs_ntuples = ntup;
+#endif
 }
 
 /*
@@ -588,6 +596,7 @@ BitmapPrefetch(BitmapHeapScanState *node, HeapScanDesc scan)
 					break;
 				}
 				node->prefetch_pages++;
+				//hari
 				PrefetchBuffer(scan->rs_rd, MAIN_FORKNUM, tbmpre->blockno);
 			}
 		}
@@ -630,6 +639,7 @@ BitmapPrefetch(BitmapHeapScanState *node, HeapScanDesc scan)
 					break;
 				}
 
+				//hari
 				PrefetchBuffer(scan->rs_rd, MAIN_FORKNUM, tbmpre->blockno);
 			}
 		}
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 9ae1561..424acac 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -61,11 +61,8 @@ ForeignNext(ForeignScanState *node)
 	 * tableoid, which is the only actually-useful system column.
 	 */
 	if (plan->fsSystemCol && !TupIsNull(slot))
-	{
-		HeapTuple	tup = ExecMaterializeSlot(slot);
-
-		tup->t_tableOid = RelationGetRelid(node->ss.ss_currentRelation);
-	}
+		slot->tts_storageam->slot_update_tableoid(slot,
+							RelationGetRelid(node->ss.ss_currentRelation));
 
 	return slot;
 }
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index c1db2e2..cae003b 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -44,7 +44,7 @@
 
 
 static TupleTableSlot *gather_getnext(GatherState *gatherstate);
-static HeapTuple gather_readnext(GatherState *gatherstate);
+static StorageTuple gather_readnext(GatherState *gatherstate);
 static void ExecShutdownGatherWorkers(GatherState *node);
 
 
@@ -243,7 +243,7 @@ gather_getnext(GatherState *gatherstate)
 	TupleTableSlot *outerTupleSlot;
 	TupleTableSlot *fslot = gatherstate->funnel_slot;
 	MemoryContext tupleContext = gatherstate->ps.ps_ExprContext->ecxt_per_tuple_memory;
-	HeapTuple	tup;
+	StorageTuple	tup;
 
 	while (gatherstate->reader != NULL || gatherstate->need_to_scan_locally)
 	{
@@ -284,7 +284,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;
@@ -292,7 +292,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 e066574..1f98939 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -31,7 +31,7 @@
  */
 typedef struct GMReaderTupleBuffer
 {
-	HeapTuple  *tuple;
+	StorageTuple  *tuple;
 	int			readCounter;
 	int			nTuples;
 	bool		done;
@@ -46,7 +46,7 @@ typedef struct GMReaderTupleBuffer
 
 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,
+static StorageTuple gm_readnext_tuple(GatherMergeState *gm_state, int nreader,
 				  bool nowait, bool *done);
 static void gather_merge_init(GatherMergeState *gm_state);
 static void ExecShutdownGatherMergeWorkers(GatherMergeState *node);
@@ -369,7 +369,7 @@ gather_merge_init(GatherMergeState *gm_state)
 	{
 		/* Allocate the tuple array with MAX_TUPLE_STORE size */
 		gm_state->gm_tuple_buffers[i].tuple =
-			(HeapTuple *) palloc0(sizeof(HeapTuple) * MAX_TUPLE_STORE);
+			(StorageTuple *) palloc0(sizeof(StorageTuple) * MAX_TUPLE_STORE);
 
 		/* Initialize slot for worker */
 		gm_state->gm_slots[i] = ExecInitExtraTupleSlot(gm_state->ps.state);
@@ -531,7 +531,7 @@ static bool
 gather_merge_readnext(GatherMergeState *gm_state, int reader, bool nowait)
 {
 	GMReaderTupleBuffer *tuple_buffer;
-	HeapTuple	tup = NULL;
+	StorageTuple	tup = NULL;
 
 	/*
 	 * If we're being asked to generate a tuple from the leader, then we just
@@ -606,12 +606,12 @@ gather_merge_readnext(GatherMergeState *gm_state, int reader, bool nowait)
 /*
  * Attempt to read a tuple from given reader.
  */
-static HeapTuple
+static StorageTuple
 gm_readnext_tuple(GatherMergeState *gm_state, int nreader, bool nowait,
 				  bool *done)
 {
 	TupleQueueReader *reader;
-	HeapTuple	tup = NULL;
+	StorageTuple	tup = NULL;
 	MemoryContext oldContext;
 	MemoryContext tupleContext;
 
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index 5550f6c..a5052de 100644
--- a/src/backend/executor/nodeIndexonlyscan.c
+++ b/src/backend/executor/nodeIndexonlyscan.c
@@ -115,7 +115,7 @@ IndexOnlyNext(IndexOnlyScanState *node)
 	 */
 	while ((tid = index_getnext_tid(scandesc, direction)) != NULL)
 	{
-		HeapTuple	tuple = NULL;
+		StorageTuple	tuple = NULL;
 
 		/*
 		 * We can skip the heap fetch if the TID references a heap page on
@@ -182,7 +182,7 @@ 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
+		 * 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.
 		 */
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 5afd02e..b936c13 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -49,7 +49,7 @@
 typedef struct
 {
 	pairingheap_node ph_node;
-	HeapTuple	htup;
+	StorageTuple	htup;
 	Datum	   *orderbyvals;
 	bool	   *orderbynulls;
 } ReorderTuple;
@@ -63,9 +63,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);
 
 
 /* ----------------------------------------------------------------
@@ -82,7 +82,7 @@ IndexNext(IndexScanState *node)
 	ExprContext *econtext;
 	ScanDirection direction;
 	IndexScanDesc scandesc;
-	HeapTuple	tuple;
+	StorageTuple	tuple;
 	TupleTableSlot *slot;
 
 	/*
@@ -181,7 +181,7 @@ IndexNextWithReorder(IndexScanState *node)
 	EState	   *estate;
 	ExprContext *econtext;
 	IndexScanDesc scandesc;
-	HeapTuple	tuple;
+	StorageTuple	tuple;
 	TupleTableSlot *slot;
 	ReorderTuple *topmost = NULL;
 	bool		was_exact;
@@ -475,7 +475,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;
@@ -508,10 +508,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/nodeLockRows.c b/src/backend/executor/nodeLockRows.c
index 5630eae..d628a40 100644
--- a/src/backend/executor/nodeLockRows.c
+++ b/src/backend/executor/nodeLockRows.c
@@ -70,7 +70,7 @@ lnext:
 	{
 		ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
 		ExecRowMark *erm = aerm->rowmark;
-		HeapTuple  *testTuple;
+		StorageTuple  *testTuple;
 		Datum		datum;
 		bool		isNull;
 		HeapTupleData tuple;
@@ -78,10 +78,10 @@ lnext:
 		HeapUpdateFailureData hufd;
 		LockTupleMode lockmode;
 		HTSU_Result test;
-		HeapTuple	copyTuple;
+		StorageTuple	copyTuple;
 
 		/* 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;
@@ -176,7 +176,7 @@ lnext:
 				break;
 		}
 
-		test = heap_lock_tuple(erm->relation, &tuple,
+		test = erm->relation->rd_stamroutine->tuple_lock(erm->relation, &tuple.t_self, &tuple,
 							   estate->es_output_cid,
 							   lockmode, erm->waitPolicy, true,
 							   &buffer, &hufd);
@@ -234,7 +234,7 @@ lnext:
 					goto lnext;
 				}
 				/* remember the actually locked tuple's TID */
-				tuple.t_self = copyTuple->t_self;
+				tuple.t_self = *(erm->relation->rd_stamroutine->tuple_get_itempointer(copyTuple));
 
 				/* Save locked tuple for EvalPlanQual testing below */
 				*testTuple = copyTuple;
@@ -276,7 +276,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 */
@@ -304,14 +304,12 @@ lnext:
 			Assert(ItemPointerIsValid(&(erm->curCtid)));
 
 			/* okay, fetch the tuple */
-			tuple.t_self = erm->curCtid;
-			if (!heap_fetch(erm->relation, SnapshotAny, &tuple, &buffer,
+			if (!erm->relation->rd_stamroutine->tuple_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);
 		}
 
@@ -389,8 +387,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 cf555fe..58827e0 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -38,6 +38,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/storageamapi.h"
 #include "access/xact.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
@@ -163,15 +164,14 @@ 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 = ExecMaterializeSlot(econtext->ecxt_scantuple);
-		tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+		econtext->ecxt_scantuple->tts_storageam->slot_update_tableoid(
+								econtext->ecxt_scantuple,
+								RelationGetRelid(resultRelInfo->ri_RelationDesc));
 	}
 	econtext->ecxt_outertuple = planSlot;
 
@@ -189,7 +189,8 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
  */
 static void
 ExecCheckHeapTupleVisible(EState *estate,
-						  HeapTuple tuple,
+						  Relation rel,
+						  StorageTuple tuple,
 						  Buffer buffer)
 {
 	if (!IsolationUsesXactSnapshot())
@@ -208,7 +209,7 @@ ExecCheckHeapTupleVisible(EState *estate,
 		 * 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(rel->rd_stamroutine->tuple_get_xmin(tuple)))
 			ereport(ERROR,
 					(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 			 errmsg("could not serialize access due to concurrent update")));
@@ -226,16 +227,15 @@ ExecCheckTIDVisible(EState *estate,
 {
 	Relation	rel = relinfo->ri_RelationDesc;
 	Buffer		buffer;
-	HeapTupleData tuple;
+	StorageTuple tuple;
 
 	/* Redundantly check isolation level */
 	if (!IsolationUsesXactSnapshot())
 		return;
 
-	tuple.t_self = *tid;
-	if (!heap_fetch(rel, SnapshotAny, &tuple, &buffer, false, NULL))
+	if (!rel->rd_stamroutine->tuple_fetch(rel, tid, 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);
 }
 
@@ -257,21 +257,16 @@ ExecInsert(ModifyTableState *mtstate,
 		   EState *estate,
 		   bool canSetTag)
 {
-	HeapTuple	tuple;
+	StorageTuple	tuple;
 	ResultRelInfo *resultRelInfo;
 	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
+    StorageAmRoutine *method;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
 	TupleTableSlot *result = NULL;
 
 	/*
-	 * get the heap tuple out of the tuple table slot, making sure we have a
-	 * writable copy
-	 */
-	tuple = ExecMaterializeSlot(slot);
-
-	/*
 	 * get information on the (current) result relation
 	 */
 	resultRelInfo = estate->es_result_relation_info;
@@ -282,6 +277,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.
@@ -337,21 +334,35 @@ ExecInsert(ModifyTableState *mtstate,
 	}
 
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
+	method = resultRelationDesc->rd_stamroutine;
 
 	/*
-	 * 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;
+		slot->tts_storageam->slot_set_tuple_oid(slot, InvalidOid);
+	}
 
 	/*
 	 * BEFORE ROW INSERT Triggers.
@@ -369,9 +380,6 @@ ExecInsert(ModifyTableState *mtstate,
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
-
-		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -383,9 +391,6 @@ ExecInsert(ModifyTableState *mtstate,
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
 
-		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
-
 		newId = InvalidOid;
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
@@ -401,14 +406,12 @@ ExecInsert(ModifyTableState *mtstate,
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
 
-		/* FDW might have changed tuple */
-		tuple = ExecMaterializeSlot(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);
+		slot->tts_storageam->slot_update_tableoid(slot, slot->tts_tableOid);
 
 		newId = InvalidOid;
 	}
@@ -418,7 +421,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);
+		slot->tts_storageam->slot_update_tableoid(slot, slot->tts_tableOid);
 
 		/*
 		 * Check any RLS INSERT WITH CHECK policies
@@ -440,6 +444,7 @@ ExecInsert(ModifyTableState *mtstate,
 		{
 			/* Perform a speculative insertion. */
 			uint32		specToken;
+			ItemPointerData newtid;
 			ItemPointerData conflictTid;
 			bool		specConflict;
 
@@ -502,24 +507,24 @@ ExecInsert(ModifyTableState *mtstate,
 			 * waiting for the whole transaction to complete.
 			 */
 			specToken = SpeculativeInsertionLockAcquire(GetCurrentTransactionId());
-			HeapTupleHeaderSetSpeculativeToken(tuple->t_data, specToken);
+			method->tuple_set_speculative_token(slot, specToken);
 
 			/* insert the tuple, with the speculative token */
-			newId = heap_insert(resultRelationDesc, tuple,
-								estate->es_output_cid,
-								HEAP_INSERT_SPECULATIVE,
-								NULL);
+			newId = method->tuple_insert(resultRelationDesc, slot,
+										 estate->es_output_cid,
+										 HEAP_INSERT_SPECULATIVE,
+										 NULL, &newtid);
 
 			/* insert index entries for tuple */
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+			recheckIndexes = ExecInsertIndexTuples(slot, &newtid,
 												 estate, true, &specConflict,
 												   arbiterIndexes);
 
 			/* adjust the tuple's state accordingly */
 			if (!specConflict)
-				heap_finish_speculative(resultRelationDesc, tuple);
+				method->speculative_finish(resultRelationDesc, slot);
 			else
-				heap_abort_speculative(resultRelationDesc, tuple);
+				method->speculative_abort(resultRelationDesc, slot);
 
 			/*
 			 * Wake up anyone waiting for our decision.  They will re-check
@@ -545,19 +550,18 @@ ExecInsert(ModifyTableState *mtstate,
 		}
 		else
 		{
+			ItemPointerData		iptr;
+
 			/*
 			 * 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);
+			newId = method->tuple_insert(resultRelationDesc, slot,
+										 estate->es_output_cid,
+										 0, NULL, &iptr);
 
 			/* insert index entries for tuple */
 			if (resultRelInfo->ri_NumIndices > 0)
-				recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+				recheckIndexes = ExecInsertIndexTuples(slot, &iptr,
 													   estate, false, NULL,
 													   arbiterIndexes);
 		}
@@ -567,11 +571,11 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		(estate->es_processed)++;
 		estate->es_lastoid = newId;
-		setLastTid(&(tuple->t_self));
+		setLastTid(method->tuple_get_itempointer(tuple));
 	}
 
 	/* AFTER ROW INSERT Triggers */
-	ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes);
+	ExecARInsertTriggers(estate, resultRelInfo, slot, recheckIndexes);
 
 	list_free(recheckIndexes);
 
@@ -620,7 +624,7 @@ ExecInsert(ModifyTableState *mtstate,
  */
 static TupleTableSlot *
 ExecDelete(ItemPointer tupleid,
-		   HeapTuple oldtuple,
+		   StorageTuple oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate,
@@ -631,12 +635,14 @@ ExecDelete(ItemPointer tupleid,
 	HTSU_Result result;
 	HeapUpdateFailureData hufd;
 	TupleTableSlot *slot = NULL;
+	StorageAmRoutine *method;
 
 	/*
 	 * get information on the (current) result relation
 	 */
 	resultRelInfo = estate->es_result_relation_info;
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
+	method = resultRelationDesc->rd_stamroutine;
 
 	/* BEFORE ROW DELETE Triggers */
 	if (resultRelInfo->ri_TrigDesc &&
@@ -665,8 +671,6 @@ ExecDelete(ItemPointer tupleid,
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
-		HeapTuple	tuple;
-
 		/*
 		 * delete from foreign table: let the FDW do it
 		 *
@@ -676,7 +680,8 @@ ExecDelete(ItemPointer tupleid,
 		 */
 		slot = estate->es_trig_tuple_slot;
 		if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
-			ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
+			ExecSetSlotDescriptor(slot,
+								  RelationGetDescr(resultRelationDesc));
 
 		slot = resultRelInfo->ri_FdwRoutine->ExecForeignDelete(estate,
 															   resultRelInfo,
@@ -692,8 +697,10 @@ ExecDelete(ItemPointer tupleid,
 		 */
 		if (slot->tts_isempty)
 			ExecStoreAllNullTuple(slot);
-		tuple = ExecMaterializeSlot(slot);
-		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
+
+		ExecMaterializeSlot(slot);
+
+		slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
 	}
 	else
 	{
@@ -707,11 +714,11 @@ ExecDelete(ItemPointer tupleid,
 		 * mode transactions.
 		 */
 ldelete:;
-		result = heap_delete(resultRelationDesc, tupleid,
-							 estate->es_output_cid,
-							 estate->es_crosscheck_snapshot,
-							 true /* wait for commit */ ,
-							 &hufd);
+		result = method->tuple_delete(resultRelationDesc, tupleid,
+									  estate->es_output_cid,
+									  estate->es_crosscheck_snapshot,
+									  true /* wait for commit */ ,
+									  &hufd);
 		switch (result)
 		{
 			case HeapTupleSelfUpdated:
@@ -806,7 +813,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)
@@ -820,20 +827,20 @@ 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 (!method->tuple_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);
+				ExecSetSlotDescriptor(slot,
+									  RelationGetDescr(resultRelationDesc));
+			ExecStoreTuple(deltuple, slot, InvalidBuffer, false);
 		}
 
 		rslot = ExecProcessReturning(resultRelInfo, slot, planSlot);
@@ -878,16 +885,17 @@ ldelete:;
  */
 static TupleTableSlot *
 ExecUpdate(ItemPointer tupleid,
-		   HeapTuple oldtuple,
+		   StorageTuple oldtuple,
 		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate,
 		   bool canSetTag)
 {
-	HeapTuple	tuple;
+	StorageTuple	tuple;
 	ResultRelInfo *resultRelInfo;
 	Relation	resultRelationDesc;
+	StorageAmRoutine *method;
 	HTSU_Result result;
 	HeapUpdateFailureData hufd;
 	List	   *recheckIndexes = NIL;
@@ -902,13 +910,14 @@ ExecUpdate(ItemPointer tupleid,
 	 * 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
 	 */
 	resultRelInfo = estate->es_result_relation_info;
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
+	method = resultRelationDesc->rd_stamroutine;
 
 	/* BEFORE ROW UPDATE Triggers */
 	if (resultRelInfo->ri_TrigDesc &&
@@ -921,7 +930,7 @@ ExecUpdate(ItemPointer tupleid,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW UPDATE Triggers */
@@ -935,7 +944,7 @@ ExecUpdate(ItemPointer tupleid,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
@@ -950,24 +959,25 @@ ExecUpdate(ItemPointer tupleid,
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
 
-		/* FDW might have changed tuple */
-		tuple = ExecMaterializeSlot(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_storageam->slot_update_tableoid(slot, RelationGetRelid(resultRelationDesc));
+
+		/* FDW might have changed tuple */
+		tuple = ExecHeapifySlot(slot);
 	}
 	else
 	{
 		LockTupleMode lockmode;
+		ItemPointerData newtid;
 
 		/*
 		 * 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
@@ -1003,11 +1013,11 @@ 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 = method->tuple_update(resultRelationDesc, tupleid, slot,
+									  estate->es_output_cid,
+									  estate->es_crosscheck_snapshot,
+									  true /* wait for commit */ ,
+									  &hufd, &lockmode, &newtid);
 		switch (result)
 		{
 			case HeapTupleSelfUpdated:
@@ -1045,6 +1055,7 @@ lreplace:;
 				return NULL;
 
 			case HeapTupleMayBeUpdated:
+				ItemPointerCopy(method->tuple_get_itempointer(tuple), &newtid);
 				break;
 
 			case HeapTupleUpdated:
@@ -1067,7 +1078,7 @@ lreplace:;
 					{
 						*tupleid = hufd.ctid;
 						slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
-						tuple = ExecMaterializeSlot(slot);
+						tuple = ExecHeapifySlot(slot);
 						goto lreplace;
 					}
 				}
@@ -1095,8 +1106,8 @@ lreplace:;
 		 *
 		 * 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),
+		if ((resultRelInfo->ri_NumIndices > 0) && !method->tuple_is_heaponly(tuple))
+			recheckIndexes = ExecInsertIndexTuples(slot, method->tuple_get_itempointer(tuple),
 												   estate, false, NULL, NIL);
 	}
 
@@ -1168,7 +1179,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	 * true anymore.
 	 */
 	tuple.t_self = *conflictTid;
-	test = heap_lock_tuple(relation, &tuple, estate->es_output_cid,
+	test = relation->rd_stamroutine->tuple_lock(relation, conflictTid, &tuple, estate->es_output_cid,
 						   lockmode, LockWaitBlock, false, &buffer,
 						   &hufd);
 	switch (test)
@@ -1254,7 +1265,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);
@@ -1416,7 +1427,7 @@ ExecModifyTable(ModifyTableState *node)
 	ItemPointer tupleid = NULL;
 	ItemPointerData tuple_ctid;
 	HeapTupleData oldtupdata;
-	HeapTuple	oldtuple;
+	StorageTuple	oldtuple;
 
 	/*
 	 * This should NOT get called during EvalPlanQual; we should have passed a
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index 0247bd2..51fb182 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -29,9 +29,9 @@
 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,
-				   HeapScanDesc scan);
+static StorageTuple tablesample_getnext(SampleScanState *scanstate);
+static bool SampleTupleVisible(StorageTuple tuple, OffsetNumber tupoffset,
+		HeapScanDesc scan); //hari
 
 /* ----------------------------------------------------------------
  *						Scan Support
@@ -47,7 +47,7 @@ static bool SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset,
 static TupleTableSlot *
 SampleNext(SampleScanState *node)
 {
-	HeapTuple	tuple;
+	StorageTuple	tuple;
 	TupleTableSlot *slot;
 
 	/*
@@ -66,7 +66,8 @@ SampleNext(SampleScanState *node)
 	if (tuple)
 		ExecStoreTuple(tuple,	/* tuple to store */
 					   slot,	/* slot to store in */
-					   node->ss.ss_currentScanDesc->rs_cbuf,	/* tuple's buffer */
+					   //harinode->ss.ss_currentScanDesc->rs_cbuf,	/* tuple's buffer */
+					   InvalidBuffer,
 					   false);	/* don't pfree this pointer */
 	else
 		ExecClearTuple(slot);
@@ -125,7 +126,7 @@ InitScanRelation(SampleScanState *node, EState *estate, int eflags)
 
 	node->ss.ss_currentRelation = currentRelation;
 
-	/* we won't set up the HeapScanDesc till later */
+	/* we won't set up the StorageScanDesc till later */
 	node->ss.ss_currentScanDesc = NULL;
 
 	/* and report the scan tuple slot's rowtype */
@@ -342,7 +343,7 @@ tablesample_init(SampleScanState *scanstate)
 	/* We'll use syncscan if there's no NextSampleBlock function */
 	allow_sync = (tsm->NextSampleBlock == NULL);
 
-	/* Now we can create or reset the HeapScanDesc */
+	/* Now we can create or reset the StorageScanDesc */
 	if (scanstate->ss.ss_currentScanDesc == NULL)
 	{
 		scanstate->ss.ss_currentScanDesc =
@@ -373,12 +374,12 @@ 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;
-	HeapScanDesc scan = scanstate->ss.ss_currentScanDesc;
-	HeapTuple	tuple = &(scan->rs_ctup);
+	HeapScanDesc scan = scanstate->ss.ss_currentScanDesc; //hari
+	HeapTuple	tuple = &(scan->rs_ctup); //hari
 	Snapshot	snapshot = scan->rs_snapshot;
 	bool		pagemode = scan->rs_pageatatime;
 	BlockNumber blockno;
@@ -409,7 +410,7 @@ tablesample_getnext(SampleScanState *scanstate)
 		else
 			blockno = scan->rs_startblock;
 		Assert(blockno < scan->rs_nblocks);
-		heapgetpage(scan, blockno);
+		//heapgetpage(scan, blockno); //hari
 		scan->rs_inited = true;
 	}
 	else
@@ -530,7 +531,7 @@ tablesample_getnext(SampleScanState *scanstate)
 		}
 
 		Assert(blockno < scan->rs_nblocks);
-		heapgetpage(scan, blockno);
+		//heapgetpage(scan, blockno); //hari
 
 		/* Re-establish state for new page */
 		if (!pagemode)
@@ -551,7 +552,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 5680464..9cba6b3 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -48,8 +48,8 @@ static TupleTableSlot *SeqNext(SeqScanState *node);
 static TupleTableSlot *
 SeqNext(SeqScanState *node)
 {
-	HeapTuple	tuple;
-	HeapScanDesc scandesc;
+	void	   *tuple;
+	StorageScanDesc scandesc;
 	EState	   *estate;
 	ScanDirection direction;
 	TupleTableSlot *slot;
@@ -90,8 +90,9 @@ SeqNext(SeqScanState *node)
 	if (tuple)
 		ExecStoreTuple(tuple,	/* tuple to store */
 					   slot,	/* slot to store in */
-					   scandesc->rs_cbuf,		/* buffer associated with this
-												 * tuple */
+					   //hariscandesc->rs_cbuf,		/* buffer associated with this
+						//						 * tuple */
+					   InvalidBuffer,
 					   false);	/* don't pfree this pointer */
 	else
 		ExecClearTuple(slot);
@@ -221,7 +222,7 @@ void
 ExecEndSeqScan(SeqScanState *node)
 {
 	Relation	relation;
-	HeapScanDesc scanDesc;
+	StorageScanDesc scanDesc;
 
 	/*
 	 * get information from node
@@ -266,7 +267,7 @@ ExecEndSeqScan(SeqScanState *node)
 void
 ExecReScanSeqScan(SeqScanState *node)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 
 	scan = node->ss.ss_currentScanDesc;
 
diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
index 4860ec0..a97993c 100644
--- a/src/backend/executor/nodeTidscan.c
+++ b/src/backend/executor/nodeTidscan.c
@@ -305,7 +305,7 @@ TidNext(TidScanState *node)
 	ScanDirection direction;
 	Snapshot	snapshot;
 	Relation	heapRelation;
-	HeapTuple	tuple;
+	StorageTuple	tuple;
 	TupleTableSlot *slot;
 	Buffer		buffer = InvalidBuffer;
 	ItemPointerData *tidList;
@@ -331,12 +331,6 @@ TidNext(TidScanState *node)
 	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.
 	 */
 	bBackward = ScanDirectionIsBackward(direction);
@@ -363,7 +357,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
@@ -371,9 +365,9 @@ TidNext(TidScanState *node)
 		 * current according to our snapshot.
 		 */
 		if (node->tss_isCurrentOf)
-			heap_get_latest_tid(heapRelation, snapshot, &tuple->t_self);
+			heap_get_latest_tid(heapRelation, snapshot, &tid);
 
-		if (heap_fetch(heapRelation, snapshot, tuple, &buffer, false, NULL))
+		if (heapRelation->rd_stamroutine->tuple_fetch(heapRelation, &tid, snapshot, &tuple, &buffer, false, NULL))
 		{
 			/*
 			 * store the scanned tuple in the scan tuple slot of the scan
@@ -385,7 +379,7 @@ TidNext(TidScanState *node)
 			ExecStoreTuple(tuple,		/* tuple to store */
 						   slot,	/* slot to store in */
 						   buffer,		/* buffer associated with tuple  */
-						   false);		/* don't pfree */
+						   true);		/* don't pfree */
 
 			/*
 			 * At this point we have an extra pin on the buffer, because
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 628bc9f..d1d73bf 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -2087,7 +2087,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;
@@ -2155,7 +2155,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 97c3925..996f129 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -627,11 +627,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)
 	{
@@ -655,7 +655,7 @@ SPI_copytuple(HeapTuple tuple)
 }
 
 HeapTupleHeader
-SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc)
+SPI_returntuple(StorageTuple tuple, TupleDesc tupdesc)
 {
 	MemoryContext oldcxt;
 	HeapTupleHeader dtup;
@@ -686,7 +686,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)
 {
@@ -852,7 +852,7 @@ char *
 SPI_gettype(TupleDesc tupdesc, int fnumber)
 {
 	Oid			typoid;
-	HeapTuple	typeTuple;
+	StorageTuple	typeTuple;
 	char	   *result;
 
 	SPI_result = 0;
@@ -960,7 +960,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);
@@ -1681,7 +1681,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);
@@ -1712,8 +1712,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 8d7e711..16adf26 100644
--- a/src/backend/executor/tqueue.c
+++ b/src/backend/executor/tqueue.c
@@ -192,12 +192,12 @@ static void TQSendRecordInfo(TQueueDestReceiver *tqueue, int32 typmod,
 				 TupleDesc tupledesc);
 static void TupleQueueHandleControlMessage(TupleQueueReader *reader,
 							   Size nbytes, char *data);
-static HeapTuple TupleQueueHandleDataMessage(TupleQueueReader *reader,
+static StorageTuple TupleQueueHandleDataMessage(TupleQueueReader *reader,
 							Size nbytes, HeapTupleHeader data);
-static HeapTuple TQRemapTuple(TupleQueueReader *reader,
+static StorageTuple TQRemapTuple(TupleQueueReader *reader,
 			 TupleDesc tupledesc,
 			 TupleRemapInfo **field_remapinfo,
-			 HeapTuple tuple);
+			 StorageTuple tuple);
 static Datum TQRemap(TupleQueueReader *reader, TupleRemapInfo *remapinfo,
 		Datum value, bool *changed);
 static Datum TQRemapArray(TupleQueueReader *reader, ArrayRemapInfo *remapinfo,
@@ -225,7 +225,7 @@ tqueueReceiveSlot(TupleTableSlot *slot, DestReceiver *self)
 {
 	TQueueDestReceiver *tqueue = (TQueueDestReceiver *) self;
 	TupleDesc	tupledesc = slot->tts_tupleDescriptor;
-	HeapTuple	tuple;
+	HeapTuple	tuple; //hari
 	shm_mq_result result;
 
 	/*
@@ -305,7 +305,7 @@ tqueueReceiveSlot(TupleTableSlot *slot, DestReceiver *self)
 	}
 
 	/* 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. */
@@ -675,7 +675,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)
 {
 	shm_mq_result result;
@@ -730,7 +730,7 @@ TupleQueueReaderNext(TupleQueueReader *reader, bool nowait, bool *done)
 /*
  * Handle a data message - that is, a tuple - from the remote side.
  */
-static HeapTuple
+static StorageTuple
 TupleQueueHandleDataMessage(TupleQueueReader *reader,
 							Size nbytes,
 							HeapTupleHeader data)
@@ -759,11 +759,11 @@ TupleQueueHandleDataMessage(TupleQueueReader *reader,
 /*
  * Copy the given tuple, remapping any transient typmods contained in it.
  */
-static HeapTuple
+static StorageTuple
 TQRemapTuple(TupleQueueReader *reader,
 			 TupleDesc tupledesc,
 			 TupleRemapInfo **field_remapinfo,
-			 HeapTuple tuple)
+			 StorageTuple tuple)
 {
 	Datum	   *values;
 	bool	   *isnull;
@@ -1011,7 +1011,7 @@ TQRemapRecord(TupleQueueReader *reader, RecordRemapInfo *remapinfo,
 	if (changed_typmod || remapinfo->field_remap != NULL)
 	{
 		HeapTupleData htup;
-		HeapTuple	atup;
+		HeapTuple	atup; //hari
 
 		/* For now, assume we always need to change the tuple in this case. */
 		*changed = true;
@@ -1121,7 +1121,7 @@ TupleQueueHandleControlMessage(TupleQueueReader *reader, Size nbytes,
 static TupleRemapInfo *
 BuildTupleRemapInfo(Oid typid, MemoryContext mycontext)
 {
-	HeapTuple	tup;
+	StorageTuple	tup;
 	Form_pg_type typ;
 
 	/* This is recursive, so it could be driven to stack overflow. */
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index a8ab945..1802a97 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -665,7 +665,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/relscan.h b/src/include/access/relscan.h
index f4d4f1e..62013dc 100644
--- a/src/include/access/relscan.h
+++ b/src/include/access/relscan.h
@@ -18,6 +18,7 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/itup.h"
+#include "access/storageamapi.h"
 #include "access/tupdesc.h"
 #include "storage/spin.h"
 
@@ -75,7 +76,7 @@ typedef struct HeapScanDescData
 	int			rs_cindex;		/* current tuple's index in vistuples */
 	int			rs_ntuples;		/* number of visible tuples on page */
 	OffsetNumber rs_vistuples[MaxHeapTuplesPerPage];	/* their offsets */
-}	HeapScanDescData;
+} HeapScanDescData;
 
 /*
  * We use the same IndexScanDescData structure for both amgettuple-based
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index d73969c..5e79ed0 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -138,7 +138,7 @@ extern TupleTableSlot *ExecBRInsertTriggers(EState *estate,
 					 TupleTableSlot *slot);
 extern void ExecARInsertTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
-					 HeapTuple trigtuple,
+					 TupleTableSlot *slot,
 					 List *recheckIndexes);
 extern TupleTableSlot *ExecIRInsertTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 8cc5f3a..0333df2 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -15,6 +15,7 @@
 #define EXECUTOR_H
 
 #include "catalog/partition.h"
+#include "access/storageamapi.h"
 #include "executor/execdesc.h"
 #include "nodes/parsenodes.h"
 
@@ -196,7 +197,7 @@ 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,
+extern StorageTuple EvalPlanQualFetch(EState *estate, Relation relation,
 				  int lockmode, LockWaitPolicy wait_policy, ItemPointer tid,
 				  TransactionId priorXmax);
 extern void EvalPlanQualInit(EPQState *epqstate, EState *estate,
@@ -204,8 +205,8 @@ extern void EvalPlanQualInit(EPQState *epqstate, EState *estate,
 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);
 extern void ExecSetupPartitionTupleRouting(Relation rel,
 							   PartitionDispatch **pd,
 							   ResultRelInfo **partitions,
diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h
index 7821a63..fe0ce3f 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 ffb4c28..4031344 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,9 +117,9 @@ 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,
+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);
@@ -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 892eec8..03282a9 100644
--- a/src/include/executor/tqueue.h
+++ b/src/include/executor/tqueue.h
@@ -27,7 +27,7 @@ extern DestReceiver *CreateTupleQueueDestReceiver(shm_mq_handle *handle);
 extern TupleQueueReader *CreateTupleQueueReader(shm_mq_handle *handle,
 					   TupleDesc tupledesc);
 extern void DestroyTupleQueueReader(TupleQueueReader *reader);
-extern HeapTuple TupleQueueReaderNext(TupleQueueReader *reader,
+extern StorageTuple TupleQueueReaderNext(TupleQueueReader *reader,
 					 bool nowait, bool *done);
 
 #endif   /* TQUEUE_H */
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index 7521739..546d5e3 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -164,12 +164,12 @@ 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 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);
diff --git a/src/include/funcapi.h b/src/include/funcapi.h
index 30e66b6..b1593b0 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -229,7 +229,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);
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index d33392f..b8b2d6a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -480,7 +480,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 */
 
@@ -1967,7 +1967,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.7.4.windows.1

0007-Replace-slot-functions-with-storage-access-methods.patchapplication/octet-stream; name=0007-Replace-slot-functions-with-storage-access-methods.patchDownload
From 13ed2713b82306d32b1bf2265a90386efde677ce Mon Sep 17 00:00:00 2001
From: Hari <haribabuk@fast.au.fujitsu.com>
Date: Fri, 9 Jun 2017 11:15:31 +1000
Subject: [PATCH 07/10] Replace slot functions with storage access methods

Replace all slot_* function with slot storage access
methods
---
 src/backend/access/common/printsimple.c  |  3 ++-
 src/backend/access/common/printtup.c     |  9 +++++----
 src/backend/catalog/index.c              |  2 +-
 src/backend/catalog/partition.c          |  2 +-
 src/backend/commands/copy.c              | 15 +++++++--------
 src/backend/executor/execCurrent.c       |  4 ++--
 src/backend/executor/execExprInterp.c    |  9 ++++++---
 src/backend/executor/execGrouping.c      | 10 +++++-----
 src/backend/executor/execJunk.c          |  4 ++--
 src/backend/executor/execMain.c          |  5 +++--
 src/backend/executor/functions.c         |  2 +-
 src/backend/executor/nodeAgg.c           |  8 ++++----
 src/backend/executor/nodeFunctionscan.c  |  2 +-
 src/backend/executor/nodeMergeAppend.c   |  4 ++--
 src/backend/executor/nodeNestloop.c      |  2 +-
 src/backend/executor/nodeSetOp.c         |  2 +-
 src/backend/executor/nodeSubplan.c       | 12 ++++++------
 src/backend/executor/tqueue.c            |  3 ++-
 src/backend/executor/tstoreReceiver.c    |  3 ++-
 src/backend/replication/logical/worker.c |  8 ++++++--
 src/backend/utils/adt/orderedsetaggs.c   |  4 ++--
 21 files changed, 62 insertions(+), 51 deletions(-)

diff --git a/src/backend/access/common/printsimple.c b/src/backend/access/common/printsimple.c
index 851c3bf..7ab9b43 100644
--- a/src/backend/access/common/printsimple.c
+++ b/src/backend/access/common/printsimple.c
@@ -19,6 +19,7 @@
 #include "postgres.h"
 
 #include "access/printsimple.h"
+#include "access/storageamapi.h"
 #include "catalog/pg_type.h"
 #include "fmgr.h"
 #include "libpq/pqformat.h"
@@ -63,7 +64,7 @@ printsimple(TupleTableSlot *slot, DestReceiver *self)
 	int			i;
 
 	/* Make sure the tuple is fully deconstructed */
-	slot_getallattrs(slot);
+	slot->tts_storageam->slot_getallattrs(slot);
 
 	/* Prepare and send message */
 	pq_beginmessage(&buf, 'D');
diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c
index a2ca2d7..0e5caa9 100644
--- a/src/backend/access/common/printtup.c
+++ b/src/backend/access/common/printtup.c
@@ -16,6 +16,7 @@
 #include "postgres.h"
 
 #include "access/printtup.h"
+#include "access/storageamapi.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "tcop/pquery.h"
@@ -310,7 +311,7 @@ printtup(TupleTableSlot *slot, DestReceiver *self)
 		printtup_prepare_info(myState, typeinfo, natts);
 
 	/* Make sure the tuple is fully deconstructed */
-	slot_getallattrs(slot);
+	slot->tts_storageam->slot_getallattrs(slot);
 
 	/* Switch into per-row context so we can recover memory below */
 	oldcontext = MemoryContextSwitchTo(myState->tmpcontext);
@@ -397,7 +398,7 @@ printtup_20(TupleTableSlot *slot, DestReceiver *self)
 		printtup_prepare_info(myState, typeinfo, natts);
 
 	/* Make sure the tuple is fully deconstructed */
-	slot_getallattrs(slot);
+	slot->tts_storageam->slot_getallattrs(slot);
 
 	/* Switch into per-row context so we can recover memory below */
 	oldcontext = MemoryContextSwitchTo(myState->tmpcontext);
@@ -542,7 +543,7 @@ debugtup(TupleTableSlot *slot, DestReceiver *self)
 
 	for (i = 0; i < natts; ++i)
 	{
-		attr = slot_getattr(slot, i + 1, &isnull);
+		attr = slot->tts_storageam->slot_getattr(slot, i + 1, &isnull);
 		if (isnull)
 			continue;
 		getTypeOutputInfo(typeinfo->attrs[i]->atttypid,
@@ -583,7 +584,7 @@ printtup_internal_20(TupleTableSlot *slot, DestReceiver *self)
 		printtup_prepare_info(myState, typeinfo, natts);
 
 	/* Make sure the tuple is fully deconstructed */
-	slot_getallattrs(slot);
+	slot->tts_storageam->slot_getallattrs(slot);
 
 	/* Switch into per-row context so we can recover memory below */
 	oldcontext = MemoryContextSwitchTo(myState->tmpcontext);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 2328b92..558dbbc 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1794,7 +1794,7 @@ FormIndexDatum(IndexInfo *indexInfo,
 			 * Plain index column; get the value we need directly from the
 			 * heap tuple.
 			 */
-			iDatum = slot_getattr(slot, keycol, &isNull);
+			iDatum = slot->tts_storageam->slot_getattr(slot, keycol, &isNull);
 		}
 		else
 		{
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 7304f6c..3fe5e0a 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1884,7 +1884,7 @@ FormPartitionKeyDatum(PartitionDispatch pd,
 		if (keycol != 0)
 		{
 			/* Plain column; get the value directly from the heap tuple */
-			datum = slot_getattr(slot, keycol, &isNull);
+			datum = slot->tts_storageam->slot_getattr(slot, keycol, &isNull);
 		}
 		else
 		{
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 84b1a54..afa4cb1 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2297,6 +2297,7 @@ CopyFrom(CopyState cstate)
 	bool	   *nulls;
 	ResultRelInfo *resultRelInfo;
 	ResultRelInfo *saved_resultRelInfo = NULL;
+	StorageAmRoutine *method;
 	EState	   *estate = CreateExecutorState(); /* for ExecConstraints() */
 	ExprContext *econtext;
 	TupleTableSlot *myslot;
@@ -2357,6 +2358,7 @@ CopyFrom(CopyState cstate)
 	}
 
 	tupDesc = RelationGetDescr(cstate->rel);
+	method = cstate->rel->rd_stamroutine;
 
 	/*----------
 	 * Check to see if we can avoid writing WAL
@@ -2626,8 +2628,6 @@ CopyFrom(CopyState cstate)
 
 			if (slot == NULL)	/* "do nothing" */
 				skip_tuple = true;
-			else	/* trigger might have changed tuple */
-				tuple = ExecMaterializeSlot(slot);
 		}
 
 		if (!skip_tuple)
@@ -2675,8 +2675,7 @@ 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);
+					method->tuple_insert(resultRelInfo->ri_RelationDesc, slot, mycid, hi_options, bistate, &(tuple->t_self));
 
 					if (resultRelInfo->ri_NumIndices > 0)
 						recheckIndexes = ExecInsertIndexTuples(slot,
@@ -2687,7 +2686,7 @@ CopyFrom(CopyState cstate)
 															   NIL);
 
 					/* AFTER ROW INSERT Triggers */
-					ExecARInsertTriggers(estate, resultRelInfo, tuple,
+					ExecARInsertTriggers(estate, resultRelInfo, slot,
 										 recheckIndexes);
 
 					list_free(recheckIndexes);
@@ -2840,7 +2839,7 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 				ExecInsertIndexTuples(myslot, &(bufferedTuples[i]->t_self),
 									  estate, false, NULL, NIL);
 			ExecARInsertTriggers(estate, resultRelInfo,
-								 bufferedTuples[i],
+								 myslot,
 								 recheckIndexes);
 			list_free(recheckIndexes);
 		}
@@ -2857,7 +2856,7 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 		{
 			cstate->cur_lineno = firstBufferedLineNo + i;
 			ExecARInsertTriggers(estate, resultRelInfo,
-								 bufferedTuples[i],
+								 myslot,
 								 NIL);
 		}
 	}
@@ -4708,7 +4707,7 @@ copy_dest_receive(TupleTableSlot *slot, DestReceiver *self)
 	CopyState	cstate = myState->cstate;
 
 	/* Make sure the tuple is fully deconstructed */
-	slot_getallattrs(slot);
+	slot->tts_storageam->slot_getallattrs(slot);
 
 	/* And send the data */
 	CopyOneRowTo(cstate, InvalidOid, slot->tts_values, slot->tts_isnull);
diff --git a/src/backend/executor/execCurrent.c b/src/backend/executor/execCurrent.c
index 3af4a90..350b688 100644
--- a/src/backend/executor/execCurrent.c
+++ b/src/backend/executor/execCurrent.c
@@ -185,12 +185,12 @@ execCurrentOf(CurrentOfExpr *cexpr,
 
 		/* Use slot_getattr to catch any possible mistakes */
 		tuple_tableoid =
-			DatumGetObjectId(slot_getattr(scanstate->ss_ScanTupleSlot,
+			DatumGetObjectId(scanstate->ss_ScanTupleSlot->tts_storageam->slot_getattr(scanstate->ss_ScanTupleSlot,
 										  TableOidAttributeNumber,
 										  &lisnull));
 		Assert(!lisnull);
 		tuple_tid = (ItemPointer)
-			DatumGetPointer(slot_getattr(scanstate->ss_ScanTupleSlot,
+			DatumGetPointer(scanstate->ss_ScanTupleSlot->tts_storageam->slot_getattr(scanstate->ss_ScanTupleSlot,
 										 SelfItemPointerAttributeNumber,
 										 &lisnull));
 		Assert(!lisnull);
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 1217304..14220dc 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -507,7 +507,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			Assert(innerslot->tts_tuple != &(innerslot->tts_minhdr));
 			/* heap_getsysattr has sufficient defenses against bad attnums */
 
-			*op->resvalue = heap_getsysattr(innerslot->tts_tuple, attnum,
+			*op->resvalue = heap_getsysattr(innerslot->tts_storageam->slot_get_tuple(innerslot),
+											attnum,
 											innerslot->tts_tupleDescriptor,
 											op->resnull);
 
@@ -523,7 +524,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			Assert(outerslot->tts_tuple != &(outerslot->tts_minhdr));
 
 			/* heap_getsysattr has sufficient defenses against bad attnums */
-			*op->resvalue = heap_getsysattr(outerslot->tts_tuple, attnum,
+			*op->resvalue = heap_getsysattr(outerslot->tts_storageam->slot_get_tuple(outerslot),
+											attnum,
 											outerslot->tts_tupleDescriptor,
 											op->resnull);
 
@@ -539,7 +541,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			Assert(scanslot->tts_tuple != &(scanslot->tts_minhdr));
 			/* heap_getsysattr has sufficient defenses against bad attnums */
 
-			*op->resvalue = heap_getsysattr(scanslot->tts_tuple, attnum,
+			*op->resvalue = heap_getsysattr(scanslot->tts_storageam->slot_get_tuple(scanslot),
+											attnum,
 											scanslot->tts_tupleDescriptor,
 											op->resnull);
 
diff --git a/src/backend/executor/execGrouping.c b/src/backend/executor/execGrouping.c
index 07c8852..0d33cfe 100644
--- a/src/backend/executor/execGrouping.c
+++ b/src/backend/executor/execGrouping.c
@@ -97,9 +97,9 @@ execTuplesMatch(TupleTableSlot *slot1,
 		bool		isNull1,
 					isNull2;
 
-		attr1 = slot_getattr(slot1, att, &isNull1);
+		attr1 = slot1->tts_storageam->slot_getattr(slot1, att, &isNull1);
 
-		attr2 = slot_getattr(slot2, att, &isNull2);
+		attr2 = slot2->tts_storageam->slot_getattr(slot2, att, &isNull2);
 
 		if (isNull1 != isNull2)
 		{
@@ -167,12 +167,12 @@ execTuplesUnequal(TupleTableSlot *slot1,
 		bool		isNull1,
 					isNull2;
 
-		attr1 = slot_getattr(slot1, att, &isNull1);
+		attr1 = slot1->tts_storageam->slot_getattr(slot1, att, &isNull1);
 
 		if (isNull1)
 			continue;			/* can't prove anything here */
 
-		attr2 = slot_getattr(slot2, att, &isNull2);
+		attr2 = slot2->tts_storageam->slot_getattr(slot2, att, &isNull2);
 
 		if (isNull2)
 			continue;			/* can't prove anything here */
@@ -498,7 +498,7 @@ TupleHashTableHash(struct tuplehash_hash *tb, const MinimalTuple tuple)
 		/* rotate hashkey left 1 bit at each step */
 		hashkey = (hashkey << 1) | ((hashkey & 0x80000000) ? 1 : 0);
 
-		attr = slot_getattr(slot, att, &isNull);
+		attr = slot->tts_storageam->slot_getattr(slot, att, &isNull);
 
 		if (!isNull)			/* treat nulls as having hash key 0 */
 		{
diff --git a/src/backend/executor/execJunk.c b/src/backend/executor/execJunk.c
index a422327..08f7b7a 100644
--- a/src/backend/executor/execJunk.c
+++ b/src/backend/executor/execJunk.c
@@ -250,7 +250,7 @@ ExecGetJunkAttribute(TupleTableSlot *slot, AttrNumber attno,
 {
 	Assert(attno > 0);
 
-	return slot_getattr(slot, attno, isNull);
+	return slot->tts_storageam->slot_getattr(slot, attno, isNull);
 }
 
 /*
@@ -274,7 +274,7 @@ ExecFilterJunk(JunkFilter *junkfilter, TupleTableSlot *slot)
 	/*
 	 * Extract all the values of the old tuple.
 	 */
-	slot_getallattrs(slot);
+	slot->tts_storageam->slot_getallattrs(slot);
 	old_values = slot->tts_values;
 	old_isnull = slot->tts_isnull;
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 3614513..b519cb0 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/storageamapi.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -1903,7 +1904,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		for (attrChk = 1; attrChk <= natts; attrChk++)
 		{
 			if (tupdesc->attrs[attrChk - 1]->attnotnull &&
-				slot_attisnull(slot, attrChk))
+				slot->tts_storageam->slot_attisnull(slot, attrChk))
 			{
 				char	   *val_desc;
 				Relation	orig_rel = rel;
@@ -2220,7 +2221,7 @@ ExecBuildSlotValueDescription(Oid reloid,
 		table_perm = any_perm = true;
 
 	/* Make sure the tuple is fully deconstructed */
-	slot_getallattrs(slot);
+	slot->tts_storageam->slot_getallattrs(slot);
 
 	for (i = 0; i < tupdesc->natts; i++)
 	{
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index e2ab776..e7db610 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -971,7 +971,7 @@ postquel_get_single_result(TupleTableSlot *slot,
 		 * Returning a scalar, which we have to extract from the first column
 		 * of the SELECT result, and then copy into result context if needed.
 		 */
-		value = slot_getattr(slot, 1, &(fcinfo->isnull));
+		value = slot->tts_storageam->slot_getattr(slot, 1, &(fcinfo->isnull));
 
 		if (!fcinfo->isnull)
 			value = datumCopy(value, fcache->typbyval, fcache->typlen);
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index a412402..1fbd5ad 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -1419,7 +1419,7 @@ process_ordered_aggregate_multi(AggState *aggstate,
 		 * transfn.  (This will help execTuplesMatch too, so we do it
 		 * immediately.)
 		 */
-		slot_getsomeattrs(slot1, numTransInputs);
+		slot1->tts_storageam->slot_getsomeattrs(slot1, numTransInputs);
 
 		if (numDistinctCols == 0 ||
 			!haveOldValue ||
@@ -1683,7 +1683,7 @@ prepare_projection_slot(AggState *aggstate, TupleTableSlot *slot, int currentSet
 			ListCell   *lc;
 
 			/* all_grouped_cols is arranged in desc order */
-			slot_getsomeattrs(slot, linitial_int(aggstate->all_grouped_cols));
+			slot->tts_storageam->slot_getsomeattrs(slot, linitial_int(aggstate->all_grouped_cols));
 
 			foreach(lc, aggstate->all_grouped_cols)
 			{
@@ -2032,7 +2032,7 @@ lookup_hash_entry(AggState *aggstate)
 	int			i;
 
 	/* transfer just the needed columns into hashslot */
-	slot_getsomeattrs(inputslot, perhash->largestGrpColIdx);
+	inputslot->tts_storageam->slot_getsomeattrs(inputslot, perhash->largestGrpColIdx);
 	ExecClearTuple(hashslot);
 
 	for (i = 0; i < perhash->numhashGrpCols; i++)
@@ -2608,7 +2608,7 @@ agg_retrieve_hash_table(AggState *aggstate)
 		 * columns.
 		 */
 		ExecStoreMinimalTuple(entry->firstTuple, hashslot, false);
-		slot_getallattrs(hashslot);
+		hashslot->tts_storageam->slot_getallattrs(hashslot);
 
 		ExecClearTuple(firstSlot);
 		memset(firstSlot->tts_isnull, true,
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c
index 426527d..9d2de95 100644
--- a/src/backend/executor/nodeFunctionscan.c
+++ b/src/backend/executor/nodeFunctionscan.c
@@ -207,7 +207,7 @@ FunctionNext(FunctionScanState *node)
 			/*
 			 * we have a result, so just copy it to the result cols.
 			 */
-			slot_getallattrs(fs->func_slot);
+			fs->func_slot->tts_storageam->slot_getallattrs(fs->func_slot);
 
 			for (i = 0; i < fs->colcount; i++)
 			{
diff --git a/src/backend/executor/nodeMergeAppend.c b/src/backend/executor/nodeMergeAppend.c
index fef83db..501e5fc 100644
--- a/src/backend/executor/nodeMergeAppend.c
+++ b/src/backend/executor/nodeMergeAppend.c
@@ -249,8 +249,8 @@ heap_compare_slots(Datum a, Datum b, void *arg)
 					isNull2;
 		int			compare;
 
-		datum1 = slot_getattr(s1, attno, &isNull1);
-		datum2 = slot_getattr(s2, attno, &isNull2);
+		datum1 = s1->tts_storageam->slot_getattr(s1, attno, &isNull1);
+		datum2 = s2->tts_storageam->slot_getattr(s2, attno, &isNull2);
 
 		compare = ApplySortComparator(datum1, isNull1,
 									  datum2, isNull2,
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index 69d2453..5874c74 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -133,7 +133,7 @@ ExecNestLoop(NestLoopState *node)
 				Assert(IsA(nlp->paramval, Var));
 				Assert(nlp->paramval->varno == OUTER_VAR);
 				Assert(nlp->paramval->varattno > 0);
-				prm->value = slot_getattr(outerTupleSlot,
+				prm->value = outerTupleSlot->tts_storageam->slot_getattr(outerTupleSlot,
 										  nlp->paramval->varattno,
 										  &(prm->isnull));
 				/* Flag parameter value as changed */
diff --git a/src/backend/executor/nodeSetOp.c b/src/backend/executor/nodeSetOp.c
index 9ae53bb..056e968 100644
--- a/src/backend/executor/nodeSetOp.c
+++ b/src/backend/executor/nodeSetOp.c
@@ -104,7 +104,7 @@ fetch_tuple_flag(SetOpState *setopstate, TupleTableSlot *inputslot)
 	int			flag;
 	bool		isNull;
 
-	flag = DatumGetInt32(slot_getattr(inputslot,
+	flag = DatumGetInt32(inputslot->tts_storageam->slot_getattr(inputslot,
 									  node->flagColIdx,
 									  &isNull));
 	Assert(!isNull);
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index e8fa4c8..4cf6870 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -358,7 +358,7 @@ ExecScanSubPlan(SubPlanState *node,
 			found = true;
 			/* stash away current value */
 			Assert(subplan->firstColType == tdesc->attrs[0]->atttypid);
-			dvalue = slot_getattr(slot, 1, &disnull);
+			dvalue = slot->tts_storageam->slot_getattr(slot, 1, &disnull);
 			astate = accumArrayResultAny(astate, dvalue, disnull,
 										 subplan->firstColType, oldcontext);
 			/* keep scanning subplan to collect all values */
@@ -386,7 +386,7 @@ ExecScanSubPlan(SubPlanState *node,
 
 			prmdata = &(econtext->ecxt_param_exec_vals[paramid]);
 			Assert(prmdata->execPlan == NULL);
-			prmdata->value = slot_getattr(slot, col, &(prmdata->isnull));
+			prmdata->value = slot->tts_storageam->slot_getattr(slot, col, &(prmdata->isnull));
 			col++;
 		}
 
@@ -556,7 +556,7 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
 
 			prmdata = &(innerecontext->ecxt_param_exec_vals[paramid]);
 			Assert(prmdata->execPlan == NULL);
-			prmdata->value = slot_getattr(slot, col,
+			prmdata->value = slot->tts_storageam->slot_getattr(slot, col,
 										  &(prmdata->isnull));
 			col++;
 		}
@@ -646,7 +646,7 @@ slotAllNulls(TupleTableSlot *slot)
 
 	for (i = 1; i <= ncols; i++)
 	{
-		if (!slot_attisnull(slot, i))
+		if (!slot->tts_storageam->slot_attisnull(slot, i))
 			return false;
 	}
 	return true;
@@ -666,7 +666,7 @@ slotNoNulls(TupleTableSlot *slot)
 
 	for (i = 1; i <= ncols; i++)
 	{
-		if (slot_attisnull(slot, i))
+		if (slot->tts_storageam->slot_attisnull(slot, i))
 			return false;
 	}
 	return true;
@@ -988,7 +988,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
 			found = true;
 			/* stash away current value */
 			Assert(subplan->firstColType == tdesc->attrs[0]->atttypid);
-			dvalue = slot_getattr(slot, 1, &disnull);
+			dvalue = slot->tts_storageam->slot_getattr(slot, 1, &disnull);
 			astate = accumArrayResultAny(astate, dvalue, disnull,
 										 subplan->firstColType, oldcontext);
 			/* keep scanning subplan to collect all values */
diff --git a/src/backend/executor/tqueue.c b/src/backend/executor/tqueue.c
index 16adf26..7bef12e 100644
--- a/src/backend/executor/tqueue.c
+++ b/src/backend/executor/tqueue.c
@@ -35,6 +35,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/storageamapi.h"
 #include "catalog/pg_type.h"
 #include "executor/tqueue.h"
 #include "funcapi.h"
@@ -265,7 +266,7 @@ tqueueReceiveSlot(TupleTableSlot *slot, DestReceiver *self)
 		MemoryContext oldcontext = NULL;
 
 		/* Deform the tuple so we can examine fields, if not done already. */
-		slot_getallattrs(slot);
+		slot->tts_storageam->slot_getallattrs(slot);
 
 		/* Iterate over each attribute and search it for transient typmods. */
 		for (i = 0; i < tupledesc->natts; i++)
diff --git a/src/backend/executor/tstoreReceiver.c b/src/backend/executor/tstoreReceiver.c
index 1e641c9..1ac48b2 100644
--- a/src/backend/executor/tstoreReceiver.c
+++ b/src/backend/executor/tstoreReceiver.c
@@ -20,6 +20,7 @@
 
 #include "postgres.h"
 
+#include "access/storageamapi.h"
 #include "access/tuptoaster.h"
 #include "executor/tstoreReceiver.h"
 
@@ -116,7 +117,7 @@ tstoreReceiveSlot_detoast(TupleTableSlot *slot, DestReceiver *self)
 	MemoryContext oldcxt;
 
 	/* Make sure the tuple is fully deconstructed */
-	slot_getallattrs(slot);
+	slot->tts_storageam->slot_getallattrs(slot);
 
 	/*
 	 * Fetch back any out-of-line datums.  We build the new datums array in
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 7d1787d..dabe87e 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -366,7 +366,7 @@ slot_modify_cstrings(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
 	SlotErrCallbackArg errarg;
 	ErrorContextCallback errcallback;
 
-	slot_getallattrs(slot);
+	slot->tts_storageam->slot_getallattrs(slot);
 	ExecClearTuple(slot);
 
 	/* Push callback + info on the error context stack */
@@ -700,6 +700,7 @@ apply_handle_update(StringInfo s)
 		found = RelationFindReplTupleSeq(rel->localrel, LockTupleExclusive,
 										 remoteslot, localslot);
 
+	remoteslot->tts_storageam->slot_clear_tuple(remoteslot);
 	ExecClearTuple(remoteslot);
 
 	/*
@@ -709,9 +710,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);
+		remoteslot->tts_storageam->slot_store_tuple(remoteslot, tuple, false);
 		slot_modify_cstrings(remoteslot, rel, newtup.values, newtup.changed);
 		MemoryContextSwitchTo(oldctx);
 
diff --git a/src/backend/utils/adt/orderedsetaggs.c b/src/backend/utils/adt/orderedsetaggs.c
index 8502fcf..718b862 100644
--- a/src/backend/utils/adt/orderedsetaggs.c
+++ b/src/backend/utils/adt/orderedsetaggs.c
@@ -1193,7 +1193,7 @@ hypothetical_rank_common(FunctionCallInfo fcinfo, int flag,
 	while (tuplesort_gettupleslot(osastate->sortstate, true, true, slot, NULL))
 	{
 		bool		isnull;
-		Datum		d = slot_getattr(slot, nargs + 1, &isnull);
+		Datum		d = slot->tts_storageam->slot_getattr(slot, nargs + 1, &isnull);
 
 		if (!isnull && DatumGetInt32(d) != 0)
 			break;
@@ -1357,7 +1357,7 @@ hypothetical_dense_rank_final(PG_FUNCTION_ARGS)
 								  &abbrevVal))
 	{
 		bool		isnull;
-		Datum		d = slot_getattr(slot, nargs + 1, &isnull);
+		Datum		d = slot->tts_storageam->slot_getattr(slot, nargs + 1, &isnull);
 		TupleTableSlot *tmpslot;
 
 		if (!isnull && DatumGetInt32(d) != 0)
-- 
2.7.4.windows.1

0008-Replace-heap_-functions-with-storage-access-methods.patchapplication/octet-stream; name=0008-Replace-heap_-functions-with-storage-access-methods.patchDownload
From 9041e7225262a3ab81e85a69288c2bdbd9a6f684 Mon Sep 17 00:00:00 2001
From: Hari <haribabuk@fast.au.fujitsu.com>
Date: Fri, 9 Jun 2017 11:36:35 +1000
Subject: [PATCH 08/10] Replace heap_* functions with storage access methods

Replace all heap_* functions with storage access methods
in the entire code, but these are not yet removed the
HeapTuple and HeapScanDesc use.
---
 contrib/pgrowlocks/pgrowlocks.c            | 14 +++++---
 contrib/pgstattuple/pgstattuple.c          |  9 ++---
 contrib/postgres_fdw/postgres_fdw.c        |  2 +-
 contrib/tsm_system_rows/tsm_system_rows.c  |  8 ++---
 contrib/tsm_system_time/tsm_system_time.c  |  2 +-
 src/backend/access/index/genam.c           |  8 +++--
 src/backend/bootstrap/bootstrap.c          | 30 ++++++++++-------
 src/backend/catalog/aclchk.c               | 17 ++++++----
 src/backend/catalog/index.c                | 32 +++++++++++-------
 src/backend/catalog/pg_conversion.c        | 10 +++---
 src/backend/catalog/pg_db_role_setting.c   | 10 +++---
 src/backend/catalog/pg_publication.c       |  8 +++--
 src/backend/catalog/pg_subscription.c      |  7 ++--
 src/backend/commands/cluster.c             | 18 ++++++----
 src/backend/commands/copy.c                |  7 ++--
 src/backend/commands/dbcommands.c          | 26 +++++++++-----
 src/backend/commands/indexcmds.c           |  9 +++--
 src/backend/commands/tablecmds.c           | 42 +++++++++++++----------
 src/backend/commands/tablespace.c          | 54 +++++++++++++++++-------------
 src/backend/commands/typecmds.c            | 15 +++++----
 src/backend/commands/vacuum.c              | 18 ++++++----
 src/backend/executor/execReplication.c     | 12 ++++---
 src/backend/executor/nodeBitmapHeapscan.c  | 13 +++----
 src/backend/executor/nodeSamplescan.c      |  6 ++--
 src/backend/executor/nodeSeqscan.c         | 11 +++---
 src/backend/postmaster/autovacuum.c        | 23 ++++++++-----
 src/backend/postmaster/pgstat.c            |  9 +++--
 src/backend/replication/logical/launcher.c |  8 +++--
 src/backend/rewrite/rewriteDefine.c        |  9 +++--
 src/backend/utils/init/postinit.c          |  9 +++--
 30 files changed, 269 insertions(+), 177 deletions(-)

diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index 00e2015..97ee00f 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"
@@ -55,7 +56,7 @@ PG_FUNCTION_INFO_V1(pgrowlocks);
 typedef struct
 {
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	int			ncolumns;
 } MyData;
 
@@ -70,13 +71,16 @@ Datum
 pgrowlocks(PG_FUNCTION_ARGS)
 {
 	FuncCallContext *funcctx;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	AttInMetadata *attinmeta;
 	Datum		result;
 	MyData	   *mydata;
 	Relation	rel;
+	StorageAmRoutine *method;
+
+	method = rel->rd_stamroutine;
 
 	if (SRF_IS_FIRSTCALL())
 	{
@@ -112,7 +116,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
 			aclcheck_error(aclresult, ACL_KIND_CLASS,
 						   RelationGetRelationName(rel));
 
-		scan = heap_beginscan(rel, GetActiveSnapshot(), 0, NULL);
+		scan = method->scan_begin(rel, GetActiveSnapshot(), 0, NULL);
 		mydata = palloc(sizeof(*mydata));
 		mydata->rel = rel;
 		mydata->scan = scan;
@@ -128,7 +132,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
 	scan = mydata->scan;
 
 	/* scan the relation */
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		HTSU_Result htsu;
 		TransactionId xmax;
@@ -293,7 +297,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
 		}
 	}
 
-	heap_endscan(scan);
+	method->scan_end(scan);
 	heap_close(mydata->rel, AccessShareLock);
 
 	SRF_RETURN_DONE(funcctx);
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index eb02ec5..5ff67e4 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -314,7 +314,7 @@ pgstat_relation(Relation rel, FunctionCallInfo fcinfo)
 static Datum
 pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	BlockNumber nblocks;
 	BlockNumber block = 0;		/* next block to count free space in */
@@ -322,15 +322,16 @@ 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);
+	scan = method->scan_begin_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 = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		CHECK_FOR_INTERRUPTS();
 
@@ -383,7 +384,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 		block++;
 	}
 
-	heap_endscan(scan);
+	method->scan_end(scan);
 	relation_close(rel, AccessShareLock);
 
 	stat.table_len = (uint64) nblocks *BLCKSZ;
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 080cb0a..498b812 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -3219,7 +3219,7 @@ convert_prep_stmt_params(PgFdwModifyState *fmstate,
 			Datum		value;
 			bool		isnull;
 
-			value = slot_getattr(slot, attnum, &isnull);
+			value = slot->tts_storageam->slot_getattr(slot, attnum, &isnull);
 			if (isnull)
 				p_values[pindex] = NULL;
 			else
diff --git a/contrib/tsm_system_rows/tsm_system_rows.c b/contrib/tsm_system_rows/tsm_system_rows.c
index 544458e..ed0078e 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, StorageScanDesc 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;
+	StorageScanDesc scan = node->ss.ss_currentScanDesc;
 
 	/* First call within scan? */
 	if (sampler->doneblocks == 0)
@@ -278,7 +278,7 @@ system_rows_nextsampletuple(SampleScanState *node,
 							OffsetNumber maxoffset)
 {
 	SystemRowsSamplerData *sampler = (SystemRowsSamplerData *) node->tsm_state;
-	HeapScanDesc scan = node->ss.ss_currentScanDesc;
+	StorageScanDesc scan = node->ss.ss_currentScanDesc;
 	OffsetNumber tupoffset = sampler->lt;
 
 	/* Quit if we've returned all needed tuples */
@@ -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, StorageScanDesc 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 af8d025..4c32166 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;
+	StorageScanDesc scan = node->ss.ss_currentScanDesc;
 	instr_time	cur_time;
 
 	/* First call within scan? */
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index a91fda7..7b85395 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/storageamapi.h"
 #include "access/transam.h"
 #include "catalog/index.h"
 #include "lib/stringinfo.h"
@@ -387,6 +388,7 @@ systable_beginscan(Relation heapRelation,
 	}
 	else
 	{
+		StorageAmRoutine *method = heapRelation->rd_stamroutine;
 		/*
 		 * We disallow synchronized scans when forced to use a heapscan on a
 		 * catalog.  In most cases the desired rows are near the front, so
@@ -394,7 +396,7 @@ 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,
+		sysscan->scan = method->scan_begin_strat(heapRelation, snapshot,
 											 nkeys, key,
 											 true, false);
 		sysscan->iscan = NULL;
@@ -432,7 +434,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 = sysscan->heap_rel->rd_stamroutine->scan_getnext(sysscan->scan, ForwardScanDirection);
 
 	return htup;
 }
@@ -504,7 +506,7 @@ systable_endscan(SysScanDesc sysscan)
 		index_close(sysscan->irel, AccessShareLock);
 	}
 	else
-		heap_endscan(sysscan->scan);
+		sysscan->heap_rel->rd_stamroutine->scan_end(sysscan->scan);
 
 	if (sysscan->snapshot)
 		UnregisterSnapshot(sysscan->snapshot);
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 4c28b2b..9b7241a 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/storageamapi.h"
 #include "access/xact.h"
 #include "bootstrap/bootstrap.h"
 #include "catalog/index.h"
@@ -571,20 +572,23 @@ boot_openrel(char *relname)
 
 	if (Typ == NULL)
 	{
+		StorageAmRoutine *method;
+
 		/* We can now load the pg_type data */
 		rel = heap_open(TypeRelationId, NoLock);
-		scan = heap_beginscan_catalog(rel, 0, NULL);
+		method = rel->rd_stamroutine;
+		scan = method->scan_begin_catalog(rel, 0, NULL);
 		i = 0;
-		while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tup = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 			++i;
-		heap_endscan(scan);
+		method->scan_end(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 = method->scan_begin_catalog(rel, 0, NULL);
 		app = Typ;
-		while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tup = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			(*app)->am_oid = HeapTupleGetOid(tup);
 			memcpy((char *) &(*app)->am_typ,
@@ -592,7 +596,7 @@ boot_openrel(char *relname)
 				   sizeof((*app)->am_typ));
 			app++;
 		}
-		heap_endscan(scan);
+		method->scan_end(scan);
 		heap_close(rel, NoLock);
 	}
 
@@ -877,6 +881,7 @@ gettype(char *type)
 	HeapScanDesc scan;
 	HeapTuple	tup;
 	struct typmap **app;
+	StorageAmRoutine *method;
 
 	if (Typ != NULL)
 	{
@@ -898,25 +903,26 @@ gettype(char *type)
 		}
 		elog(DEBUG4, "external type: %s", type);
 		rel = heap_open(TypeRelationId, NoLock);
-		scan = heap_beginscan_catalog(rel, 0, NULL);
+		method = rel->rd_stamroutine;
+		scan = method->scan_begin_catalog(rel, 0, NULL);
 		i = 0;
-		while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tup = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 			++i;
-		heap_endscan(scan);
+		method->scan_end(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 = method->scan_begin_catalog(rel, 0, NULL);
 		app = Typ;
-		while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tup = method->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);
+		method->scan_end(scan);
 		heap_close(rel, NoLock);
 		return gettype(type);
 	}
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 387a3be..22ccd34 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/storageamapi.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/binary_upgrade.h"
@@ -790,6 +791,7 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 					Relation	rel;
 					HeapScanDesc scan;
 					HeapTuple	tuple;
+					StorageAmRoutine *method;
 
 					ScanKeyInit(&key[0],
 								Anum_pg_proc_pronamespace,
@@ -797,14 +799,15 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 								ObjectIdGetDatum(namespaceId));
 
 					rel = heap_open(ProcedureRelationId, AccessShareLock);
-					scan = heap_beginscan_catalog(rel, 1, key);
+					method = rel->rd_stamroutine;
+					scan = method->scan_begin_catalog(rel, 1, key);
 
-					while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+					while ((tuple = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 					{
 						objects = lappend_oid(objects, HeapTupleGetOid(tuple));
 					}
 
-					heap_endscan(scan);
+					method->scan_end(scan);
 					heap_close(rel, AccessShareLock);
 				}
 				break;
@@ -831,6 +834,7 @@ getRelationsInNamespace(Oid namespaceId, char relkind)
 	Relation	rel;
 	HeapScanDesc scan;
 	HeapTuple	tuple;
+	StorageAmRoutine *method;
 
 	ScanKeyInit(&key[0],
 				Anum_pg_class_relnamespace,
@@ -842,14 +846,15 @@ getRelationsInNamespace(Oid namespaceId, char relkind)
 				CharGetDatum(relkind));
 
 	rel = heap_open(RelationRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 2, key);
+	method = rel->rd_stamroutine;
+	scan = method->scan_begin_catalog(rel, 2, key);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		relations = lappend_oid(relations, HeapTupleGetOid(tuple));
 	}
 
-	heap_endscan(scan);
+	method->scan_end(scan);
 	heap_close(rel, AccessShareLock);
 
 	return relations;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 558dbbc..42be25b 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1892,16 +1892,18 @@ index_update_stats(Relation rel,
 		/* don't assume syscache will work */
 		HeapScanDesc pg_class_scan;
 		ScanKeyData key[1];
+		StorageAmRoutine *method;
 
 		ScanKeyInit(&key[0],
 					ObjectIdAttributeNumber,
 					BTEqualStrategyNumber, F_OIDEQ,
 					ObjectIdGetDatum(relid));
 
-		pg_class_scan = heap_beginscan_catalog(pg_class, 1, key);
-		tuple = heap_getnext(pg_class_scan, ForwardScanDirection);
+		method =  pg_class->rd_stamroutine;
+		pg_class_scan = method->scan_begin_catalog(pg_class, 1, key);
+		tuple = method->scan_getnext(pg_class_scan, ForwardScanDirection);
 		tuple = heap_copytuple(tuple);
-		heap_endscan(pg_class_scan);
+		method->scan_end(pg_class_scan);
 	}
 	else
 	{
@@ -2216,6 +2218,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	TransactionId OldestXmin;
 	BlockNumber root_blkno = InvalidBlockNumber;
 	OffsetNumber root_offsets[MaxHeapTuplesPerPage];
+	StorageAmRoutine *method;
 
 	/*
 	 * sanity checks
@@ -2271,7 +2274,8 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		OldestXmin = GetOldestXmin(heapRelation, PROCARRAY_FLAGS_VACUUM);
 	}
 
-	scan = heap_beginscan_strat(heapRelation,	/* relation */
+	method = heapRelation->rd_stamroutine;
+	scan = method->scan_begin_strat(heapRelation,	/* relation */
 								snapshot,		/* snapshot */
 								0,		/* number of keys */
 								NULL,	/* scan key */
@@ -2280,7 +2284,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 
 	/* set our scan endpoints */
 	if (!allow_sync)
-		heap_setscanlimits(scan, start_blockno, numblocks);
+		method->scansetlimits(scan, start_blockno, numblocks);
 	else
 	{
 		/* syncscan can only be requested on whole relation */
@@ -2293,7 +2297,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	/*
 	 * Scan all tuples in the base relation.
 	 */
-	while ((heapTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((heapTuple = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		bool		tupleIsAlive;
 
@@ -2605,7 +2609,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		}
 	}
 
-	heap_endscan(scan);
+	method->scan_end(scan);
 
 	/* we can now forget our snapshot, if set */
 	if (IsBootstrapProcessingMode() || indexInfo->ii_Concurrent)
@@ -2649,6 +2653,7 @@ IndexCheckExclusion(Relation heapRelation,
 	EState	   *estate;
 	ExprContext *econtext;
 	Snapshot	snapshot;
+	StorageAmRoutine *method = heapRelation->rd_stamroutine;
 
 	/*
 	 * If we are reindexing the target index, mark it as no longer being
@@ -2676,14 +2681,14 @@ IndexCheckExclusion(Relation heapRelation,
 	 * Scan all live tuples in the base relation.
 	 */
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = heap_beginscan_strat(heapRelation,	/* relation */
+	scan = method->scan_begin_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)
+	while ((heapTuple = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		CHECK_FOR_INTERRUPTS();
 
@@ -2719,7 +2724,7 @@ IndexCheckExclusion(Relation heapRelation,
 								   estate, true);
 	}
 
-	heap_endscan(scan);
+	method->scan_end(scan);
 	UnregisterSnapshot(snapshot);
 
 	ExecDropSingleTupleTableSlot(slot);
@@ -2970,6 +2975,7 @@ validate_index_heapscan(Relation heapRelation,
 	ItemPointer indexcursor = NULL;
 	ItemPointerData decoded;
 	bool		tuplesort_empty = false;
+	StorageAmRoutine *method = heapRelation->rd_stamroutine;
 
 	/*
 	 * sanity checks
@@ -2996,7 +3002,7 @@ 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 */
+	scan = method->scan_begin_strat(heapRelation,	/* relation */
 								snapshot,		/* snapshot */
 								0,		/* number of keys */
 								NULL,	/* scan key */
@@ -3006,7 +3012,7 @@ validate_index_heapscan(Relation heapRelation,
 	/*
 	 * Scan all tuples matching the snapshot.
 	 */
-	while ((heapTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((heapTuple = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		ItemPointer heapcursor = &heapTuple->t_self;
 		ItemPointerData rootTuple;
@@ -3163,7 +3169,7 @@ validate_index_heapscan(Relation heapRelation,
 		}
 	}
 
-	heap_endscan(scan);
+	method->scan_end(scan);
 
 	ExecDropSingleTupleTableSlot(slot);
 
diff --git a/src/backend/catalog/pg_conversion.c b/src/backend/catalog/pg_conversion.c
index 5746dc3..01fb130 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/storageamapi.h"
 #include "access/sysattr.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
@@ -152,6 +153,7 @@ RemoveConversionById(Oid conversionOid)
 	HeapTuple	tuple;
 	HeapScanDesc scan;
 	ScanKeyData scanKeyData;
+	StorageAmRoutine *method;
 
 	ScanKeyInit(&scanKeyData,
 				ObjectIdAttributeNumber,
@@ -160,15 +162,15 @@ RemoveConversionById(Oid conversionOid)
 
 	/* open pg_conversion */
 	rel = heap_open(ConversionRelationId, RowExclusiveLock);
-
-	scan = heap_beginscan_catalog(rel, 1, &scanKeyData);
+	method = rel->rd_stamroutine;
+	scan = method->scan_begin_catalog(rel, 1, &scanKeyData);
 
 	/* search for the target tuple */
-	if (HeapTupleIsValid(tuple = heap_getnext(scan, ForwardScanDirection)))
+	if (HeapTupleIsValid(tuple = method->scan_getnext(scan, ForwardScanDirection)))
 		CatalogTupleDelete(rel, &tuple->t_self);
 	else
 		elog(ERROR, "could not find tuple for conversion %u", conversionOid);
-	heap_endscan(scan);
+	method->scan_end(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 323471b..c0adb61 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/storageamapi.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_db_role_setting.h"
@@ -174,9 +175,10 @@ DropSetting(Oid databaseid, Oid roleid)
 	ScanKeyData keys[2];
 	HeapTuple	tup;
 	int			numkeys = 0;
+	StorageAmRoutine *method;
 
 	relsetting = heap_open(DbRoleSettingRelationId, RowExclusiveLock);
-
+	method = relsetting->rd_stamroutine;
 	if (OidIsValid(databaseid))
 	{
 		ScanKeyInit(&keys[numkeys],
@@ -196,12 +198,12 @@ DropSetting(Oid databaseid, Oid roleid)
 		numkeys++;
 	}
 
-	scan = heap_beginscan_catalog(relsetting, numkeys, keys);
-	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	scan = method->scan_begin_catalog(relsetting, numkeys, keys);
+	while (HeapTupleIsValid(tup = method->scan_getnext(scan, ForwardScanDirection)))
 	{
 		CatalogTupleDelete(relsetting, &tup->t_self);
 	}
-	heap_endscan(scan);
+	method->scan_end(scan);
 
 	heap_close(relsetting, RowExclusiveLock);
 }
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 17105f4..f1eeccb 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -292,17 +292,19 @@ GetAllTablesPublicationRelations(void)
 	HeapScanDesc scan;
 	HeapTuple	tuple;
 	List	   *result = NIL;
+	StorageAmRoutine *method;
 
 	classRel = heap_open(RelationRelationId, AccessShareLock);
+	method = classRel->rd_stamroutine;
 
 	ScanKeyInit(&key[0],
 				Anum_pg_class_relkind,
 				BTEqualStrategyNumber, F_CHAREQ,
 				CharGetDatum(RELKIND_RELATION));
 
-	scan = heap_beginscan_catalog(classRel, 1, key);
+	scan = method->scan_begin_catalog(classRel, 1, key);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			relid = HeapTupleGetOid(tuple);
 		Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
@@ -311,7 +313,7 @@ GetAllTablesPublicationRelations(void)
 			result = lappend_oid(result, relid);
 	}
 
-	heap_endscan(scan);
+	method->scan_end(scan);
 	heap_close(classRel, AccessShareLock);
 
 	return result;
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index ab5f371..93d89d2 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/storageamapi.h"
 #include "access/xact.h"
 
 #include "catalog/indexing.h"
@@ -393,12 +394,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 = rel->rd_stamroutine->scan_begin_catalog(rel, nkeys, skey);
+	while (HeapTupleIsValid(tup = rel->rd_stamroutine->scan_getnext(scan, ForwardScanDirection)))
 	{
 		simple_heap_delete(rel, &tup->t_self);
 	}
-	heap_endscan(scan);
+	rel->rd_stamroutine->scan_end(scan);
 
 	heap_close(rel, RowExclusiveLock);
 }
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index ef1abf3..5786226 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"
@@ -758,6 +759,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 				tups_recently_dead = 0;
 	int			elevel = verbose ? INFO : DEBUG2;
 	PGRUsage	ru0;
+	StorageAmRoutine *method;
 
 	pg_rusage_init(&ru0);
 
@@ -771,6 +773,8 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	else
 		OldIndex = NULL;
 
+	method = OldHeap->rd_stamroutine;
+
 	/*
 	 * Their tuple descriptors should be exactly alike, but here we only need
 	 * assume that they have the same number of columns.
@@ -908,7 +912,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	}
 	else
 	{
-		heapScan = heap_beginscan(OldHeap, SnapshotAny, 0, (ScanKey) NULL);
+		heapScan = method->scan_begin(OldHeap, SnapshotAny, 0, (ScanKey) NULL);
 		indexScan = NULL;
 	}
 
@@ -958,7 +962,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 		}
 		else
 		{
-			tuple = heap_getnext(heapScan, ForwardScanDirection);
+			tuple = method->scan_getnext(heapScan, ForwardScanDirection);
 			if (tuple == NULL)
 				break;
 
@@ -1044,7 +1048,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
 	if (indexScan != NULL)
 		index_endscan(indexScan);
 	if (heapScan != NULL)
-		heap_endscan(heapScan);
+		method->scan_end(heapScan);
 
 	/*
 	 * In scan-and-sort mode, complete the sort, then read out all live tuples
@@ -1643,6 +1647,7 @@ get_tables_to_cluster(MemoryContext cluster_context)
 	MemoryContext old_context;
 	RelToCluster *rvtc;
 	List	   *rvs = NIL;
+	StorageAmRoutine *method;
 
 	/*
 	 * Get all indexes that have indisclustered set and are owned by
@@ -1651,12 +1656,13 @@ get_tables_to_cluster(MemoryContext cluster_context)
 	 * called with one of them as argument.
 	 */
 	indRelation = heap_open(IndexRelationId, AccessShareLock);
+	method = indRelation->rd_stamroutine;
 	ScanKeyInit(&entry,
 				Anum_pg_index_indisclustered,
 				BTEqualStrategyNumber, F_BOOLEQ,
 				BoolGetDatum(true));
-	scan = heap_beginscan_catalog(indRelation, 1, &entry);
-	while ((indexTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	scan = method->scan_begin_catalog(indRelation, 1, &entry);
+	while ((indexTuple = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		index = (Form_pg_index) GETSTRUCT(indexTuple);
 
@@ -1676,7 +1682,7 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 		MemoryContextSwitchTo(old_context);
 	}
-	heap_endscan(scan);
+	method->scan_end(scan);
 
 	relation_close(indRelation, AccessShareLock);
 
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index afa4cb1..810c0cd 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2042,14 +2042,15 @@ CopyTo(CopyState cstate)
 		bool	   *nulls;
 		HeapScanDesc scandesc;
 		HeapTuple	tuple;
+		StorageAmRoutine *method = cstate->rel->rd_stamroutine;
 
 		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 = method->scan_begin(cstate->rel, GetActiveSnapshot(), 0, NULL);
 
 		processed = 0;
-		while ((tuple = heap_getnext(scandesc, ForwardScanDirection)) != NULL)
+		while ((tuple = method->scan_getnext(scandesc, ForwardScanDirection)) != NULL)
 		{
 			CHECK_FOR_INTERRUPTS();
 
@@ -2061,7 +2062,7 @@ CopyTo(CopyState cstate)
 			processed++;
 		}
 
-		heap_endscan(scandesc);
+		method->scan_end(scandesc);
 
 		pfree(values);
 		pfree(nulls);
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 11038f6..ed19018 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/storageamapi.h"
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
@@ -585,13 +586,16 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 	PG_ENSURE_ERROR_CLEANUP(createdb_failure_callback,
 							PointerGetDatum(&fparms));
 	{
+		StorageAmRoutine *method;
+
 		/*
 		 * Iterate through all tablespaces of the template database, and copy
 		 * 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)
+		method = rel->rd_stamroutine;
+		scan = method->scan_begin_catalog(rel, 0, NULL);
+		while ((tuple = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			Oid			srctablespace = HeapTupleGetOid(tuple);
 			Oid			dsttablespace;
@@ -643,7 +647,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
 								  XLOG_DBASE_CREATE | XLR_SPECIAL_REL_UPDATE);
 			}
 		}
-		heap_endscan(scan);
+		method->scan_end(scan);
 		heap_close(rel, AccessShareLock);
 
 		/*
@@ -1873,10 +1877,12 @@ remove_dbtablespaces(Oid db_id)
 	Relation	rel;
 	HeapScanDesc scan;
 	HeapTuple	tuple;
+	StorageAmRoutine *method;
 
 	rel = heap_open(TableSpaceRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 0, NULL);
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	method = rel->rd_stamroutine;
+	scan = method->scan_begin_catalog(rel, 0, NULL);
+	while ((tuple = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			dsttablespace = HeapTupleGetOid(tuple);
 		char	   *dstpath;
@@ -1917,7 +1923,7 @@ remove_dbtablespaces(Oid db_id)
 		pfree(dstpath);
 	}
 
-	heap_endscan(scan);
+	method->scan_end(scan);
 	heap_close(rel, AccessShareLock);
 }
 
@@ -1940,10 +1946,12 @@ check_db_file_conflict(Oid db_id)
 	Relation	rel;
 	HeapScanDesc scan;
 	HeapTuple	tuple;
+	StorageAmRoutine *method;
 
 	rel = heap_open(TableSpaceRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 0, NULL);
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	method = rel->rd_stamroutine;
+	scan = method->scan_begin_catalog(rel, 0, NULL);
+	while ((tuple = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			dsttablespace = HeapTupleGetOid(tuple);
 		char	   *dstpath;
@@ -1966,7 +1974,7 @@ check_db_file_conflict(Oid db_id)
 		pfree(dstpath);
 	}
 
-	heap_endscan(scan);
+	method->scan_end(scan);
 	heap_close(rel, AccessShareLock);
 
 	return result;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 4861799..d5554c5 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/storageamapi.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
@@ -1869,6 +1870,7 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	List	   *relids = NIL;
 	ListCell   *l;
 	int			num_keys;
+	StorageAmRoutine *method;
 
 	AssertArg(objectName);
 	Assert(objectKind == REINDEX_OBJECT_SCHEMA ||
@@ -1935,8 +1937,9 @@ 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)
+	method = relationRelation->rd_stamroutine;
+	scan = method->scan_begin_catalog(relationRelation, num_keys, scan_keys);
+	while ((tuple = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Form_pg_class classtuple = (Form_pg_class) GETSTRUCT(tuple);
 		Oid			relid = HeapTupleGetOid(tuple);
@@ -1976,7 +1979,7 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 
 		MemoryContextSwitchTo(old);
 	}
-	heap_endscan(scan);
+	method->scan_end(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 fb961e4..8256145 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4424,6 +4424,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		List	   *dropped_attrs = NIL;
 		ListCell   *lc;
 		Snapshot	snapshot;
+		StorageAmRoutine *method = oldrel->rd_stamroutine;
 
 		if (newrel)
 			ereport(DEBUG1,
@@ -4477,7 +4478,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		 * checking all the constraints.
 		 */
 		snapshot = RegisterSnapshot(GetLatestSnapshot());
-		scan = heap_beginscan(oldrel, snapshot, 0, NULL);
+		scan = method->scan_begin(oldrel, snapshot, 0, NULL);
 
 		/*
 		 * Switch to per-tuple memory context and reset it for each tuple
@@ -4485,7 +4486,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		 */
 		oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
 
-		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tuple = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			if (tab->rewrite > 0)
 			{
@@ -4579,7 +4580,7 @@ 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);
+				newrel->rd_stamroutine->tuple_insert(newrel, newslot, mycid, hi_options, bistate, &tuple->t_self);
 
 			ResetExprContext(econtext);
 
@@ -4587,7 +4588,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 
 		MemoryContextSwitchTo(oldCxt);
-		heap_endscan(scan);
+		method->scan_end(scan);
 		UnregisterSnapshot(snapshot);
 
 		ExecDropSingleTupleTableSlot(oldslot);
@@ -4969,17 +4970,18 @@ find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior be
 	HeapScanDesc scan;
 	HeapTuple	tuple;
 	List	   *result = NIL;
+	StorageAmRoutine *method;
 
 	classRel = heap_open(RelationRelationId, AccessShareLock);
-
+	method = classRel->rd_stamroutine;
 	ScanKeyInit(&key[0],
 				Anum_pg_class_reloftype,
 				BTEqualStrategyNumber, F_OIDEQ,
 				ObjectIdGetDatum(typeOid));
 
-	scan = heap_beginscan_catalog(classRel, 1, key);
+	scan = method->scan_begin_catalog(classRel, 1, key);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		if (behavior == DROP_RESTRICT)
 			ereport(ERROR,
@@ -4991,7 +4993,7 @@ find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior be
 			result = lappend_oid(result, HeapTupleGetOid(tuple));
 	}
 
-	heap_endscan(scan);
+	method->scan_end(scan);
 	heap_close(classRel, AccessShareLock);
 
 	return result;
@@ -8090,6 +8092,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	Form_pg_constraint constrForm;
 	bool		isnull;
 	Snapshot	snapshot;
+	StorageAmRoutine *method;
 
 	/*
 	 * VALIDATE CONSTRAINT is a no-op for foreign tables and partitioned
@@ -8123,7 +8126,8 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	econtext->ecxt_scantuple = slot;
 
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = heap_beginscan(rel, snapshot, 0, NULL);
+	method = rel->rd_stamroutine;
+	scan = method->scan_begin(rel, snapshot, 0, NULL);
 
 	/*
 	 * Switch to per-tuple memory context and reset it for each tuple
@@ -8131,7 +8135,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	 */
 	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
@@ -8146,7 +8150,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	}
 
 	MemoryContextSwitchTo(oldcxt);
-	heap_endscan(scan);
+	method->scan_end(scan);
 	UnregisterSnapshot(snapshot);
 	ExecDropSingleTupleTableSlot(slot);
 	FreeExecutorState(estate);
@@ -8169,6 +8173,7 @@ validateForeignKeyConstraint(char *conname,
 	HeapTuple	tuple;
 	Trigger		trig;
 	Snapshot	snapshot;
+	StorageAmRoutine *method;
 
 	ereport(DEBUG1,
 			(errmsg("validating foreign key constraint \"%s\"", conname)));
@@ -8201,9 +8206,10 @@ validateForeignKeyConstraint(char *conname,
 	 * ereport(ERROR) and that's that.
 	 */
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = heap_beginscan(rel, snapshot, 0, NULL);
+	method = rel->rd_stamroutine;
+	scan = method->scan_begin(rel, snapshot, 0, NULL);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		FunctionCallInfoData fcinfo;
 		TriggerData trigdata;
@@ -8232,7 +8238,7 @@ validateForeignKeyConstraint(char *conname,
 		RI_FKey_check_ins(&fcinfo);
 	}
 
-	heap_endscan(scan);
+	method->scan_end(scan);
 	UnregisterSnapshot(snapshot);
 }
 
@@ -10620,6 +10626,7 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 	Oid			orig_tablespaceoid;
 	Oid			new_tablespaceoid;
 	List	   *role_oids = roleSpecsToIds(stmt->roles);
+	StorageAmRoutine *method;
 
 	/* Ensure we were not asked to move something we can't */
 	if (stmt->objtype != OBJECT_TABLE && stmt->objtype != OBJECT_INDEX &&
@@ -10680,8 +10687,9 @@ 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)
+	method = rel->rd_stamroutine;
+	scan = method->scan_begin_catalog(rel, 1, key);
+	while ((tuple = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			relOid = HeapTupleGetOid(tuple);
 		Form_pg_class relForm;
@@ -10740,7 +10748,7 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 		relations = lappend_oid(relations, relOid);
 	}
 
-	heap_endscan(scan);
+	method->scan_end(scan);
 	heap_close(rel, AccessShareLock);
 
 	if (relations == NIL)
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index f9c2620..ca7145e 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/storageamapi.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -406,18 +407,19 @@ DropTableSpace(DropTableSpaceStmt *stmt)
 	HeapTuple	tuple;
 	ScanKeyData entry[1];
 	Oid			tablespaceoid;
+	StorageAmRoutine *method;
 
 	/*
 	 * Find the target tuple
 	 */
 	rel = heap_open(TableSpaceRelationId, RowExclusiveLock);
-
+	method = rel->rd_stamroutine;
 	ScanKeyInit(&entry[0],
 				Anum_pg_tablespace_spcname,
 				BTEqualStrategyNumber, F_NAMEEQ,
 				CStringGetDatum(tablespacename));
-	scandesc = heap_beginscan_catalog(rel, 1, entry);
-	tuple = heap_getnext(scandesc, ForwardScanDirection);
+	scandesc = method->scan_begin_catalog(rel, 1, entry);
+	tuple = method->scan_getnext(scandesc, ForwardScanDirection);
 
 	if (!HeapTupleIsValid(tuple))
 	{
@@ -434,7 +436,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);
+			method->scan_end(scandesc);
 			heap_close(rel, NoLock);
 		}
 		return;
@@ -461,7 +463,7 @@ DropTableSpace(DropTableSpaceStmt *stmt)
 	 */
 	CatalogTupleDelete(rel, &tuple->t_self);
 
-	heap_endscan(scandesc);
+	method->scan_end(scandesc);
 
 	/*
 	 * Remove any comments or security labels on this tablespace.
@@ -917,16 +919,17 @@ RenameTableSpace(const char *oldname, const char *newname)
 	HeapTuple	newtuple;
 	Form_pg_tablespace newform;
 	ObjectAddress address;
+	StorageAmRoutine *method;
 
 	/* Search pg_tablespace */
 	rel = heap_open(TableSpaceRelationId, RowExclusiveLock);
-
+	method = rel->rd_stamroutine;
 	ScanKeyInit(&entry[0],
 				Anum_pg_tablespace_spcname,
 				BTEqualStrategyNumber, F_NAMEEQ,
 				CStringGetDatum(oldname));
-	scan = heap_beginscan_catalog(rel, 1, entry);
-	tup = heap_getnext(scan, ForwardScanDirection);
+	scan = method->scan_begin_catalog(rel, 1, entry);
+	tup = method->scan_getnext(scan, ForwardScanDirection);
 	if (!HeapTupleIsValid(tup))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -937,7 +940,7 @@ RenameTableSpace(const char *oldname, const char *newname)
 	newtuple = heap_copytuple(tup);
 	newform = (Form_pg_tablespace) GETSTRUCT(newtuple);
 
-	heap_endscan(scan);
+	method->scan_end(scan);
 
 	/* Must be owner */
 	if (!pg_tablespace_ownercheck(HeapTupleGetOid(newtuple), GetUserId()))
@@ -955,15 +958,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 = method->scan_begin_catalog(rel, 1, entry);
+	tup = method->scan_getnext(scan, ForwardScanDirection);
 	if (HeapTupleIsValid(tup))
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("tablespace \"%s\" already exists",
 						newname)));
 
-	heap_endscan(scan);
+	method->scan_end(scan);
 
 	/* OK, update the entry */
 	namestrcpy(&(newform->spcname), newname);
@@ -997,16 +1000,17 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt)
 	bool		repl_null[Natts_pg_tablespace];
 	bool		repl_repl[Natts_pg_tablespace];
 	HeapTuple	newtuple;
+	StorageAmRoutine *method;
 
 	/* Search pg_tablespace */
 	rel = heap_open(TableSpaceRelationId, RowExclusiveLock);
-
+	method = rel->rd_stamroutine;
 	ScanKeyInit(&entry[0],
 				Anum_pg_tablespace_spcname,
 				BTEqualStrategyNumber, F_NAMEEQ,
 				CStringGetDatum(stmt->tablespacename));
-	scandesc = heap_beginscan_catalog(rel, 1, entry);
-	tup = heap_getnext(scandesc, ForwardScanDirection);
+	scandesc = method->scan_begin_catalog(rel, 1, entry);
+	tup = method->scan_getnext(scandesc, ForwardScanDirection);
 	if (!HeapTupleIsValid(tup))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -1047,7 +1051,7 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt)
 	heap_freetuple(newtuple);
 
 	/* Conclude heap scan. */
-	heap_endscan(scandesc);
+	method->scan_end(scandesc);
 	heap_close(rel, NoLock);
 
 	return tablespaceoid;
@@ -1384,6 +1388,7 @@ get_tablespace_oid(const char *tablespacename, bool missing_ok)
 	HeapScanDesc scandesc;
 	HeapTuple	tuple;
 	ScanKeyData entry[1];
+	StorageAmRoutine *method;
 
 	/*
 	 * Search pg_tablespace.  We use a heapscan here even though there is an
@@ -1391,13 +1396,13 @@ get_tablespace_oid(const char *tablespacename, bool missing_ok)
 	 * a few entries and so an indexed lookup is a waste of effort.
 	 */
 	rel = heap_open(TableSpaceRelationId, AccessShareLock);
-
+	method = rel->rd_stamroutine;
 	ScanKeyInit(&entry[0],
 				Anum_pg_tablespace_spcname,
 				BTEqualStrategyNumber, F_NAMEEQ,
 				CStringGetDatum(tablespacename));
-	scandesc = heap_beginscan_catalog(rel, 1, entry);
-	tuple = heap_getnext(scandesc, ForwardScanDirection);
+	scandesc = method->scan_begin_catalog(rel, 1, entry);
+	tuple = method->scan_getnext(scandesc, ForwardScanDirection);
 
 	/* We assume that there can be at most one matching tuple */
 	if (HeapTupleIsValid(tuple))
@@ -1405,7 +1410,7 @@ get_tablespace_oid(const char *tablespacename, bool missing_ok)
 	else
 		result = InvalidOid;
 
-	heap_endscan(scandesc);
+	method->scan_end(scandesc);
 	heap_close(rel, AccessShareLock);
 
 	if (!OidIsValid(result) && !missing_ok)
@@ -1430,6 +1435,7 @@ get_tablespace_name(Oid spc_oid)
 	HeapScanDesc scandesc;
 	HeapTuple	tuple;
 	ScanKeyData entry[1];
+	StorageAmRoutine *method;
 
 	/*
 	 * Search pg_tablespace.  We use a heapscan here even though there is an
@@ -1437,13 +1443,13 @@ get_tablespace_name(Oid spc_oid)
 	 * few entries and so an indexed lookup is a waste of effort.
 	 */
 	rel = heap_open(TableSpaceRelationId, AccessShareLock);
-
+	method = rel->rd_stamroutine;
 	ScanKeyInit(&entry[0],
 				ObjectIdAttributeNumber,
 				BTEqualStrategyNumber, F_OIDEQ,
 				ObjectIdGetDatum(spc_oid));
-	scandesc = heap_beginscan_catalog(rel, 1, entry);
-	tuple = heap_getnext(scandesc, ForwardScanDirection);
+	scandesc = method->scan_begin_catalog(rel, 1, entry);
+	tuple = method->scan_getnext(scandesc, ForwardScanDirection);
 
 	/* We assume that there can be at most one matching tuple */
 	if (HeapTupleIsValid(tuple))
@@ -1451,7 +1457,7 @@ get_tablespace_name(Oid spc_oid)
 	else
 		result = NULL;
 
-	heap_endscan(scandesc);
+	method->scan_end(scandesc);
 	heap_close(rel, AccessShareLock);
 
 	return result;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index c765e97..2c494cc 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2312,11 +2312,13 @@ AlterDomainNotNull(List *names, bool notNull)
 			HeapScanDesc scan;
 			HeapTuple	tuple;
 			Snapshot	snapshot;
+			StorageAmRoutine *method;
 
 			/* Scan all tuples in this relation */
 			snapshot = RegisterSnapshot(GetLatestSnapshot());
-			scan = heap_beginscan(testrel, snapshot, 0, NULL);
-			while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+			method = testrel->rd_stamroutine;
+			scan = method->scan_begin(testrel, snapshot, 0, NULL);
+			while ((tuple = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 			{
 				int			i;
 
@@ -2344,7 +2346,7 @@ AlterDomainNotNull(List *names, bool notNull)
 					}
 				}
 			}
-			heap_endscan(scan);
+			method->scan_end(scan);
 			UnregisterSnapshot(snapshot);
 
 			/* Close each rel after processing, but keep lock */
@@ -2707,11 +2709,12 @@ validateDomainConstraint(Oid domainoid, char *ccbin)
 		HeapScanDesc scan;
 		HeapTuple	tuple;
 		Snapshot	snapshot;
+		StorageAmRoutine *method = testrel->rd_stamroutine;
 
 		/* Scan all tuples in this relation */
 		snapshot = RegisterSnapshot(GetLatestSnapshot());
-		scan = heap_beginscan(testrel, snapshot, 0, NULL);
-		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		scan = method->scan_begin(testrel, snapshot, 0, NULL);
+		while ((tuple = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			int			i;
 
@@ -2753,7 +2756,7 @@ validateDomainConstraint(Oid domainoid, char *ccbin)
 
 			ResetExprContext(econtext);
 		}
-		heap_endscan(scan);
+		method->scan_end(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 9fbb0eb..e30f270 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/storageamapi.h"
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
@@ -444,12 +445,14 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
 		Relation	pgclass;
 		HeapScanDesc scan;
 		HeapTuple	tuple;
+		StorageAmRoutine *method;
 
 		pgclass = heap_open(RelationRelationId, AccessShareLock);
+		method = pgclass->rd_stamroutine;
 
-		scan = heap_beginscan_catalog(pgclass, 0, NULL);
+		scan = method->scan_begin_catalog(pgclass, 0, NULL);
 
-		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		while ((tuple = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 		{
 			Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
 
@@ -469,7 +472,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
 			MemoryContextSwitchTo(oldcontext);
 		}
 
-		heap_endscan(scan);
+		method->scan_end(scan);
 		heap_close(pgclass, AccessShareLock);
 	}
 
@@ -1096,6 +1099,7 @@ vac_truncate_clog(TransactionId frozenXID,
 	Oid			minmulti_datoid;
 	bool		bogus = false;
 	bool		frozenAlreadyWrapped = false;
+	StorageAmRoutine *method;
 
 	/* init oldest datoids to sync with my frozenXID/minMulti values */
 	oldestxid_datoid = MyDatabaseId;
@@ -1120,10 +1124,10 @@ vac_truncate_clog(TransactionId frozenXID,
 	 * as it could be.
 	 */
 	relation = heap_open(DatabaseRelationId, AccessShareLock);
+	method = relation->rd_stamroutine;
+	scan = method->scan_begin_catalog(relation, 0, NULL);
 
-	scan = heap_beginscan_catalog(relation, 0, NULL);
-
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tuple = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		volatile FormData_pg_database *dbform = (Form_pg_database) GETSTRUCT(tuple);
 		TransactionId datfrozenxid = dbform->datfrozenxid;
@@ -1160,7 +1164,7 @@ vac_truncate_clog(TransactionId frozenXID,
 		}
 	}
 
-	heap_endscan(scan);
+	method->scan_end(scan);
 
 	heap_close(relation, AccessShareLock);
 
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 206238a..dca2f08 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -270,25 +270,27 @@ RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 						 TupleTableSlot *searchslot, TupleTableSlot *outslot)
 {
     StorageTuple            scantuple;
-	HeapScanDesc scan;
+    StorageScanDesc scan;
 	SnapshotData snap;
 	TransactionId xwait;
 	bool		found;
 	TupleDesc	desc = RelationGetDescr(rel);
+    StorageAmRoutine *method;
 
 	Assert(equalTupleDescs(desc, outslot->tts_tupleDescriptor));
 
 	/* Start an index scan. */
 	InitDirtySnapshot(snap);
-	scan = heap_beginscan(rel, &snap, 0, NULL);
+    method = rel->rd_stamroutine;
+    scan = method->scan_begin(rel, &snap, 0, NULL);
 
 retry:
 	found = false;
 
-	heap_rescan(scan, NULL);
+    method->scan_rescan(scan, NULL);
 
 	/* Try to find the tuple */
-	while ((scantuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((scantuple = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		if (!tuple_equals_slot(desc, scantuple, searchslot))
 			continue;
@@ -353,7 +355,7 @@ retry:
 		}
 	}
 
-	heap_endscan(scan);
+	method->scan_end(scan);
 
 	return found;
 }
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index f8a4e7d..51d8f74 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -690,7 +690,7 @@ ExecReScanBitmapHeapScan(BitmapHeapScanState *node)
 	PlanState  *outerPlan = outerPlanState(node);
 
 	/* rescan to release any page pin */
-	heap_rescan(node->ss.ss_currentScanDesc, NULL);
+	node->ss.ss_currentRelation->rd_stamroutine->scan_rescan(node->ss.ss_currentScanDesc, NULL);
 
 	if (node->tbmiterator)
 		tbm_end_iterate(node->tbmiterator);
@@ -745,7 +745,7 @@ void
 ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 {
 	Relation	relation;
-	HeapScanDesc scanDesc;
+	StorageScanDesc scanDesc;
 
 	/*
 	 * extract information from the node
@@ -786,7 +786,7 @@ ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 	/*
 	 * close heap scan
 	 */
-	heap_endscan(scanDesc);
+	relation->rd_stamroutine->scan_end(scanDesc);
 
 	/*
 	 * close the heap relation.
@@ -884,9 +884,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.
+	 * to create a StorageScanDesc --- most of the fields in it are usable.
 	 */
-	scanstate->ss.ss_currentScanDesc = heap_beginscan_bm(currentRelation,
+	scanstate->ss.ss_currentScanDesc
+			= currentRelation->rd_stamroutine->scan_begin_bm(currentRelation,
 														 estate->es_snapshot,
 														 0,
 														 NULL);
@@ -1019,5 +1020,5 @@ ExecBitmapHeapInitializeWorker(BitmapHeapScanState *node, shm_toc *toc)
 	node->pstate = pstate;
 
 	snapshot = RestoreSnapshot(pstate->phs_snapshot_data);
-	heap_update_snapshot(node->ss.ss_currentScanDesc, snapshot);
+	node->ss.ss_currentRelation->rd_stamroutine->scan_update_snapshot(node->ss.ss_currentScanDesc, snapshot);
 }
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index 51fb182..a7d2ba8 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -242,7 +242,7 @@ ExecEndSampleScan(SampleScanState *node)
 	 * close heap scan
 	 */
 	if (node->ss.ss_currentScanDesc)
-		heap_endscan(node->ss.ss_currentScanDesc);
+		node->ss.ss_currentRelation->rd_stamroutine->scan_end(node->ss.ss_currentScanDesc);
 
 	/*
 	 * close the heap relation.
@@ -347,7 +347,7 @@ tablesample_init(SampleScanState *scanstate)
 	if (scanstate->ss.ss_currentScanDesc == NULL)
 	{
 		scanstate->ss.ss_currentScanDesc =
-			heap_beginscan_sampling(scanstate->ss.ss_currentRelation,
+				scanstate->ss.ss_currentRelation->rd_stamroutine->scan_begin_sampling(scanstate->ss.ss_currentRelation,
 									scanstate->ss.ps.state->es_snapshot,
 									0, NULL,
 									scanstate->use_bulkread,
@@ -356,7 +356,7 @@ tablesample_init(SampleScanState *scanstate)
 	}
 	else
 	{
-		heap_rescan_set_params(scanstate->ss.ss_currentScanDesc, NULL,
+		scanstate->ss.ss_currentRelation->rd_stamroutine->scan_rescan_set_params(scanstate->ss.ss_currentScanDesc, NULL,
 							   scanstate->use_bulkread,
 							   allow_sync,
 							   scanstate->use_pagemode);
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index 9cba6b3..5096248 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -27,6 +27,7 @@
 #include "postgres.h"
 
 #include "access/relscan.h"
+#include "access/storageamapi.h"
 #include "executor/execdebug.h"
 #include "executor/nodeSeqscan.h"
 #include "utils/rel.h"
@@ -53,10 +54,12 @@ SeqNext(SeqScanState *node)
 	EState	   *estate;
 	ScanDirection direction;
 	TupleTableSlot *slot;
+	StorageAmRoutine *method;
 
 	/*
 	 * get information from the estate and scan state
 	 */
+	method = node->ss.ss_currentRelation->rd_stamroutine;
 	scandesc = node->ss.ss_currentScanDesc;
 	estate = node->ss.ps.state;
 	direction = estate->es_direction;
@@ -68,7 +71,7 @@ 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,
+		scandesc = method->scan_begin(node->ss.ss_currentRelation,
 								  estate->es_snapshot,
 								  0, NULL);
 		node->ss.ss_currentScanDesc = scandesc;
@@ -77,7 +80,7 @@ SeqNext(SeqScanState *node)
 	/*
 	 * get the next tuple from the table
 	 */
-	tuple = heap_getnext(scandesc, direction);
+	tuple = method->scan_getnext(scandesc, direction);
 
 	/*
 	 * save the tuple and the buffer returned to us by the access methods in
@@ -245,7 +248,7 @@ ExecEndSeqScan(SeqScanState *node)
 	 * close heap scan
 	 */
 	if (scanDesc != NULL)
-		heap_endscan(scanDesc);
+		relation->rd_stamroutine->scan_end(scanDesc);
 
 	/*
 	 * close the heap relation.
@@ -272,7 +275,7 @@ ExecReScanSeqScan(SeqScanState *node)
 	scan = node->ss.ss_currentScanDesc;
 
 	if (scan != NULL)
-		heap_rescan(scan,		/* scan desc */
+		node->ss.ss_currentRelation->rd_stamroutine->scan_rescan(scan,		/* scan desc */
 					NULL);		/* new scan keys */
 
 	ExecScanReScan((ScanState *) node);
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 89dd3b3..e6b8ee0 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -69,6 +69,7 @@
 #include "access/htup_details.h"
 #include "access/multixact.h"
 #include "access/reloptions.h"
+#include "access/storageamapi.h"
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/dependency.h"
@@ -1887,6 +1888,7 @@ get_database_list(void)
 	HeapScanDesc scan;
 	HeapTuple	tup;
 	MemoryContext resultcxt;
+	StorageAmRoutine *method;
 
 	/* This is the context that we will allocate our output data in */
 	resultcxt = CurrentMemoryContext;
@@ -1902,9 +1904,10 @@ get_database_list(void)
 	(void) GetTransactionSnapshot();
 
 	rel = heap_open(DatabaseRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 0, NULL);
+	method = rel->rd_stamroutine;
+	scan = method->scan_begin_catalog(rel, 0, NULL);
 
-	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	while (HeapTupleIsValid(tup = method->scan_getnext(scan, ForwardScanDirection)))
 	{
 		Form_pg_database pgdatabase = (Form_pg_database) GETSTRUCT(tup);
 		avw_dbase  *avdb;
@@ -1931,7 +1934,7 @@ get_database_list(void)
 		MemoryContextSwitchTo(oldcxt);
 	}
 
-	heap_endscan(scan);
+	method->scan_end(scan);
 	heap_close(rel, AccessShareLock);
 
 	CommitTransactionCommand();
@@ -1965,6 +1968,7 @@ do_autovacuum(void)
 	int			effective_multixact_freeze_max_age;
 	bool		did_vacuum = false;
 	bool		found_concurrent_worker = false;
+	StorageAmRoutine *method;
 
 	/*
 	 * StartTransactionCommand and CommitTransactionCommand will automatically
@@ -2033,6 +2037,7 @@ do_autovacuum(void)
 	shared = pgstat_fetch_stat_dbentry(InvalidOid);
 
 	classRel = heap_open(RelationRelationId, AccessShareLock);
+	method = classRel->rd_stamroutine;
 
 	/* create a copy so we can use it after closing pg_class */
 	pg_class_desc = CreateTupleDescCopy(RelationGetDescr(classRel));
@@ -2061,13 +2066,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 = method->scan_begin_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 = method->scan_getnext(relScan, ForwardScanDirection)) != NULL)
 	{
 		Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
 		PgStat_StatTabEntry *tabentry;
@@ -2153,7 +2158,7 @@ do_autovacuum(void)
 		}
 	}
 
-	heap_endscan(relScan);
+	method->scan_end(relScan);
 
 	/* second pass: check TOAST tables */
 	ScanKeyInit(&key,
@@ -2161,8 +2166,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 = method->scan_begin_catalog(classRel, 1, &key);
+	while ((tuple = method->scan_getnext(relScan, ForwardScanDirection)) != NULL)
 	{
 		Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
 		PgStat_StatTabEntry *tabentry;
@@ -2208,7 +2213,7 @@ do_autovacuum(void)
 			table_oids = lappend_oid(table_oids, relid);
 	}
 
-	heap_endscan(relScan);
+	method->scan_end(relScan);
 	heap_close(classRel, AccessShareLock);
 
 	/*
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index f453dad..d07914b 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/storageamapi.h"
 #include "access/transam.h"
 #include "access/twophase_rmgr.h"
 #include "access/xact.h"
@@ -1209,6 +1210,7 @@ pgstat_collect_oids(Oid catalogid)
 	HeapScanDesc scan;
 	HeapTuple	tup;
 	Snapshot	snapshot;
+	StorageAmRoutine *method;
 
 	memset(&hash_ctl, 0, sizeof(hash_ctl));
 	hash_ctl.keysize = sizeof(Oid);
@@ -1221,8 +1223,9 @@ 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)
+	method = rel->rd_stamroutine;
+	scan = method->scan_begin(rel, snapshot, 0, NULL);
+	while ((tup = method->scan_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Oid			thisoid = HeapTupleGetOid(tup);
 
@@ -1230,7 +1233,7 @@ pgstat_collect_oids(Oid catalogid)
 
 		(void) hash_search(htab, (void *) &thisoid, HASH_ENTER, NULL);
 	}
-	heap_endscan(scan);
+	method->scan_end(scan);
 	UnregisterSnapshot(snapshot);
 	heap_close(rel, AccessShareLock);
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 4e2c350..9813efc 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -102,6 +102,7 @@ get_subscription_list(void)
 	HeapScanDesc scan;
 	HeapTuple	tup;
 	MemoryContext resultcxt;
+	StorageAmRoutine *method;
 
 	/* This is the context that we will allocate our output data in */
 	resultcxt = CurrentMemoryContext;
@@ -117,9 +118,10 @@ get_subscription_list(void)
 	(void) GetTransactionSnapshot();
 
 	rel = heap_open(SubscriptionRelationId, AccessShareLock);
-	scan = heap_beginscan_catalog(rel, 0, NULL);
+	method = rel->rd_stamroutine;
+	scan = method->scan_begin_catalog(rel, 0, NULL);
 
-	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	while (HeapTupleIsValid(tup = method->scan_getnext(scan, ForwardScanDirection)))
 	{
 		Form_pg_subscription subform = (Form_pg_subscription) GETSTRUCT(tup);
 		Subscription *sub;
@@ -145,7 +147,7 @@ get_subscription_list(void)
 		MemoryContextSwitchTo(oldcxt);
 	}
 
-	heap_endscan(scan);
+	method->scan_end(scan);
 	heap_close(rel, AccessShareLock);
 
 	CommitTransactionCommand();
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index fd3768d..3db218d 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/storageamapi.h"
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
@@ -421,6 +422,7 @@ DefineQueryRewrite(char *rulename,
 		{
 			HeapScanDesc scanDesc;
 			Snapshot	snapshot;
+			StorageAmRoutine *method;
 
 			if (event_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 				ereport(ERROR,
@@ -429,13 +431,14 @@ DefineQueryRewrite(char *rulename,
 					   RelationGetRelationName(event_relation))));
 
 			snapshot = RegisterSnapshot(GetLatestSnapshot());
-			scanDesc = heap_beginscan(event_relation, snapshot, 0, NULL);
-			if (heap_getnext(scanDesc, ForwardScanDirection) != NULL)
+			method = event_relation->rd_stamroutine;
+			scanDesc = method->scan_begin(event_relation, snapshot, 0, NULL);
+			if (method->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);
+			method->scan_end(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 b8b4a06..400da47 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -21,6 +21,7 @@
 
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/storageamapi.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -1205,13 +1206,15 @@ ThereIsAtLeastOneRole(void)
 	Relation	pg_authid_rel;
 	HeapScanDesc scan;
 	bool		result;
+	StorageAmRoutine *method;
 
 	pg_authid_rel = heap_open(AuthIdRelationId, AccessShareLock);
+	method = pg_authid_rel->rd_stamroutine;
 
-	scan = heap_beginscan_catalog(pg_authid_rel, 0, NULL);
-	result = (heap_getnext(scan, ForwardScanDirection) != NULL);
+	scan = method->scan_begin_catalog(pg_authid_rel, 0, NULL);
+	result = (method->scan_getnext(scan, ForwardScanDirection) != NULL);
 
-	heap_endscan(scan);
+	method->scan_end(scan);
 	heap_close(pg_authid_rel, AccessShareLock);
 
 	return result;
-- 
2.7.4.windows.1

0009-Remaining-heap_insert-calls-repalce.patchapplication/octet-stream; name=0009-Remaining-heap_insert-calls-repalce.patchDownload
From 546dd54a77a190d104bc79bfdb016bc23948bd86 Mon Sep 17 00:00:00 2001
From: Hari <haribabuk@fast.au.fujitsu.com>
Date: Fri, 9 Jun 2017 11:38:36 +1000
Subject: [PATCH 09/10] Remaining heap_insert calls repalce

---
 src/backend/commands/createas.c | 26 +++++++++++++-------------
 src/backend/commands/matview.c  | 21 ++++++++++-----------
 2 files changed, 23 insertions(+), 24 deletions(-)

diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 06425cc..d0d167a 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/storageamapi.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -582,25 +583,24 @@ static bool
 intorel_receive(TupleTableSlot *slot, DestReceiver *self)
 {
 	DR_intorel *myState = (DR_intorel *) self;
-	HeapTuple	tuple;
+	StorageAmRoutine *method;
+	ItemPointerData	iptr;
 
-	/*
-	 * get the heap tuple out of the tuple table slot, making sure we have a
-	 * writable copy
-	 */
-	tuple = ExecMaterializeSlot(slot);
+	ExecMaterializeSlot(slot);
+	method = myState->rel->rd_stamroutine;
 
 	/*
 	 * 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;
+
+	method->tuple_insert(myState->rel,
+						 slot,
+						 myState->output_cid,
+						 myState->hi_options,
+						 myState->bistate,
+						 &iptr);
 
 	/* We know this is a newly created relation, so there are no indexes */
 
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 9ffd91e..1fb94d7 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/storageamapi.h"
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/catalog.h"
@@ -491,19 +492,17 @@ static bool
 transientrel_receive(TupleTableSlot *slot, DestReceiver *self)
 {
 	DR_transientrel *myState = (DR_transientrel *) self;
-	HeapTuple	tuple;
+	StorageAmRoutine *method;
+	ItemPointerData iptr;
 
-	/*
-	 * get the heap tuple out of the tuple table slot, making sure we have a
-	 * writable copy
-	 */
-	tuple = ExecMaterializeSlot(slot);
+	method = myState->transientrel->rd_stamroutine;
 
-	heap_insert(myState->transientrel,
-				tuple,
-				myState->output_cid,
-				myState->hi_options,
-				myState->bistate);
+	method->tuple_insert(myState->transientrel,
+						slot,
+						myState->output_cid,
+						myState->hi_options,
+						myState->bistate,
+						&iptr);
 
 	/* We know this is a newly created relation, so there are no indexes */
 
-- 
2.7.4.windows.1

0010-compilation-fixes.patchapplication/octet-stream; name=0010-compilation-fixes.patchDownload
From 948bcd88098b4abf8356bd1dc3240d1cb80d847e Mon Sep 17 00:00:00 2001
From: Hari <haribabuk@fast.au.fujitsu.com>
Date: Fri, 9 Jun 2017 14:44:19 +1000
Subject: [PATCH 10/10] compilation fixes

---
 src/backend/access/heap/tuptoaster.c   | 1 +
 src/backend/executor/nodeModifyTable.c | 1 +
 2 files changed, 2 insertions(+)

diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index aa5a45d..99e8018 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/storageamapi.h"
 #include "access/tuptoaster.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 58827e0..0f985c2 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -40,6 +40,7 @@
 #include "access/htup_details.h"
 #include "access/storageamapi.h"
 #include "access/xact.h"
+#include "catalog/pg_am.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "executor/nodeModifyTable.h"
-- 
2.7.4.windows.1

0001-Create-Access-method-change-to-include-storage-handl.patchapplication/octet-stream; name=0001-Create-Access-method-change-to-include-storage-handl.patchDownload
From 318d7dea1f487aff1f8f8bbde101c7c6b6d84fb3 Mon Sep 17 00:00:00 2001
From: Hari <haribabuk@fast.au.fujitsu.com>
Date: Thu, 1 Jun 2017 15:09:45 +1000
Subject: [PATCH 01/10] Create Access method change 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         |  5 +++++
 src/include/catalog/pg_proc.h       |  4 ++++
 src/include/catalog/pg_type.h       |  2 ++
 6 files changed, 35 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/amcmds.c b/src/backend/commands/amcmds.c
index 7e0a9aa..33079c1 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 2822331..7b5ed84 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
@@ -5153,16 +5155,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 be79353..0a7e0a3 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 9b6dc38..1857b95 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
@@ -84,4 +85,8 @@ DATA(insert OID = 3580 (  brin		brinhandler i ));
 DESCR("block range index (BRIN) access method");
 #define BRIN_AM_OID 3580
 
+DATA(insert OID = 772 (  heapam		heapam_storage_handler s ));
+DESCR("heapam storage access method");
+#define HEAPAM_STORAGE_AM_OID 772
+
 #endif   /* PG_AM_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 460cdb9..e232006 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3826,6 +3826,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 = 336  (  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 = 337  (  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 345e916..3170bc7 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_ ));
-- 
2.7.4.windows.1

0002-Regression-suite-update-according-to-the-new-access-.patchapplication/octet-stream; name=0002-Regression-suite-update-according-to-the-new-access-.patchDownload
From 6f75ae6ebddc30d5c725176f97b1f300b52616af Mon Sep 17 00:00:00 2001
From: Hari <haribabuk@fast.au.fujitsu.com>
Date: Thu, 1 Jun 2017 15:17:26 +1000
Subject: [PATCH 02/10] Regression suite update according to the new access
 method

---
 src/test/regress/expected/opr_sanity.out | 19 ++++++++++++++++---
 src/test/regress/sql/opr_sanity.sql      | 16 +++++++++++++---
 2 files changed, 29 insertions(+), 6 deletions(-)

diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 1d7629f..c69fa4c 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1701,11 +1701,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 bf2edb5..c0cdf61 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1146,15 +1146,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.7.4.windows.1

0003-Storage-access-method-API-functions.patchapplication/octet-stream; name=0003-Storage-access-method-API-functions.patchDownload
From 8094682bf16e25873376b29ffd0f8edae9a6d735 Mon Sep 17 00:00:00 2001
From: Hari <haribabuk@fast.au.fujitsu.com>
Date: Thu, 1 Jun 2017 15:22:04 +1000
Subject: [PATCH 03/10] Storage access method API functions

---
 src/backend/access/heap/Makefile       |   3 +-
 src/backend/access/heap/storageamapi.c | 103 +++++++++++++++
 src/include/access/storageamapi.h      | 228 +++++++++++++++++++++++++++++++++
 3 files changed, 333 insertions(+), 1 deletion(-)
 create mode 100644 src/backend/access/heap/storageamapi.c
 create mode 100644 src/include/access/storageamapi.h

diff --git a/src/backend/access/heap/Makefile b/src/backend/access/heap/Makefile
index b83d496..35e7b92 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 pruneheap.o rewriteheap.o storageamapi.o \
+	syncscan.o tuptoaster.o visibilitymap.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/heap/storageamapi.c b/src/backend/access/heap/storageamapi.c
new file mode 100644
index 0000000..def2029
--- /dev/null
+++ b/src/backend/access/heap/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 0000000..87130c9
--- /dev/null
+++ b/src/include/access/storageamapi.h
@@ -0,0 +1,228 @@
+/*---------------------------------------------------------------------
+ *
+ * storageamapi.h
+ *		API for Postgres storage access methods
+ *
+ * Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * src/include/access/storageamapi.h
+ *---------------------------------------------------------------------
+ */
+#ifndef STORAGEAMAPI_H
+#define STORAGEAMAPI_H
+
+#include "access/heapam.h"
+#include "access/sdir.h"
+#include "access/skey.h"
+#include "executor/tuptable.h"
+#include "utils/relcache.h"
+#include "utils/snapshot.h"
+
+/* A physical tuple coming from a storage AM scan */
+typedef void *StorageTuple;
+
+typedef HeapScanDesc (*scan_begin_function) (Relation relation,
+												 Snapshot snapshot,
+												 int nkeys,
+												 ScanKey key);
+typedef HeapScanDesc (*scan_begin_catalog_function) (Relation relation,
+												 int nkeys,
+												 ScanKey key);
+typedef HeapScanDesc (*scan_begin_strat_function) (Relation relation,
+													   Snapshot snapshot,
+													   int nkeys,
+													   ScanKey key,
+													   bool allow_strat,
+													   bool allow_sync);
+typedef HeapScanDesc (*scan_begin_bm_function) (Relation relation,
+													Snapshot snapshot,
+													int nkeys,
+													ScanKey key);
+typedef HeapScanDesc (*scan_begin_sampling_function) (Relation relation,
+														  Snapshot snapshot,
+														  int nkeys,
+														  ScanKey key,
+														  bool allow_strat,
+														  bool allow_sync,
+														  bool allow_pagemode);
+typedef void (*scan_setlimits_function) (HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
+
+/* must return a TupleTableSlot? */
+typedef StorageTuple (*scan_getnext_function) (HeapScanDesc scan,
+											ScanDirection direction);
+
+typedef void (*scan_end_function) (HeapScanDesc scan);
+
+
+typedef void (*scan_getpage_function) (HeapScanDesc scan, BlockNumber page);
+typedef void (*scan_rescan_function) (HeapScanDesc scan, ScanKey key);
+typedef void (*scan_rescan_set_params_function) (HeapScanDesc scan, ScanKey key,
+					 bool allow_strat, bool allow_sync, bool allow_pagemode);
+typedef void (*scan_update_snapshot_function) (HeapScanDesc scan, Snapshot snapshot);
+
+typedef Oid (*tuple_insert_function) (Relation relation,
+									  TupleTableSlot *tupslot,
+									  CommandId cid,
+									  int options,
+									  BulkInsertState bistate,
+									  ItemPointer iptr);
+
+typedef HTSU_Result (*tuple_delete_function) (Relation relation,
+											  ItemPointer tid,
+											  CommandId cid,
+											  Snapshot crosscheck,
+											  bool wait,
+											  HeapUpdateFailureData *hufd);
+
+typedef HTSU_Result (*tuple_update_function) (Relation relation,
+											  ItemPointer otid,
+											  TupleTableSlot *slot,
+											  CommandId cid,
+											  Snapshot crosscheck,
+											  bool wait,
+											  HeapUpdateFailureData *hufd,
+											  LockTupleMode *lockmode,
+											  ItemPointer iptr);
+typedef bool (*tuple_fetch_function) (Relation relation,
+									ItemPointer tid,
+									Snapshot snapshot,
+									StorageTuple *tuple,
+									Buffer *userbuf,
+									bool keep_buf,
+									Relation stats_relation);
+
+typedef HTSU_Result (*tuple_lock_function) (Relation relation,
+											ItemPointer tid,
+											StorageTuple tuple,
+											CommandId cid,
+											LockTupleMode mode,
+											LockWaitPolicy wait_policy,
+											bool follow_update,
+											Buffer *buffer,
+											HeapUpdateFailureData *hufd);
+
+typedef TransactionId (*tuple_get_xmin_function) (StorageTuple tuple);
+
+typedef TransactionId (*tuple_get_updated_xid_function) (StorageTuple tuple);
+
+typedef CommandId (*tuple_get_cmin_function) (StorageTuple tuple);
+
+typedef ItemPointer (*tuple_get_itempointer_function) (StorageTuple tuple);
+
+typedef ItemPointerData (*tuple_get_ctid_function) (StorageTuple tuple);
+
+typedef void (*slot_set_tuple_oid_function) (TupleTableSlot *slot, Oid tupleoid);
+
+typedef StorageTuple (*tuple_from_datum_function) (Datum data, Oid tableoid);
+
+typedef bool (* tuple_is_heaponly_function) (StorageTuple tuple);
+
+typedef void (*slot_store_tuple_function) (TupleTableSlot *slot,
+										   StorageTuple tuple,
+										   bool shouldFree);
+typedef void (*slot_clear_tuple_function) (TupleTableSlot *slot);
+typedef void (*slot_materialize_tuple_function) (TupleTableSlot *slot);
+typedef Datum (*slot_getattr_function) (TupleTableSlot *slot,
+										int attnum, bool *isnull);
+typedef void (*slot_getallattrs_function) (TupleTableSlot *slot);
+typedef void (*slot_getsomeattrs_function) (TupleTableSlot *slot, int attnum);
+typedef bool (*slot_attisnull_function) (TupleTableSlot *slot, int attnum);
+
+typedef HeapTuple (*slot_copy_tuple_function) (TupleTableSlot *slot);
+typedef MinimalTuple (*slot_copy_min_tuple_function) (TupleTableSlot *slot);
+typedef HeapTuple (*slot_get_tuple_function) (TupleTableSlot *slot);
+typedef bool (*slot_is_physical_tuple_function) (TupleTableSlot *slot);
+
+typedef void (*slot_update_tableoid_function) (TupleTableSlot *slot, Oid tableoid);
+
+typedef void (*tuple_set_speculative_token_function) (TupleTableSlot *slot,
+								   uint32 specToken);
+typedef void (*speculative_finish_function) (Relation rel,
+											 TupleTableSlot *slot);
+typedef void (*speculative_abort_function) (Relation rel,
+											TupleTableSlot *slot);
+
+
+/*
+ * 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;
+
+	/* Operations on relation scans */
+	scan_begin_function scan_begin;
+	scan_begin_catalog_function scan_begin_catalog;
+	scan_begin_strat_function scan_begin_strat;
+	scan_begin_bm_function scan_begin_bm;
+	scan_begin_sampling_function scan_begin_sampling;
+	scan_setlimits_function scansetlimits;
+	scan_getnext_function scan_getnext;
+	scan_end_function scan_end;
+	scan_getpage_function scan_getpage;
+	scan_rescan_function scan_rescan;
+	scan_rescan_set_params_function scan_rescan_set_params;
+	scan_update_snapshot_function scan_update_snapshot;
+
+	// heap_sync_function		heap_sync;		/* heap_sync */
+	/* not implemented */
+	//	parallelscan_estimate_function	parallelscan_estimate;	/* heap_parallelscan_estimate */
+	//	parallelscan_initialize_function parallelscan_initialize;	/* heap_parallelscan_initialize */
+	//	parallelscan_begin_function	parallelscan_begin;	/* heap_beginscan_parallel */
+
+	/* Operations on physical tuples */
+	tuple_insert_function		tuple_insert;	/* heap_insert */
+	tuple_update_function		tuple_update;	/* heap_update */
+	tuple_delete_function		tuple_delete;	/* heap_delete */
+	tuple_fetch_function		tuple_fetch;	/* heap_fetch */
+	tuple_lock_function			tuple_lock;		/* heap_lock_tuple */
+
+	tuple_get_xmin_function		tuple_get_xmin;	/* HeapTupleHeaderGetXmin */
+	tuple_get_updated_xid_function tuple_get_updated_xid;
+	tuple_get_cmin_function     tuple_get_cmin; /* HeapTupleHeaderGetCmin */
+	tuple_get_itempointer_function tuple_get_itempointer;
+	tuple_get_ctid_function 	tuple_get_ctid;
+	tuple_is_heaponly_function	tuple_is_heaponly;
+	tuple_from_datum_function	tuple_from_datum;
+
+	/* Operations on TupleTableSlot */
+	slot_store_tuple_function	slot_store_tuple;
+	slot_getattr_function		slot_getattr;
+	slot_getallattrs_function	slot_getallattrs;
+	slot_getsomeattrs_function  slot_getsomeattrs;
+	slot_attisnull_function		slot_attisnull;
+	slot_clear_tuple_function	slot_clear_tuple;
+	slot_materialize_tuple_function slot_materialize_tuple;
+	slot_copy_tuple_function     slot_copy_tuple;
+	slot_get_tuple_function      slot_get_tuple;
+	slot_copy_min_tuple_function slot_copy_min_tuple;
+	slot_is_physical_tuple_function slot_is_physical_tuple;
+	slot_update_tableoid_function slot_update_tableoid;
+	slot_set_tuple_oid_function slot_set_tuple_oid;
+
+	/*
+	 * 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:
+	 */
+	tuple_set_speculative_token_function tuple_set_speculative_token;	/* HeapTupleHeaderSetSpeculativeToken */
+	speculative_finish_function	speculative_finish;
+	speculative_abort_function	speculative_abort;
+} StorageAmRoutine;
+
+extern StorageAmRoutine *GetStorageAmRoutine(Oid amhandler);
+extern StorageAmRoutine *GetStorageAmRoutineByAmId(Oid amoid);
+
+extern StorageAmRoutine *GetHeapamStorageAmRoutine(void);
+
+extern void heap_abort_speculative(Relation relation, HeapTuple tuple);
+
+#endif		/* STORAGEAMAPI_H */
-- 
2.7.4.windows.1

0004-Heap-storage-handler.patchapplication/octet-stream; name=0004-Heap-storage-handler.patchDownload
From 63ee9cfc5076c1fe0bc36b1d619f20f8133fb7a2 Mon Sep 17 00:00:00 2001
From: Hari <haribabuk@fast.au.fujitsu.com>
Date: Thu, 1 Jun 2017 15:41:01 +1000
Subject: [PATCH 04/10] Heap storage handler

Adding the handler function in the relation
and add other required function hooks to support the
heap storage
---
 src/backend/access/heap/heapam.c   | 510 +++++++++++++++++++++++++++++++++----
 src/backend/utils/cache/relcache.c | 117 ++++++++-
 src/include/access/heapam.h        |  43 +---
 src/include/catalog/pg_proc.h      |   5 +
 src/include/utils/rel.h            |  12 +
 src/include/utils/relcache.h       |   2 +
 6 files changed, 595 insertions(+), 94 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index e890e08..fe8f91f 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"
@@ -66,6 +67,7 @@
 #include "storage/smgr.h"
 #include "storage/spin.h"
 #include "storage/standby.h"
+#include "utils/builtins.h"
 #include "utils/datum.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
@@ -74,11 +76,21 @@
 #include "utils/syscache.h"
 #include "utils/tqual.h"
 
-
 /* GUC variable */
 bool		synchronize_seqscans = true;
 
-
+static void heap_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
+static HeapScanDesc heap_beginscan(Relation relation, Snapshot snapshot,
+			   int nkeys, ScanKey key);
+static HeapScanDesc heap_beginscan_catalog(Relation relation, int nkeys, ScanKey key);
+static HeapScanDesc heap_beginscan_strat(Relation relation, Snapshot snapshot,
+					 int nkeys, ScanKey key,
+					 bool allow_strat, bool allow_sync);
+static HeapScanDesc heap_beginscan_bm(Relation relation, Snapshot snapshot,
+				  int nkeys, ScanKey key);
+static HeapScanDesc heap_beginscan_sampling(Relation relation, Snapshot snapshot,
+						int nkeys, ScanKey key,
+					  bool allow_strat, bool allow_sync, bool allow_pagemode);
 static HeapScanDesc heap_beginscan_internal(Relation relation,
 						Snapshot snapshot,
 						int nkeys, ScanKey key,
@@ -89,7 +101,36 @@ static HeapScanDesc heap_beginscan_internal(Relation relation,
 						bool is_bitmapscan,
 						bool is_samplescan,
 						bool temp_snap);
+static StorageTuple heap_getnext(HeapScanDesc sscan, ScanDirection direction);
+static void heap_endscan(HeapScanDesc sscan);
+
+static void heapgetpage(HeapScanDesc scan, BlockNumber page);
+static void heap_rescan(HeapScanDesc scan, ScanKey key);
+static void heap_rescan_set_params(HeapScanDesc scan, ScanKey key,
+					 bool allow_strat, bool allow_sync, bool allow_pagemode);
+static void heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
+
+static bool heap_fetch(Relation relation,
+		   ItemPointer tid,
+		   Snapshot snapshot,
+		   StorageTuple *stuple,
+		   Buffer *userbuf,
+		   bool keep_buf,
+		   Relation stats_relation);
+
 static BlockNumber heap_parallelscan_nextpage(HeapScanDesc scan);
+
+static HTSU_Result heap_delete(Relation relation, ItemPointer tid,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			HeapUpdateFailureData *hufd);
+static HTSU_Result heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			HeapUpdateFailureData *hufd, LockTupleMode *lockmode);
+static HTSU_Result heap_lock_tuple(Relation relation, ItemPointer tid, StorageTuple stuple,
+				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
+				bool follow_updates,
+				Buffer *buffer, HeapUpdateFailureData *hufd);
+
 static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
 					TransactionId xid, CommandId cid, int options);
 static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
@@ -124,6 +165,9 @@ static bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status
 static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
 static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_modified,
 					   bool *copy);
+static void heapam_finish_speculative(Relation relation, TupleTableSlot *slot);
+static void heapam_abort_speculative(Relation relation, TupleTableSlot *slot);
+static void heap_finish_speculative(Relation relation, HeapTuple tuple);
 
 
 /*
@@ -202,6 +246,358 @@ static const int MultiXactStatusLock[MaxMultiXactStatus + 1] =
 			(MultiXactStatusLock[(status)])
 
 /* ----------------------------------------------------------------
+ *				storage AM support routines for heapam
+ * ----------------------------------------------------------------
+ */
+
+/*
+ * 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,
+				   ItemPointer iptr)
+{
+	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, iptr);
+
+	if (slot->tts_storage == NULL)
+		ExecStoreTuple(tuple, slot, InvalidBuffer, true);
+
+	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,
+				   CommandId cid, Snapshot crosscheck, bool wait,
+				   HeapUpdateFailureData *hufd, LockTupleMode *lockmode,
+				   ItemPointer iptr)
+{
+	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, iptr);
+
+	if (slot->tts_storage == NULL)
+		ExecStoreTuple(tuple, slot, InvalidBuffer, true);
+
+	return result;
+}
+
+static void
+heapam_finish_speculative(Relation relation, TupleTableSlot *slot)
+{
+	HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage;
+
+	Assert(slot->tts_speculativeToken != 0);
+
+	heap_finish_speculative(relation, stuple->hst_heaptuple);
+	slot->tts_speculativeToken = 0;
+}
+
+static void
+heapam_abort_speculative(Relation relation, TupleTableSlot *slot)
+{
+	HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage;
+
+	Assert(slot->tts_speculativeToken != 0);
+
+	heap_abort_speculative(relation, stuple->hst_heaptuple);
+	slot->tts_speculativeToken = 0;
+}
+
+static TransactionId
+heapam_get_xmin(StorageTuple tuple)
+{
+	TransactionId xmin;
+
+	xmin = HeapTupleHeaderGetXmin(((HeapTuple)tuple)->t_data);
+	return xmin;
+}
+
+static TransactionId
+heapam_get_updated_xid(StorageTuple tuple)
+{
+	TransactionId xid;
+
+	xid = HeapTupleHeaderGetUpdateXid(((HeapTuple)tuple)->t_data);
+	return xid;
+}
+
+static CommandId
+heapam_get_cmin(StorageTuple tuple)
+{
+	return HeapTupleHeaderGetCmin(((HeapTuple)tuple)->t_data);
+}
+
+static ItemPointer
+heapam_get_itempointer(StorageTuple tuple)
+{
+	return &(((HeapTuple)tuple)->t_self);
+}
+
+static ItemPointerData
+heapam_get_ctid(StorageTuple tuple)
+{
+	return ((HeapTuple)tuple)->t_data->t_ctid;
+}
+
+static bool
+heapam_tuple_is_heaopnly(StorageTuple tuple)
+{
+	return HeapTupleIsHeapOnly((HeapTuple)tuple);
+}
+
+static StorageTuple
+heapam_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;
+}
+
+static HeapTuple
+heapam_get_tuple(TupleTableSlot *slot)
+{
+	HeapamTuple *stuple = (HeapamTuple *)slot->tts_storage;
+
+	return stuple->hst_heaptuple;
+}
+
+static bool
+heapam_is_physical_tuple(TupleTableSlot *slot)
+{
+	HeapamTuple *stuple = (HeapamTuple *)slot->tts_storage;
+
+	return (stuple->hst_heaptuple != &slot->tts_minhdr);
+}
+
+static void
+slot_update_tuple_tableoid(TupleTableSlot *slot, Oid tableoid)
+{
+	HeapTuple	tuple;
+
+	tuple = ExecHeapifySlot(slot);
+	tuple->t_tableOid = tableoid;
+}
+
+static void
+slot_set_tuple_oid(TupleTableSlot *slot, Oid tupleoid)
+{
+	HeapTuple	tuple;
+
+	tuple = ExecHeapifySlot(slot);
+	HeapTupleSetOid(tuple, tupleoid);
+}
+
+static void
+heapam_slot_store_tuple(TupleTableSlot *slot, StorageTuple tuple, bool shouldFree)
+{
+	HeapamTuple *stuple;
+	MemoryContext oldcontext;
+
+	oldcontext = MemoryContextSwitchTo(slot->tts_mcxt);
+
+	stuple = (HeapamTuple *)palloc(sizeof(HeapamTuple));
+	stuple->hst_heaptuple = tuple;
+	stuple->hst_shouldFree = shouldFree;
+	stuple->hst_slow = false;
+	stuple->hst_off = 0;
+
+	MemoryContextSwitchTo(oldcontext);
+
+	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 (stuple->hst_shouldFree)
+		heap_freetuple(stuple->hst_heaptuple);
+	if (slot->tts_shouldFreeMin)
+		heap_free_minimal_tuple(slot->tts_mintuple);
+
+	slot->tts_shouldFreeMin = false;
+
+	pfree(stuple);
+	slot->tts_storage = NULL;
+}
+
+static HeapTuple
+heapam_copy_tuple(TupleTableSlot *slot)
+{
+	HeapamTuple *stuple = (HeapamTuple *)slot->tts_storage;
+	return heap_copytuple(stuple->hst_heaptuple);
+}
+
+static MinimalTuple
+minimal_tuple_from_heapam_tuple(TupleTableSlot *slot)
+{
+	HeapamTuple *stuple = (HeapamTuple *)slot->tts_storage;
+	return minimal_tuple_from_heap_tuple(stuple->hst_heaptuple);
+}
+
+static void
+heapam_tuple_set_speculative_token(TupleTableSlot *slot, uint32 specToken)
+{
+	HeapTuple	tuple;
+
+	/*
+	 * This implementation violates the contract of ExecFetchSlotTuple, which
+	 * states that the returned tuple must be treated as read-only.  Probably
+	 * the way to fix this is to set the speculative token in the slot rather
+	 * than the heaptuple directly, so that it can correctly be set on the
+	 * tuple once it is materialized.
+	 */
+	tuple = ExecFetchSlotTuple(slot);
+	HeapTupleHeaderSetSpeculativeToken(tuple->t_data, specToken);
+}
+
+Datum
+heapam_storage_handler(PG_FUNCTION_ARGS)
+{
+	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
+
+	amroutine->scan_begin = heap_beginscan;
+	amroutine->scan_begin_catalog = heap_beginscan_catalog;
+	amroutine->scan_begin_strat = heap_beginscan_strat;
+	amroutine->scan_begin_bm = heap_beginscan_bm;
+	amroutine->scan_begin_sampling = heap_beginscan_sampling;
+	amroutine->scansetlimits = heap_setscanlimits;
+	amroutine->scan_getnext = heap_getnext;
+	amroutine->scan_end = heap_endscan;
+	//amroutine->scan_getpage = ;
+	amroutine->scan_rescan = heap_rescan;
+	amroutine->scan_rescan_set_params = heap_rescan_set_params;
+	amroutine->scan_update_snapshot = heap_update_snapshot;
+
+	amroutine->tuple_fetch = heap_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->tuple_get_cmin = heapam_get_cmin;
+	amroutine->tuple_get_xmin = heapam_get_xmin;
+	amroutine->tuple_get_updated_xid = heapam_get_updated_xid;
+	amroutine->tuple_get_itempointer = heapam_get_itempointer;
+	amroutine->tuple_get_ctid = heapam_get_ctid;
+	amroutine->tuple_is_heaponly = heapam_tuple_is_heaopnly;
+	amroutine->tuple_from_datum = heapam_form_tuple_by_datum;
+
+	amroutine->slot_store_tuple = heapam_slot_store_tuple;
+	amroutine->slot_getattr = slot_getattr;
+	amroutine->slot_getallattrs = slot_getallattrs;
+	amroutine->slot_getsomeattrs = slot_getsomeattrs;
+	amroutine->slot_attisnull =  slot_attisnull;
+	amroutine->slot_clear_tuple = heapam_slot_clear_tuple;
+	amroutine->slot_copy_tuple = heapam_copy_tuple;
+	amroutine->slot_get_tuple = heapam_get_tuple;
+	amroutine->slot_copy_min_tuple = minimal_tuple_from_heapam_tuple;
+	amroutine->slot_is_physical_tuple = heapam_is_physical_tuple;
+	amroutine->slot_update_tableoid = slot_update_tuple_tableoid;
+	amroutine->slot_set_tuple_oid = slot_set_tuple_oid;
+
+
+	amroutine->tuple_set_speculative_token = heapam_tuple_set_speculative_token;
+	amroutine->speculative_finish = heapam_finish_speculative;
+	amroutine->speculative_abort = heapam_abort_speculative;
+
+	/* XXX more? */
+
+	PG_RETURN_POINTER(amroutine);
+}
+
+/* ----------------------------------------------------------------
  *						 heap support routines
  * ----------------------------------------------------------------
  */
@@ -321,9 +717,11 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
  * startBlk is the page to start at
  * numBlks is number of pages to scan (InvalidBlockNumber means "all")
  */
-void
-heap_setscanlimits(HeapScanDesc scan, BlockNumber startBlk, BlockNumber numBlks)
+static void
+heap_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
 {
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
 	Assert(!scan->rs_inited);	/* else too late to change */
 	Assert(!scan->rs_syncscan); /* else rs_startblock is significant */
 
@@ -1387,7 +1785,7 @@ heap_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
  * also allows control of whether page-mode visibility checking is used.
  * ----------------
  */
-HeapScanDesc
+static HeapScanDesc
 heap_beginscan(Relation relation, Snapshot snapshot,
 			   int nkeys, ScanKey key)
 {
@@ -1395,7 +1793,7 @@ heap_beginscan(Relation relation, Snapshot snapshot,
 								   true, true, true, false, false, false);
 }
 
-HeapScanDesc
+static HeapScanDesc
 heap_beginscan_catalog(Relation relation, int nkeys, ScanKey key)
 {
 	Oid			relid = RelationGetRelid(relation);
@@ -1405,7 +1803,7 @@ heap_beginscan_catalog(Relation relation, int nkeys, ScanKey key)
 								   true, true, true, false, false, true);
 }
 
-HeapScanDesc
+static HeapScanDesc
 heap_beginscan_strat(Relation relation, Snapshot snapshot,
 					 int nkeys, ScanKey key,
 					 bool allow_strat, bool allow_sync)
@@ -1415,7 +1813,7 @@ heap_beginscan_strat(Relation relation, Snapshot snapshot,
 								   false, false, false);
 }
 
-HeapScanDesc
+static HeapScanDesc
 heap_beginscan_bm(Relation relation, Snapshot snapshot,
 				  int nkeys, ScanKey key)
 {
@@ -1423,7 +1821,7 @@ heap_beginscan_bm(Relation relation, Snapshot snapshot,
 								   false, false, true, true, false, false);
 }
 
-HeapScanDesc
+static HeapScanDesc
 heap_beginscan_sampling(Relation relation, Snapshot snapshot,
 						int nkeys, ScanKey key,
 					  bool allow_strat, bool allow_sync, bool allow_pagemode)
@@ -1574,7 +1972,7 @@ heap_rescan_set_params(HeapScanDesc scan, ScanKey key,
  *		Check handling if reldesc caching.
  * ----------------
  */
-void
+static void
 heap_endscan(HeapScanDesc scan)
 {
 	/* Note: no locking manipulations needed */
@@ -1790,9 +2188,11 @@ heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
 #endif   /* !defined(HEAPDEBUGALL) */
 
 
-HeapTuple
-heap_getnext(HeapScanDesc scan, ScanDirection direction)
+static StorageTuple
+heap_getnext(HeapScanDesc sscan, ScanDirection direction)
 {
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
 	/* Note: no locking manipulations needed */
 
 	HEAPDEBUG_1;				/* heap_getnext( info ) */
@@ -1858,20 +2258,21 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
  * truncate off the destination page without having killed the referencing
  * tuple first), but the item number might well not be good.
  */
-bool
+static bool
 heap_fetch(Relation relation,
+		   ItemPointer tid,
 		   Snapshot snapshot,
-		   HeapTuple tuple,
+		   StorageTuple *stuple,
 		   Buffer *userbuf,
 		   bool keep_buf,
 		   Relation stats_relation)
 {
-	ItemPointer tid = &(tuple->t_self);
 	ItemId		lp;
 	Buffer		buffer;
 	Page		page;
 	OffsetNumber offnum;
 	bool		valid;
+	HeapTupleData tuple;
 
 	/*
 	 * Fetch and pin the appropriate page of the relation.
@@ -1900,7 +2301,7 @@ heap_fetch(Relation relation,
 			ReleaseBuffer(buffer);
 			*userbuf = InvalidBuffer;
 		}
-		tuple->t_data = NULL;
+		*stuple = NULL;
 		return false;
 	}
 
@@ -1922,26 +2323,27 @@ heap_fetch(Relation relation,
 			ReleaseBuffer(buffer);
 			*userbuf = InvalidBuffer;
 		}
-		tuple->t_data = NULL;
+		*stuple = NULL;
 		return false;
 	}
 
 	/*
-	 * fill in *tuple fields
+	 * fill in tuple fields and place it in stuple
 	 */
-	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);
 
 	/*
 	 * check time qualification of tuple, then release lock
 	 */
-	valid = HeapTupleSatisfiesVisibility(tuple, snapshot, buffer);
+	valid = HeapTupleSatisfiesVisibility(&tuple, snapshot, buffer);
 
 	if (valid)
-		PredicateLockTuple(relation, tuple, snapshot);
+		PredicateLockTuple(relation, &tuple, snapshot);
 
-	CheckForSerializableConflictOut(valid, relation, tuple, buffer, snapshot);
+	CheckForSerializableConflictOut(valid, relation, &tuple, buffer, snapshot);
 
 	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 
@@ -1957,6 +2359,7 @@ heap_fetch(Relation relation,
 		if (stats_relation != NULL)
 			pgstat_count_heap_fetch(stats_relation);
 
+		*stuple = heap_copytuple(&tuple);
 		return true;
 	}
 
@@ -3007,7 +3410,7 @@ xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask)
  * cannot obtain cmax from a combocid generated by another transaction).
  * See comments for struct HeapUpdateFailureData for additional info.
  */
-HTSU_Result
+static HTSU_Result
 heap_delete(Relation relation, ItemPointer tid,
 			CommandId cid, Snapshot crosscheck, bool wait,
 			HeapUpdateFailureData *hufd)
@@ -3458,7 +3861,7 @@ simple_heap_delete(Relation relation, ItemPointer tid)
  * cannot obtain cmax from a combocid generated by another transaction).
  * See comments for struct HeapUpdateFailureData for additional info.
  */
-HTSU_Result
+static HTSU_Result
 heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 			CommandId cid, Snapshot crosscheck, bool wait,
 			HeapUpdateFailureData *hufd, LockTupleMode *lockmode)
@@ -4536,14 +4939,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, HeapTuple tuple,
+static HTSU_Result
+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;
@@ -4556,6 +4958,9 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
 	bool		first_time = true;
 	bool		have_tuple_lock = false;
 	bool		cleared_all_frozen = false;
+	HeapTuple	tuple = (HeapTuple)stuple;
+
+	Assert(tuple != NULL);
 
 	*buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 	block = ItemPointerGetBlockNumber(tid);
@@ -5621,7 +6026,7 @@ heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid,
 {
 	HTSU_Result result;
 	ItemPointerData tupid;
-	HeapTupleData mytup;
+	HeapTuple 	mytup;
 	Buffer		buf;
 	uint16		new_infomask,
 				new_infomask2,
@@ -5641,9 +6046,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, (StorageTuple *)&mytup, &buf, false, NULL))
 		{
 			/*
 			 * if we fail to find the updated version of the tuple, it's
@@ -5689,7 +6093,7 @@ l4:
 		 * end of the chain, we're done, so return success.
 		 */
 		if (TransactionIdIsValid(priorXmax) &&
-			!TransactionIdEquals(HeapTupleHeaderGetXmin(mytup.t_data),
+			!TransactionIdEquals(HeapTupleHeaderGetXmin(mytup->t_data),
 								 priorXmax))
 		{
 			result = HeapTupleMayBeUpdated;
@@ -5701,15 +6105,15 @@ l4:
 		 * (sub)transaction, then we already locked the last live one in the
 		 * chain, thus we're done, so return success.
 		 */
-		if (TransactionIdDidAbort(HeapTupleHeaderGetXmin(mytup.t_data)))
+		if (TransactionIdDidAbort(HeapTupleHeaderGetXmin(mytup->t_data)))
 		{
 			UnlockReleaseBuffer(buf);
 			return HeapTupleMayBeUpdated;
 		}
 
-		old_infomask = mytup.t_data->t_infomask;
-		old_infomask2 = mytup.t_data->t_infomask2;
-		xmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
+		old_infomask = mytup->t_data->t_infomask;
+		old_infomask2 = mytup->t_data->t_infomask2;
+		xmax = HeapTupleHeaderGetRawXmax(mytup->t_data);
 
 		/*
 		 * If this tuple version has been updated or locked by some concurrent
@@ -5722,7 +6126,7 @@ l4:
 			TransactionId rawxmax;
 			bool		needwait;
 
-			rawxmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
+			rawxmax = HeapTupleHeaderGetRawXmax(mytup->t_data);
 			if (old_infomask & HEAP_XMAX_IS_MULTI)
 			{
 				int			nmembers;
@@ -5752,7 +6156,7 @@ l4:
 					{
 						LockBuffer(buf, BUFFER_LOCK_UNLOCK);
 						XactLockTableWait(members[i].xid, rel,
-										  &mytup.t_self,
+										  &mytup->t_self,
 										  XLTW_LockUpdated);
 						pfree(members);
 						goto l4;
@@ -5811,7 +6215,7 @@ l4:
 				if (needwait)
 				{
 					LockBuffer(buf, BUFFER_LOCK_UNLOCK);
-					XactLockTableWait(rawxmax, rel, &mytup.t_self,
+					XactLockTableWait(rawxmax, rel, &mytup->t_self,
 									  XLTW_LockUpdated);
 					goto l4;
 				}
@@ -5823,7 +6227,7 @@ l4:
 		}
 
 		/* compute the new Xmax and infomask values for the tuple ... */
-		compute_new_xmax_infomask(xmax, old_infomask, mytup.t_data->t_infomask2,
+		compute_new_xmax_infomask(xmax, old_infomask, mytup->t_data->t_infomask2,
 								  xid, mode, false,
 								  &new_xmax, &new_infomask, &new_infomask2);
 
@@ -5835,11 +6239,11 @@ l4:
 		START_CRIT_SECTION();
 
 		/* ... and set them */
-		HeapTupleHeaderSetXmax(mytup.t_data, new_xmax);
-		mytup.t_data->t_infomask &= ~HEAP_XMAX_BITS;
-		mytup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
-		mytup.t_data->t_infomask |= new_infomask;
-		mytup.t_data->t_infomask2 |= new_infomask2;
+		HeapTupleHeaderSetXmax(mytup->t_data, new_xmax);
+		mytup->t_data->t_infomask &= ~HEAP_XMAX_BITS;
+		mytup->t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+		mytup->t_data->t_infomask |= new_infomask;
+		mytup->t_data->t_infomask2 |= new_infomask2;
 
 		MarkBufferDirty(buf);
 
@@ -5853,7 +6257,7 @@ l4:
 			XLogBeginInsert();
 			XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
 
-			xlrec.offnum = ItemPointerGetOffsetNumber(&mytup.t_self);
+			xlrec.offnum = ItemPointerGetOffsetNumber(&mytup->t_self);
 			xlrec.xmax = new_xmax;
 			xlrec.infobits_set = compute_infobits(new_infomask, new_infomask2);
 			xlrec.flags =
@@ -5869,17 +6273,17 @@ l4:
 		END_CRIT_SECTION();
 
 		/* if we find the end of update chain, we're done. */
-		if (mytup.t_data->t_infomask & HEAP_XMAX_INVALID ||
-			ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid) ||
-			HeapTupleHeaderIsOnlyLocked(mytup.t_data))
+		if (mytup->t_data->t_infomask & HEAP_XMAX_INVALID ||
+			ItemPointerEquals(&mytup->t_self, &mytup->t_data->t_ctid) ||
+			HeapTupleHeaderIsOnlyLocked(mytup->t_data))
 		{
 			result = HeapTupleMayBeUpdated;
 			goto out_locked;
 		}
 
 		/* tail recursion */
-		priorXmax = HeapTupleHeaderGetUpdateXid(mytup.t_data);
-		ItemPointerCopy(&(mytup.t_data->t_ctid), &tupid);
+		priorXmax = HeapTupleHeaderGetUpdateXid(mytup->t_data);
+		ItemPointerCopy(&(mytup->t_data->t_ctid), &tupid);
 		UnlockReleaseBuffer(buf);
 		if (vmbuffer != InvalidBuffer)
 			ReleaseBuffer(vmbuffer);
@@ -5959,7 +6363,7 @@ heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid,
  * but clearing the token at completion isn't very expensive either.
  * An explicit confirmation WAL record also makes logical decoding simpler.
  */
-void
+static void
 heap_finish_speculative(Relation relation, HeapTuple tuple)
 {
 	Buffer		buffer;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index c2e8361..98dcc85 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -34,6 +34,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"
@@ -1360,10 +1361,26 @@ 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);
@@ -1860,6 +1877,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
@@ -2019,6 +2101,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
 	 */
 	if (IsBootstrapProcessingMode())
@@ -2346,6 +2433,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)
@@ -3357,6 +3446,13 @@ 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.
 	 *
@@ -3878,6 +3974,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);
 
@@ -5593,6 +5701,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/access/heapam.h b/src/include/access/heapam.h
index 7e85510..962291e 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -100,41 +100,21 @@ extern Relation heap_openrv_extended(const RangeVar *relation,
 typedef struct HeapScanDescData *HeapScanDesc;
 typedef struct ParallelHeapScanDescData *ParallelHeapScanDesc;
 
+/* struct definitions appear in storageamapi.h */
+typedef void *StorageScanDesc;
+typedef void *StorageTuple;
+
 /*
  * HeapScanIsValid
  *		True iff the heap scan is valid.
  */
 #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);
-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_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 Size heap_parallelscan_estimate(Snapshot snapshot);
 extern void heap_parallelscan_initialize(ParallelHeapScanDesc target,
 							 Relation relation, Snapshot snapshot);
 extern HeapScanDesc heap_beginscan_parallel(Relation, ParallelHeapScanDesc);
 
-extern bool heap_fetch(Relation relation, Snapshot snapshot,
-		   HeapTuple tuple, Buffer *userbuf, bool keep_buf,
-		   Relation stats_relation);
 extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
 					   Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
 					   bool *all_dead, bool first_call);
@@ -153,19 +133,6 @@ extern Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 			int options, BulkInsertState bistate);
 extern void heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
 				  CommandId cid, int options, BulkInsertState bistate);
-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 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,
-				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);
@@ -179,7 +146,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);
+
 
 /* in heap/pruneheap.c */
 extern void heap_page_prune_opt(Relation relation, Buffer buffer);
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index e232006..f6b899f 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -549,6 +549,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 = 425 (  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	425
+
 /* 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/utils/rel.h b/src/include/utils/rel.h
index 8476896..c187c65 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -161,6 +161,12 @@ typedef struct RelationData
 	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)
 	 *
 	 * Note: only default support procs for each opclass are cached, namely
@@ -429,6 +435,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 81af3ae..807345a 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -71,6 +71,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.7.4.windows.1

#17Robert Haas
robertmhaas@gmail.com
In reply to: Haribabu Kommi (#16)
Re: Pluggable storage

On Mon, Jun 12, 2017 at 9:50 PM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

Open Items:

1. The BitmapHeapScan and TableSampleScan are tightly coupled with
HeapTuple and HeapScanDesc, So these scans are directly operating
on those structures and providing the result.

These scan types may not be applicable to different storage formats.
So how to handle them?

I think that BitmapHeapScan, at least, is applicable to any table AM
that has TIDs. It seems to me that in general we can imagine three
kinds of table AMs:

1. Table AMs where a tuple can be efficiently located by a real TID.
By a real TID, I mean that the block number part is really a block
number and the item ID is really a location within the block. These
are necessarily quite similar to our current heap, but they can change
the tuple format and page format to some degree, and it seems like in
many cases it should be possible to plug them into our existing index
AMs without too much heartache. Both index scans and bitmap index
scans ought to work.

2. Table AMs where a tuple has some other kind of locator. For
example, imagine an index-organized table where the locator is the
primary key, which is a bit like what Alvaro had in mind for indirect
indexes. If the locator is 6 bytes or less, it could potentially be
jammed into a TID, but I don't think that's a great idea. For things
like int8 or numeric, it won't work at all. Even for other things,
it's going to cause problems because the bit patterns won't be what
the code is expecting; e.g. bitmap scans care about the structure of
the TID, not just how many bits it is. (Due credit: Somebody, maybe
Alvaro, pointed out this problem before, at PGCon.) For these kinds
of tables, larger modifications to the index AMs are likely to be
necessary, at least if we want a really general solution, or maybe we
should have separate index AMs - e.g. btree for traditional TID-based
heaps, and generic_btree or indirect_btree or key_btree or whatever
for heaps with some other kind of locator. It's not too hard to see
how to make index scans work with this sort of structure but it's very
unclear to me whether, or how, bitmap scans can be made to work.

3. Table AMs where a tuple doesn't really have a locator at all. In
these cases, we can't support any sort of index AM at all. When the
table is queried, there's really nothing the core system can do except
ask the table AM for a full scan, supply the quals, and hope the table
AM has some sort of smarts that enable it to optimize somehow. For
example, you can imagine converting cstore_fdw into a table AM of this
sort - ORC has a sort of inbuilt BRIN-like indexing that allows whole
chunks to be proven uninteresting and skipped. (You could use chunk
number + offset to turn this into a table AM of the previous type if
you wanted to support secondary indexes; not sure if that'd be useful,
but it'd certainly be harder.)

I'm more interested in #1 than in #3, and more interested in #3 than
#2, but other people may have different priorities.

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#18Michael Paquier
michael.paquier@gmail.com
In reply to: Robert Haas (#17)
Re: Pluggable storage

On Thu, Jun 22, 2017 at 4:47 AM, Robert Haas <robertmhaas@gmail.com> wrote:

I think that BitmapHeapScan, at least, is applicable to any table AM
that has TIDs. It seems to me that in general we can imagine three
kinds of table AMs:

1. Table AMs where a tuple can be efficiently located by a real TID.
By a real TID, I mean that the block number part is really a block
number and the item ID is really a location within the block. These
are necessarily quite similar to our current heap, but they can change
the tuple format and page format to some degree, and it seems like in
many cases it should be possible to plug them into our existing index
AMs without too much heartache. Both index scans and bitmap index
scans ought to work.

2. Table AMs where a tuple has some other kind of locator. For
example, imagine an index-organized table where the locator is the
primary key, which is a bit like what Alvaro had in mind for indirect
indexes. If the locator is 6 bytes or less, it could potentially be
jammed into a TID, but I don't think that's a great idea. For things
like int8 or numeric, it won't work at all. Even for other things,
it's going to cause problems because the bit patterns won't be what
the code is expecting; e.g. bitmap scans care about the structure of
the TID, not just how many bits it is. (Due credit: Somebody, maybe
Alvaro, pointed out this problem before, at PGCon.) For these kinds
of tables, larger modifications to the index AMs are likely to be
necessary, at least if we want a really general solution, or maybe we
should have separate index AMs - e.g. btree for traditional TID-based
heaps, and generic_btree or indirect_btree or key_btree or whatever
for heaps with some other kind of locator. It's not too hard to see
how to make index scans work with this sort of structure but it's very
unclear to me whether, or how, bitmap scans can be made to work.

3. Table AMs where a tuple doesn't really have a locator at all. In
these cases, we can't support any sort of index AM at all. When the
table is queried, there's really nothing the core system can do except
ask the table AM for a full scan, supply the quals, and hope the table
AM has some sort of smarts that enable it to optimize somehow. For
example, you can imagine converting cstore_fdw into a table AM of this
sort - ORC has a sort of inbuilt BRIN-like indexing that allows whole
chunks to be proven uninteresting and skipped. (You could use chunk
number + offset to turn this into a table AM of the previous type if
you wanted to support secondary indexes; not sure if that'd be useful,
but it'd certainly be harder.)

I'm more interested in #1 than in #3, and more interested in #3 than
#2, but other people may have different priorities.

Putting that in a couple of words.
1. Table AM with a 6-byte TID.
2. Table AM with a custom locator format, which could be TID-like.
3. Table AM with no locators.

Getting into having #1 first to work out would already be really
useful for users. My take on the matter is that being able to plug in
in-core index AMs directly into a table AM #1 is more useful in the
long term, as it is possible for multiple table AMs to use the same
kind of index AM which is designed nicely enough. So the index AM
logic basically does not need to be duplicated across multiple table
AMs. #3 implies that the index AM logic is implemented in the table
AM. Not saying that it is not useful, but it does not feel natural to
have the planner request for a sequential scan, just to have the table
AM secretly do some kind of index/skipping scan.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#19Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Michael Paquier (#18)
Re: Pluggable storage

On 2017/06/22 10:01, Michael Paquier wrote:

#3 implies that the index AM logic is implemented in the table
AM. Not saying that it is not useful, but it does not feel natural to
have the planner request for a sequential scan, just to have the table
AM secretly do some kind of index/skipping scan.

I had read a relevant comment on a pluggable storage thread awhile back
[1]: /messages/by-id/CA+TgmoY3LXVUPQVdZW70XKp5PsXffO82pXXt=beegcV+=RsQgg@mail.gmail.com
some intelligence, via some API, from the heap storage implementation
about the latter's access cost characteristics. The storage should
provide accurate-enough cost information to the planner when such a
request is made by, say, cost_seqscan(), so that the planner can make
appropriate choice. If two tables containing the same number of rows (and
the same size in bytes, perhaps) use different storage implementations,
then, planner's cost parameters remaining same, cost_seqscan() will end up
calculating different costs for the two tables. Perhaps, SeqScan would be
chosen for one table but not the another based on that.

Thanks,
Amit

[1]: /messages/by-id/CA+TgmoY3LXVUPQVdZW70XKp5PsXffO82pXXt=beegcV+=RsQgg@mail.gmail.com
/messages/by-id/CA+TgmoY3LXVUPQVdZW70XKp5PsXffO82pXXt=beegcV+=RsQgg@mail.gmail.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#20Michael Paquier
michael.paquier@gmail.com
In reply to: Amit Langote (#19)
Re: Pluggable storage

On Thu, Jun 22, 2017 at 11:12 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

On 2017/06/22 10:01, Michael Paquier wrote:

#3 implies that the index AM logic is implemented in the table
AM. Not saying that it is not useful, but it does not feel natural to
have the planner request for a sequential scan, just to have the table
AM secretly do some kind of index/skipping scan.

I had read a relevant comment on a pluggable storage thread awhile back
[1]. In short, the comment was that the planner should be able to get
some intelligence, via some API, from the heap storage implementation
about the latter's access cost characteristics. The storage should
provide accurate-enough cost information to the planner when such a
request is made by, say, cost_seqscan(), so that the planner can make
appropriate choice. If two tables containing the same number of rows (and
the same size in bytes, perhaps) use different storage implementations,
then, planner's cost parameters remaining same, cost_seqscan() will end up
calculating different costs for the two tables. Perhaps, SeqScan would be
chosen for one table but not the another based on that.

Yeah, I agree that the costing part needs some clear attention and
thoughts, and the gains are absolutely huge with the correct
interface. That could be done in a later step though.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#21Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Haribabu Kommi (#16)
Re: Pluggable storage

On Tue, Jun 13, 2017 at 4:50 AM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:

On Fri, Oct 14, 2016 at 7:26 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

I have sent the partial patch I have to Hari Babu Kommi. We expect that
he will be able to further this goal some more.

Thanks Alvaro for sharing your development patch.

Most of the patch design is same as described by Alvaro in the first mail
[1].
I will detail the modifications, pending items and open items (needs
discussion)
to implement proper pluggable storage.

Here I attached WIP patches to support pluggable storage. The patch series
are may not work individually. Still so many things are under development.
These patches are just to share the approach of the current development.

Some notable changes that I did to make the patch work:

1. Added storageam handler to the slot, this is because not all places
the relation is not available in handy.
2. Retained the minimal Tuple in the slot, as this is used in HASH join.
As per the first version, I feel it is fine to allow creating HeapTuple
format data.

Thanks everyone for sharing their ideas in the developer's unconference at
PGCon Ottawa.

Pending items:

1. Replacement of Tuple with slot in Trigger functionality
2. Replacement of Tuple with Slot from storage handler functions.
3. Remove/minimize the use of HeapTuple as a Datum.
4. Replace all references of HeapScanDesc with StorageScanDesc
5. Planner changes to consider the relation storage during the planning.
6. Any planner changes based on the discussion of open items?
7. some Executor changes to consider the storage advantages?

Open Items:

1. The BitmapHeapScan and TableSampleScan are tightly coupled with
HeapTuple and HeapScanDesc, So these scans are directly operating
on those structures and providing the result.

What about vacuum? I see vacuum is untouched in the patchset and it is not
mentioned in this discussion.
Do you plan to override low-level function like heap_page_prune(),
lazy_vacuum_page() etc., but preserve high-level logic of vacuum?
Or do you plan to let pluggable storage implement its own high-level vacuum
algorithm?

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

#22Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Michael Paquier (#18)
Re: Pluggable storage

On Thu, Jun 22, 2017 at 4:01 AM, Michael Paquier <michael.paquier@gmail.com>
wrote:

Putting that in a couple of words.
1. Table AM with a 6-byte TID.
2. Table AM with a custom locator format, which could be TID-like.
3. Table AM with no locators.

Getting into having #1 first to work out would already be really
useful for users.

What exactly would be useful for *users*? Any kind of API itself is
completely useless for users, because they are users, not developers.
Storage API could be useful for developers to implement storage AMs whose
in turn could be useful for users. Then while saying that #1 is useful for
users, it would be nice to keep in mind particular storage AMs which can be
implemented using #1.

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

#23Robert Haas
robertmhaas@gmail.com
In reply to: Alexander Korotkov (#22)
Re: Pluggable storage

On Thu, Jun 22, 2017 at 8:32 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Thu, Jun 22, 2017 at 4:01 AM, Michael Paquier <michael.paquier@gmail.com>
wrote:

Putting that in a couple of words.
1. Table AM with a 6-byte TID.
2. Table AM with a custom locator format, which could be TID-like.
3. Table AM with no locators.

Getting into having #1 first to work out would already be really
useful for users.

What exactly would be useful for *users*? Any kind of API itself is
completely useless for users, because they are users, not developers.
Storage API could be useful for developers to implement storage AMs whose in
turn could be useful for users.

What's your point? I assume that is what Michael meant.

Then while saying that #1 is useful for
users, it would be nice to keep in mind particular storage AMs which can be
implemented using #1.

I don't think anybody's arguing with that.

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#24Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Robert Haas (#17)
Re: Pluggable storage

On Wed, Jun 21, 2017 at 10:47 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Jun 12, 2017 at 9:50 PM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

Open Items:

1. The BitmapHeapScan and TableSampleScan are tightly coupled with
HeapTuple and HeapScanDesc, So these scans are directly operating
on those structures and providing the result.

These scan types may not be applicable to different storage formats.
So how to handle them?

I think that BitmapHeapScan, at least, is applicable to any table AM
that has TIDs. It seems to me that in general we can imagine three
kinds of table AMs:

1. Table AMs where a tuple can be efficiently located by a real TID.
By a real TID, I mean that the block number part is really a block
number and the item ID is really a location within the block. These
are necessarily quite similar to our current heap, but they can change
the tuple format and page format to some degree, and it seems like in
many cases it should be possible to plug them into our existing index
AMs without too much heartache. Both index scans and bitmap index
scans ought to work.

If #1 is only about changing tuple and page formats, then could be much
simpler than the patch upthread? We can implement "page format access
methods" with routines for insertion, update, pruning and deletion of
tuples *in particular page*. There is no need to redefine high-level logic
for scanning heap, doing updates and so on...

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

#25Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Robert Haas (#23)
Re: Pluggable storage

On Thu, Jun 22, 2017 at 5:27 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Jun 22, 2017 at 8:32 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Thu, Jun 22, 2017 at 4:01 AM, Michael Paquier <

michael.paquier@gmail.com>

wrote:

Putting that in a couple of words.
1. Table AM with a 6-byte TID.
2. Table AM with a custom locator format, which could be TID-like.
3. Table AM with no locators.

Getting into having #1 first to work out would already be really
useful for users.

What exactly would be useful for *users*? Any kind of API itself is
completely useless for users, because they are users, not developers.
Storage API could be useful for developers to implement storage AMs

whose in

turn could be useful for users.

What's your point? I assume that is what Michael meant.

TBH, I don't understand what particular enchantments do we expect from
having #1.
This is why it's hard for me to say if #1 is good idea. It's also hard for
me to say if patch upthread is right way of implementing #1.
But, I have gut feeling that if even #1 is good idea itself, it's
definitely not what users expect from "pluggable storages".
From user side, it would be normal to expect that "pluggable storage" could
be index-organized table where index could be say LSM...

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

#26Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Robert Haas (#17)
Re: Pluggable storage

Hi,

On 6/21/17 9:47 PM, Robert Haas wrote:
...

like int8 or numeric, it won't work at all. Even for other things,
it's going to cause problems because the bit patterns won't be what
the code is expecting; e.g. bitmap scans care about the structure of
the TID, not just how many bits it is. (Due credit: Somebody, maybe
Alvaro, pointed out this problem before, at PGCon.)

Can you elaborate a bit more about this TID bit pattern issues? I do
remember that not all TIDs are valid due to safeguards on individual
fields, like for example

Assert(iptr->ip_posid < (1 << MaxHeapTuplesPerPageBits))

But perhaps there are some other issues?

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#27Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Alexander Korotkov (#25)
Re: Pluggable storage

Hi,

On 6/22/17 4:36 PM, Alexander Korotkov wrote:

On Thu, Jun 22, 2017 at 5:27 PM, Robert Haas <robertmhaas@gmail.com
<mailto:robertmhaas@gmail.com>> wrote:

On Thu, Jun 22, 2017 at 8:32 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru <mailto:a.korotkov@postgrespro.ru>> wrote:

On Thu, Jun 22, 2017 at 4:01 AM, Michael Paquier <michael.paquier@gmail.com <mailto:michael.paquier@gmail.com>>
wrote:

Putting that in a couple of words.
1. Table AM with a 6-byte TID.
2. Table AM with a custom locator format, which could be TID-like.
3. Table AM with no locators.

Getting into having #1 first to work out would already be really
useful for users.

What exactly would be useful for *users*? Any kind of API itself is
completely useless for users, because they are users, not developers.
Storage API could be useful for developers to implement storage AMs whose in
turn could be useful for users.

What's your point? I assume that is what Michael meant.

TBH, I don't understand what particular enchantments do we expect from
having #1.

I'd say that's one of the things we're trying to figure out in this
thread. Does it make sense to go with #1 in v1 of the patch, or do we
have to implement #2 or #3 to get some real benefit for the users?

This is why it's hard for me to say if #1 is good idea. It's also hard
for me to say if patch upthread is right way of implementing #1.
But, I have gut feeling that if even #1 is good idea itself, it's
definitely not what users expect from "pluggable storages".

The question is "why" do you think that. What features do you expect
from pluggable storage API that would be impossible to implement with
option #1?

I'm not trying to be annoying or anything like that - I don't know the
answer and discussing those things is exactly why this thread exists.

I do agree #1 has limitations, and that it'd be great to get API that
supports all kinds of pluggable storage implementations. But I guess
that'll take some time.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#28Michael Paquier
michael.paquier@gmail.com
In reply to: Robert Haas (#23)
Re: Pluggable storage

On Thu, Jun 22, 2017 at 11:27 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Jun 22, 2017 at 8:32 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Thu, Jun 22, 2017 at 4:01 AM, Michael Paquier <michael.paquier@gmail.com>
wrote:

Putting that in a couple of words.
1. Table AM with a 6-byte TID.
2. Table AM with a custom locator format, which could be TID-like.
3. Table AM with no locators.

Getting into having #1 first to work out would already be really
useful for users.

What exactly would be useful for *users*? Any kind of API itself is
completely useless for users, because they are users, not developers.
Storage API could be useful for developers to implement storage AMs whose in
turn could be useful for users.

What's your point? I assume that is what Michael meant.

Sorry for the confusion. I implied here that users are the ones
developing modules.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#29Teodor Sigaev
teodor@sigaev.ru
In reply to: Michael Paquier (#18)
Re: Pluggable storage

1. Table AM with a 6-byte TID.
2. Table AM with a custom locator format, which could be TID-like.
3. Table AM with no locators.

Currently TID has its own type in system catalog. Seems, it's possible that
storage claims type of TID which it uses. Then, AM could claim it too, so the
core based on that information could solve the question about AM-storage
compatibility. Storage could also claim that it hasn't TID type at all so it
couldn't be used with any access method, use case: compressed column oriented
storage.

As I remeber, only GIN depends on TID format, other indexes use it as opaque
type. Except, at least, btree and GiST - they believ that internal pointers are
the same as outer (to heap)

Another dubious part - BitmapScan.

--
Teodor Sigaev E-mail: teodor@sigaev.ru
WWW: http://www.sigaev.ru/

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#30Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Teodor Sigaev (#29)
Re: Pluggable storage

Hi,

On 6/23/17 10:38 AM, Teodor Sigaev wrote:

1. Table AM with a 6-byte TID.
2. Table AM with a custom locator format, which could be TID-like.
3. Table AM with no locators.

Currently TID has its own type in system catalog. Seems, it's possible
that storage claims type of TID which it uses. Then, AM could claim it
too, so the core based on that information could solve the question
about AM-storage compatibility. Storage could also claim that it hasn't
TID type at all so it couldn't be used with any access method, use case:
compressed column oriented storage.

Isn't the fact that TID is an existing type defined in system catalogs
is a fairly insignificant detail? I mean, we could just as easily define
a new 64-bit locator data type, and use that instead, for example.

The main issue here is that we assume things about the TID contents,
i.e. that it contains page/offset etc. And Bitmap nodes rely on that, to
some extent - e.g. when prefetching data.

As I remeber, only GIN depends on TID format, other indexes use it as
opaque type. Except, at least, btree and GiST - they believe that
internal pointers are the same as outer (to heap)

I think you're probably right - GIN does compress the posting lists by
exploiting the TID redundancy (that it's page/offset structure), and I
don't think there are other index AMs doing that.

But I'm not sure we can simply rely on that - it's possible people will
try to improve other index types (e.g. by adding similar compression to
other index types). Moreover we now support extensions defining custom
index AMs, and we have no clue what people may do in those.

So this would clearly require some sort of flag for each index AM.

Another dubious part - BitmapScan.

It would be really great if you could explain why BitmapScans are
dubious, instead of just labeling them as dubious. (I guess you mean
Bitmap Heap Scans, right?)

I see no conceptual issue with bitmap scans on arbitrary locator types,
as long as there's sufficient locality information encoded in the value.
What I mean by that is that for any two locator values A and B:

(1) if (A < B) then (A is stored before B)

(2) if (A is close to B) then (A is stored close to B)

Without these features it's probably futile to try to do bitmap scans,
because the bitmap would not result in mostly sequential access pattern
and things like prefetch would not be very efficient, I think.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#31Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tomas Vondra (#30)
Re: Pluggable storage

Tomas Vondra <tomas.vondra@2ndquadrant.com> writes:

It would be really great if you could explain why BitmapScans are
dubious, instead of just labeling them as dubious. (I guess you mean
Bitmap Heap Scans, right?)

The two things I'm aware of are (a) the assumption you noted, that
fetching tuples in TID sort order is a reasonably efficient thing,
and (b) the "offset" part of a TID can't exceed MaxHeapTuplesPerPage
--- see data structure in tidbitmap.c.  The latter issue means that
you don't really have a full six bytes to play with in a TID, only
about five.

I don't think (b) would be terribly hard to fix if we had a motivation to,
but I wonder whether there aren't other places that also know this about
TIDs.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#32Julien Rouhaud
julien.rouhaud@dalibo.com
In reply to: Tomas Vondra (#30)
Re: Pluggable storage

On 23/06/2017 16:07, Tomas Vondra wrote:

I think you're probably right - GIN does compress the posting lists by
exploiting the TID redundancy (that it's page/offset structure), and I
don't think there are other index AMs doing that.

But I'm not sure we can simply rely on that - it's possible people will
try to improve other index types (e.g. by adding similar compression to
other index types).

That reminds me
/messages/by-id/55E4051B.7020209@postgrespro.ru
where Anastasia proposed something similar.

--
Julien Rouhaud
http://dalibo.com - http://dalibo.org

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#33Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Julien Rouhaud (#32)
Re: Pluggable storage

Hackers,

I see that design question for PostgreSQL pluggable storages is very hard.
BTW, I think it worth analyzing existing use-cases of pluggable storages.
I think that the most famous DBMS with pluggable storage API is MySQL.
This why I decided to start with it. I've added MySQL/MariaDB section on
wiki page.
https://wiki.postgresql.org/wiki/Future_of_storage#MySQL.2FMariaDB
It appears that significant part of MySQL storage engines are misuses.
MySQL lacks of various features like FDWs or writable views and so on.
This is why developers created a lot of pluggable storages for that
purposes. We definitely don't want something like this in PostgreSQL now.
I created special resume column where I expressed whether it would be nice
to have something like this table engine in PostgreSQL.

Any comments and additions are welcome. I'm planning to write similar
table for MongoDB.

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

#34Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Alexander Korotkov (#33)
Re: Pluggable storage

Hi,

On 06/26/2017 05:18 PM, Alexander Korotkov wrote:

Hackers,

I see that design question for PostgreSQL pluggable storages is very
hard.

IMHO it's mostly expected to be hard.

Firstly, PostgreSQL is a mature product with many advanced features, and
reworking a low-level feature without breaking something on top of it is
hard by definition.

Secondly, project policies and code quality requirements set the bar
very high too, I think.

BTW, I think it worth analyzing existing use-cases of pluggable
storages. I think that the most famous DBMS with pluggable storage API
is MySQL. This why I decided to start with it. I've added
MySQL/MariaDB section on wiki page.
https://wiki.postgresql.org/wiki/Future_of_storage#MySQL.2FMariaDB
It appears that significant part of MySQL storage engines are misuses.
MySQL lacks of various features like FDWs or writable views and so on.
This is why developers created a lot of pluggable storages for that
purposes. We definitely don't want something like this in PostgreSQL
now. I created special resume column where I expressed whether it
would be nice to have something like this table engine in PostgreSQL.

I don't want to discourage you, but I'm not sure how valuable this is.

I agree it's valuable to have a an over-view of use cases for pluggable
storage, but I don't think we'll get that from looking at MySQL. As you
noticed, most of the storage engines are misuses, so it's difficult to
learn anything valuable from them. You can argue that using FDWs to
implement alternative storage engines is a misuse too, but at least that
gives us a valid use case (columnar storage implemented using FDW).

If anything, the MySQL storage engines should serve as a cautionary tale
how not to do things - there's also a plenty of references in the MySQL
"Restrictions and Limitations" section of the manual:

https://downloads.mysql.com/docs/mysql-reslimits-excerpt-5.7-en.pdf

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#35Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Tomas Vondra (#34)
Re: Pluggable storage

On Mon, Jun 26, 2017 at 10:55 PM, Tomas Vondra <tomas.vondra@2ndquadrant.com

wrote:

On 06/26/2017 05:18 PM, Alexander Korotkov wrote:

I see that design question for PostgreSQL pluggable storages is very hard.

IMHO it's mostly expected to be hard.

Firstly, PostgreSQL is a mature product with many advanced features, and
reworking a low-level feature without breaking something on top of it is
hard by definition.

Secondly, project policies and code quality requirements set the bar very
high too, I think.

Sure.

BTW, I think it worth analyzing existing use-cases of pluggable

storages. I think that the most famous DBMS with pluggable storage API
is MySQL. This why I decided to start with it. I've added
MySQL/MariaDB section on wiki page.
https://wiki.postgresql.org/wiki/Future_of_storage#MySQL.2FMariaDB
It appears that significant part of MySQL storage engines are misuses.
MySQL lacks of various features like FDWs or writable views and so on.
This is why developers created a lot of pluggable storages for that
purposes. We definitely don't want something like this in PostgreSQL now.
I created special resume column where I expressed whether it
would be nice to have something like this table engine in PostgreSQL.

I don't want to discourage you, but I'm not sure how valuable this is.

I agree it's valuable to have a an over-view of use cases for pluggable
storage, but I don't think we'll get that from looking at MySQL. As you
noticed, most of the storage engines are misuses, so it's difficult to
learn anything valuable from them. You can argue that using FDWs to
implement alternative storage engines is a misuse too, but at least that
gives us a valid use case (columnar storage implemented using FDW).

If anything, the MySQL storage engines should serve as a cautionary tale
how not to do things - there's also a plenty of references in the MySQL
"Restrictions and Limitations" section of the manual:

https://downloads.mysql.com/docs/mysql-reslimits-excerpt-5.7-en.pdf

Just to clarify the thing. I don't propose any adoption of MySQL pluggable
storage API to PostgreSQL or something like this. I just wrote this table
for completeness of vision. It may appear that somebody will make some
valuable notes out of it, it may appear that not. "Yes" in third column
means only that there is positive user visible effects which are *nice to
have* in PostgreSQL.

Also, I remember there was a table with comparison of different designs of
pluggable storages and their use-cases at PGCon 2017 unconference. Could
somebody reproduce it?

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

#36Amit Kapila
amit.kapila16@gmail.com
In reply to: Alexander Korotkov (#24)
Re: Pluggable storage

On Thu, Jun 22, 2017 at 8:00 PM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Wed, Jun 21, 2017 at 10:47 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Jun 12, 2017 at 9:50 PM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

Open Items:

1. The BitmapHeapScan and TableSampleScan are tightly coupled with
HeapTuple and HeapScanDesc, So these scans are directly operating
on those structures and providing the result.

These scan types may not be applicable to different storage formats.
So how to handle them?

I think that BitmapHeapScan, at least, is applicable to any table AM
that has TIDs. It seems to me that in general we can imagine three
kinds of table AMs:

1. Table AMs where a tuple can be efficiently located by a real TID.
By a real TID, I mean that the block number part is really a block
number and the item ID is really a location within the block. These
are necessarily quite similar to our current heap, but they can change
the tuple format and page format to some degree, and it seems like in
many cases it should be possible to plug them into our existing index
AMs without too much heartache. Both index scans and bitmap index
scans ought to work.

If #1 is only about changing tuple and page formats, then could be much
simpler than the patch upthread? We can implement "page format access
methods" with routines for insertion, update, pruning and deletion of tuples
*in particular page*. There is no need to redefine high-level logic for
scanning heap, doing updates and so on...

If you are changing tuple format then you do need to worry about
places wherever we are using HeapTuple like TupleTableSlots,
Visibility routines, etc. (just think if somebody changes tuple
header, then all such places are susceptible to change). Similarly,
if the page format is changed you need to consider all page scan API's
like heapgettup_pagemode/heapgetpage.

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#37Amit Kapila
amit.kapila16@gmail.com
In reply to: Alexander Korotkov (#21)
Re: Pluggable storage

On Thu, Jun 22, 2017 at 5:46 PM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Tue, Jun 13, 2017 at 4:50 AM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:

On Fri, Oct 14, 2016 at 7:26 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

I have sent the partial patch I have to Hari Babu Kommi. We expect that
he will be able to further this goal some more.

Thanks Alvaro for sharing your development patch.

Most of the patch design is same as described by Alvaro in the first mail
[1].
I will detail the modifications, pending items and open items (needs
discussion)
to implement proper pluggable storage.

Here I attached WIP patches to support pluggable storage. The patch series
are may not work individually. Still so many things are under development.
These patches are just to share the approach of the current development.

Some notable changes that I did to make the patch work:

1. Added storageam handler to the slot, this is because not all places
the relation is not available in handy.
2. Retained the minimal Tuple in the slot, as this is used in HASH join.
As per the first version, I feel it is fine to allow creating HeapTuple
format data.

Thanks everyone for sharing their ideas in the developer's unconference at
PGCon Ottawa.

Pending items:

1. Replacement of Tuple with slot in Trigger functionality
2. Replacement of Tuple with Slot from storage handler functions.
3. Remove/minimize the use of HeapTuple as a Datum.
4. Replace all references of HeapScanDesc with StorageScanDesc
5. Planner changes to consider the relation storage during the planning.
6. Any planner changes based on the discussion of open items?
7. some Executor changes to consider the storage advantages?

Open Items:

1. The BitmapHeapScan and TableSampleScan are tightly coupled with
HeapTuple and HeapScanDesc, So these scans are directly operating
on those structures and providing the result.

What about vacuum? I see vacuum is untouched in the patchset and it is not
mentioned in this discussion.
Do you plan to override low-level function like heap_page_prune(),
lazy_vacuum_page() etc., but preserve high-level logic of vacuum?
Or do you plan to let pluggable storage implement its own high-level vacuum
algorithm?

Probably, some other algorithm for vacuum. I am not sure current
vacuum with its parameters can be used so easily. One thing that
might need some thoughts is that is it sufficient to say that keep
autovacuum as off and call some different API for places where the
vacuum can be invoked manually like Vacuum command to the developer
implementing some different strategy for vacuum or we need something
more as well.

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#38Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Amit Kapila (#36)
Re: Pluggable storage

On Tue, Jun 27, 2017 at 4:08 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:

On Thu, Jun 22, 2017 at 8:00 PM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Wed, Jun 21, 2017 at 10:47 PM, Robert Haas <robertmhaas@gmail.com>

wrote:

On Mon, Jun 12, 2017 at 9:50 PM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

Open Items:

1. The BitmapHeapScan and TableSampleScan are tightly coupled with
HeapTuple and HeapScanDesc, So these scans are directly operating
on those structures and providing the result.

These scan types may not be applicable to different storage formats.
So how to handle them?

I think that BitmapHeapScan, at least, is applicable to any table AM
that has TIDs. It seems to me that in general we can imagine three
kinds of table AMs:

1. Table AMs where a tuple can be efficiently located by a real TID.
By a real TID, I mean that the block number part is really a block
number and the item ID is really a location within the block. These
are necessarily quite similar to our current heap, but they can change
the tuple format and page format to some degree, and it seems like in
many cases it should be possible to plug them into our existing index
AMs without too much heartache. Both index scans and bitmap index
scans ought to work.

If #1 is only about changing tuple and page formats, then could be much
simpler than the patch upthread? We can implement "page format access
methods" with routines for insertion, update, pruning and deletion of

tuples

*in particular page*. There is no need to redefine high-level logic for
scanning heap, doing updates and so on...

If you are changing tuple format then you do need to worry about
places wherever we are using HeapTuple like TupleTableSlots,
Visibility routines, etc. (just think if somebody changes tuple
header, then all such places are susceptible to change).

Agree. I think that we can consider pluggable tuple format as an
independent feature which is desirable to have before pluggable storages.
For example, I believe some FDWs could already have benefit from pluggable
tuple format.

Similarly,
if the page format is changed you need to consider all page scan API's
like heapgettup_pagemode/heapgetpage.

If page format is changed, then heapgettup_pagemode/heapgetpage should use
appropriate API functions for manipulating page items. I'm very afraid of
overriding whole heapgettup_pagemode/heapgetpage and monstrous functions
like heap_update without understanding of clear use-case. It's definitely
not needed for changing heap page format.

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

#39Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Amit Kapila (#37)
Re: Pluggable storage

On Tue, Jun 27, 2017 at 4:19 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:

On Thu, Jun 22, 2017 at 5:46 PM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Tue, Jun 13, 2017 at 4:50 AM, Haribabu Kommi <

kommi.haribabu@gmail.com>

wrote:

On Fri, Oct 14, 2016 at 7:26 AM, Alvaro Herrera <

alvherre@2ndquadrant.com>

wrote:

I have sent the partial patch I have to Hari Babu Kommi. We expect

that

he will be able to further this goal some more.

Thanks Alvaro for sharing your development patch.

Most of the patch design is same as described by Alvaro in the first

mail

[1].
I will detail the modifications, pending items and open items (needs
discussion)
to implement proper pluggable storage.

Here I attached WIP patches to support pluggable storage. The patch

series

are may not work individually. Still so many things are under

development.

These patches are just to share the approach of the current development.

Some notable changes that I did to make the patch work:

1. Added storageam handler to the slot, this is because not all places
the relation is not available in handy.
2. Retained the minimal Tuple in the slot, as this is used in HASH join.
As per the first version, I feel it is fine to allow creating HeapTuple
format data.

Thanks everyone for sharing their ideas in the developer's unconference

at

PGCon Ottawa.

Pending items:

1. Replacement of Tuple with slot in Trigger functionality
2. Replacement of Tuple with Slot from storage handler functions.
3. Remove/minimize the use of HeapTuple as a Datum.
4. Replace all references of HeapScanDesc with StorageScanDesc
5. Planner changes to consider the relation storage during the planning.
6. Any planner changes based on the discussion of open items?
7. some Executor changes to consider the storage advantages?

Open Items:

1. The BitmapHeapScan and TableSampleScan are tightly coupled with
HeapTuple and HeapScanDesc, So these scans are directly operating
on those structures and providing the result.

What about vacuum? I see vacuum is untouched in the patchset and it is

not

mentioned in this discussion.
Do you plan to override low-level function like heap_page_prune(),
lazy_vacuum_page() etc., but preserve high-level logic of vacuum?
Or do you plan to let pluggable storage implement its own high-level

vacuum

algorithm?

Probably, some other algorithm for vacuum. I am not sure current
vacuum with its parameters can be used so easily. One thing that
might need some thoughts is that is it sufficient to say that keep
autovacuum as off and call some different API for places where the
vacuum can be invoked manually like Vacuum command to the developer
implementing some different strategy for vacuum or we need something
more as well.

What kind of other vacuum algorithm do you expect? It would be rather
easier to understand if we would have examples.

For me, changing of vacuum algorithm is not needed for just heap page
format change. Existing vacuum algorithm could just call page format API
functions for manipulating individual pages.

Changing of vacuum algorithm might be needed for more invasive changes than
just heap page format. However, we should first understand what these
changes could be and how are they consistent with rest of API design.

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

#40Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Robert Haas (#17)
Re: Pluggable storage

On Thu, Jun 22, 2017 at 5:47 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Jun 12, 2017 at 9:50 PM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

Open Items:

1. The BitmapHeapScan and TableSampleScan are tightly coupled with
HeapTuple and HeapScanDesc, So these scans are directly operating
on those structures and providing the result.

These scan types may not be applicable to different storage formats.
So how to handle them?

I think that BitmapHeapScan, at least, is applicable to any table AM
that has TIDs. It seems to me that in general we can imagine three
kinds of table AMs:

1. Table AMs where a tuple can be efficiently located by a real TID.
By a real TID, I mean that the block number part is really a block
number and the item ID is really a location within the block. These
are necessarily quite similar to our current heap, but they can change
the tuple format and page format to some degree, and it seems like in
many cases it should be possible to plug them into our existing index
AMs without too much heartache. Both index scans and bitmap index
scans ought to work.

2. Table AMs where a tuple has some other kind of locator. For
example, imagine an index-organized table where the locator is the
primary key, which is a bit like what Alvaro had in mind for indirect
indexes. If the locator is 6 bytes or less, it could potentially be
jammed into a TID, but I don't think that's a great idea. For things
like int8 or numeric, it won't work at all. Even for other things,
it's going to cause problems because the bit patterns won't be what
the code is expecting; e.g. bitmap scans care about the structure of
the TID, not just how many bits it is. (Due credit: Somebody, maybe
Alvaro, pointed out this problem before, at PGCon.) For these kinds
of tables, larger modifications to the index AMs are likely to be
necessary, at least if we want a really general solution, or maybe we
should have separate index AMs - e.g. btree for traditional TID-based
heaps, and generic_btree or indirect_btree or key_btree or whatever
for heaps with some other kind of locator. It's not too hard to see
how to make index scans work with this sort of structure but it's very
unclear to me whether, or how, bitmap scans can be made to work.

3. Table AMs where a tuple doesn't really have a locator at all. In
these cases, we can't support any sort of index AM at all. When the
table is queried, there's really nothing the core system can do except
ask the table AM for a full scan, supply the quals, and hope the table
AM has some sort of smarts that enable it to optimize somehow. For
example, you can imagine converting cstore_fdw into a table AM of this
sort - ORC has a sort of inbuilt BRIN-like indexing that allows whole
chunks to be proven uninteresting and skipped. (You could use chunk
number + offset to turn this into a table AM of the previous type if
you wanted to support secondary indexes; not sure if that'd be useful,
but it'd certainly be harder.)

I'm more interested in #1 than in #3, and more interested in #3 than
#2, but other people may have different priorities.

Hi Robert,

Thanks for the details and your opinion.
I also agree that option#1 is better to do first.

Regards,
Hari Babu
Fujitsu Australia

#41Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Alexander Korotkov (#39)
Re: Pluggable storage

On Wed, Jun 28, 2017 at 12:00 AM, Alexander Korotkov <
a.korotkov@postgrespro.ru> wrote:

On Tue, Jun 27, 2017 at 4:19 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:

On Thu, Jun 22, 2017 at 5:46 PM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Tue, Jun 13, 2017 at 4:50 AM, Haribabu Kommi <

kommi.haribabu@gmail.com>

wrote:

On Fri, Oct 14, 2016 at 7:26 AM, Alvaro Herrera <

alvherre@2ndquadrant.com>

wrote:

I have sent the partial patch I have to Hari Babu Kommi. We expect

that

he will be able to further this goal some more.

Thanks Alvaro for sharing your development patch.

Most of the patch design is same as described by Alvaro in the first

mail

[1].
I will detail the modifications, pending items and open items (needs
discussion)
to implement proper pluggable storage.

Here I attached WIP patches to support pluggable storage. The patch

series

are may not work individually. Still so many things are under

development.

These patches are just to share the approach of the current

development.

Some notable changes that I did to make the patch work:

1. Added storageam handler to the slot, this is because not all places
the relation is not available in handy.
2. Retained the minimal Tuple in the slot, as this is used in HASH

join.

As per the first version, I feel it is fine to allow creating HeapTuple
format data.

Thanks everyone for sharing their ideas in the developer's

unconference at

PGCon Ottawa.

Pending items:

1. Replacement of Tuple with slot in Trigger functionality
2. Replacement of Tuple with Slot from storage handler functions.
3. Remove/minimize the use of HeapTuple as a Datum.
4. Replace all references of HeapScanDesc with StorageScanDesc
5. Planner changes to consider the relation storage during the

planning.

6. Any planner changes based on the discussion of open items?
7. some Executor changes to consider the storage advantages?

Open Items:

1. The BitmapHeapScan and TableSampleScan are tightly coupled with
HeapTuple and HeapScanDesc, So these scans are directly operating
on those structures and providing the result.

What about vacuum? I see vacuum is untouched in the patchset and it is

not

mentioned in this discussion.
Do you plan to override low-level function like heap_page_prune(),
lazy_vacuum_page() etc., but preserve high-level logic of vacuum?
Or do you plan to let pluggable storage implement its own high-level

vacuum

algorithm?

Probably, some other algorithm for vacuum. I am not sure current
vacuum with its parameters can be used so easily. One thing that
might need some thoughts is that is it sufficient to say that keep
autovacuum as off and call some different API for places where the
vacuum can be invoked manually like Vacuum command to the developer
implementing some different strategy for vacuum or we need something
more as well.

What kind of other vacuum algorithm do you expect? It would be rather
easier to understand if we would have examples.

For me, changing of vacuum algorithm is not needed for just heap page
format change. Existing vacuum algorithm could just call page format API
functions for manipulating individual pages.

Changing of vacuum algorithm might be needed for more invasive changes
than just heap page format. However, we should first understand what these
changes could be and how are they consistent with rest of API design.

Yes, I agree that we need some changes in the vacuum to handle the
pluggable storage.
Currently I didn't fully checked the changes that are needed in vacuum, but
I feel the low level changes of the function are enough, and also there
should be
some option from storage handler to decide whether it needs a vacuum or not?
Based on this flag, the vacuum may be skipped on those tables. So these
handlers
no need to register those API's.

Regards,
Hari Babu
Fujitsu Australia

#42Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Alexander Korotkov (#38)
Re: Pluggable storage

On Tue, Jun 27, 2017 at 11:53 PM, Alexander Korotkov <
a.korotkov@postgrespro.ru> wrote:

On Tue, Jun 27, 2017 at 4:08 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:

On Thu, Jun 22, 2017 at 8:00 PM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Wed, Jun 21, 2017 at 10:47 PM, Robert Haas <robertmhaas@gmail.com>

wrote:

On Mon, Jun 12, 2017 at 9:50 PM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

Open Items:

1. The BitmapHeapScan and TableSampleScan are tightly coupled with
HeapTuple and HeapScanDesc, So these scans are directly operating
on those structures and providing the result.

These scan types may not be applicable to different storage formats.
So how to handle them?

I think that BitmapHeapScan, at least, is applicable to any table AM
that has TIDs. It seems to me that in general we can imagine three
kinds of table AMs:

1. Table AMs where a tuple can be efficiently located by a real TID.
By a real TID, I mean that the block number part is really a block
number and the item ID is really a location within the block. These
are necessarily quite similar to our current heap, but they can change
the tuple format and page format to some degree, and it seems like in
many cases it should be possible to plug them into our existing index
AMs without too much heartache. Both index scans and bitmap index
scans ought to work.

If #1 is only about changing tuple and page formats, then could be much
simpler than the patch upthread? We can implement "page format access
methods" with routines for insertion, update, pruning and deletion of

tuples

*in particular page*. There is no need to redefine high-level logic for
scanning heap, doing updates and so on...

If you are changing tuple format then you do need to worry about
places wherever we are using HeapTuple like TupleTableSlots,
Visibility routines, etc. (just think if somebody changes tuple
header, then all such places are susceptible to change).

Agree. I think that we can consider pluggable tuple format as an
independent feature which is desirable to have before pluggable storages.
For example, I believe some FDWs could already have benefit from pluggable
tuple format.

Accepting multiple tuple format is possible with complete replacement of
HeapTuple with TupleTableSlot or something like value/null array
instead of a single memory chunk tuple data.

Currently I am working on it to replace the HeapTuple with TupleTableSlot
in the upper layers once the tuples is returned from the scan. In most of
the
places where the HeapTuple is present, either replace it with TupleTableSlot
or change it to StorageTuple (void *).

I am yet to evaluate whether it is possible to support as an independent
feature
without the need of some heap format function to understand the tuple format
in every place.

Regards,
Hari Babu
Fujitsu Australia

#43Amit Kapila
amit.kapila16@gmail.com
In reply to: Alexander Korotkov (#38)
Re: Pluggable storage

On Tue, Jun 27, 2017 at 7:23 PM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Tue, Jun 27, 2017 at 4:08 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:

Similarly,
if the page format is changed you need to consider all page scan API's
like heapgettup_pagemode/heapgetpage.

If page format is changed, then heapgettup_pagemode/heapgetpage should use
appropriate API functions for manipulating page items. I'm very afraid of
overriding whole heapgettup_pagemode/heapgetpage and monstrous functions
like heap_update without understanding of clear use-case. It's definitely
not needed for changing heap page format.

Yeah, we might not change them completely, there will always be some
common parts, but as of now, this API considers that we can return a
pointer to tuple on the page with just having a pin on the buffer.
This is based on the assumption that nobody can update the current
tuple, only a new tuple will be created on the update. For some use
cases like in-place updates, we might want to do that differently.

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#44Amit Kapila
amit.kapila16@gmail.com
In reply to: Haribabu Kommi (#41)
Re: Pluggable storage

On Wed, Jun 28, 2017 at 7:43 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

On Wed, Jun 28, 2017 at 12:00 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Tue, Jun 27, 2017 at 4:19 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:

On Thu, Jun 22, 2017 at 5:46 PM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

Probably, some other algorithm for vacuum. I am not sure current
vacuum with its parameters can be used so easily. One thing that
might need some thoughts is that is it sufficient to say that keep
autovacuum as off and call some different API for places where the
vacuum can be invoked manually like Vacuum command to the developer
implementing some different strategy for vacuum or we need something
more as well.

What kind of other vacuum algorithm do you expect? It would be rather
easier to understand if we would have examples.

For me, changing of vacuum algorithm is not needed for just heap page
format change. Existing vacuum algorithm could just call page format API
functions for manipulating individual pages.

Changing of vacuum algorithm might be needed for more invasive changes
than just heap page format. However, we should first understand what these
changes could be and how are they consistent with rest of API design.

Yes, I agree that we need some changes in the vacuum to handle the pluggable
storage.
Currently I didn't fully checked the changes that are needed in vacuum, but
I feel the low level changes of the function are enough, and also there
should be
some option from storage handler to decide whether it needs a vacuum or not?
Based on this flag, the vacuum may be skipped on those tables. So these
handlers
no need to register those API's.

Something in that direction sounds reasonable to me. I am also not
very clear what kind of pluggability will be required for vacuum. I
think for now we can park this problem and try to tackle tuple format
and page format related stuff.

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#45Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Haribabu Kommi (#42)
1 attachment(s)
Re: Pluggable storage

On Wed, Jun 28, 2017 at 1:16 PM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:

Accepting multiple tuple format is possible with complete replacement of
HeapTuple with TupleTableSlot or something like value/null array
instead of a single memory chunk tuple data.

Currently I am working on it to replace the HeapTuple with TupleTableSlot
in the upper layers once the tuples is returned from the scan. In most of
the
places where the HeapTuple is present, either replace it with
TupleTableSlot
or change it to StorageTuple (void *).

I am yet to evaluate whether it is possible to support as an independent
feature
without the need of some heap format function to understand the tuple
format
in every place.

To replace tuple with slot, I took trigger and SPI calls as the first step
in modifying
from tuple to slot, Here I attached a WIP patch. The notable changes are,

1. Replace most of the HeapTuple with Slot in SPI interface functions.
2. In SPITupleTable, Instead of HeapTuple, it is changed to TupleTableSlot.
But this change may not be a proper approach, because a duplicate copy of
TupleTableSlot is generated and stored.
3. Changed all trigger interfaces to accept TupleTableSlot Instead of
HeapTuple.
4. ItemPointerData is added as a member to the TupleTableSlot structure.
5. Modified the ExecInsert and others to work directly on TupleTableSlot
instead
of tuple(not completely).

In many places while accessing system tables, the tuple is directly mapped
to
a catalog table structure and accessed. Currently I am not modifying any of
those,
and leaving them as it is until we solve all other HeapTuple replacement
problems.

In order to continue further changes in replacing the HeapTuple with
TupleTableSlot,
I am just thinking of replacing Trigger functions like plpgsql_exec_trigger
to return
TupleTableSlot instead of HeapTuple. I am thinking that these changes may
improve
the performance as it avoids the deform and forming a tuple. I am thinking
that
same TupleTableSlot memory is valid across these function calls. Am I
correct?

I am thinking that it may not possible to replace all the places of
HeapTuple with
TupleTableSlot, but it is possible with a StorageTuple (which is a void*).
wherever
the tuple is present, there exists a tupledesc in most of the cases. How
about
adding some kind of information in tupledesc to find out the tuple format
and call
the necessary functions to generate a TupleTableSlot from it and use that
from there
onward? This approach may be useful for Storing StorageTuple in
SPITupleTable
instead of TupleTableSlot.

Please let me know your comments/suggestions.

Regards,
Hari Babu
Fujitsu Australia

Attachments:

0001-WIP-tuple-replace-with-slot.patchapplication/octet-stream; name=0001-WIP-tuple-replace-with-slot.patchDownload
From b98a8b002a0fddbbd665a55abcc820273a88b0f3 Mon Sep 17 00:00:00 2001
From: Hari Babu Kommi <kommih@localhost.localdomain>
Date: Fri, 14 Jul 2017 22:13:09 +1000
Subject: [PATCH] WIP tuple replace with slot

---
 contrib/dblink/dblink.c                  |  38 ++--
 contrib/lo/lo.c                          |  24 +--
 contrib/spi/autoinc.c                    |  16 +-
 contrib/spi/insert_username.c            |  10 +-
 contrib/spi/moddatetime.c                |   9 +-
 contrib/spi/refint.c                     |  40 ++--
 contrib/spi/timetravel.c                 |  40 ++--
 contrib/tablefunc/tablefunc.c            |  34 ++--
 contrib/tcn/tcn.c                        |   6 +-
 contrib/xml2/xpath.c                     |   8 +-
 src/backend/bootstrap/bootstrap.c        |   4 +-
 src/backend/catalog/aclchk.c             |  12 +-
 src/backend/catalog/index.c              |   4 +-
 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/constraint.c        |  14 +-
 src/backend/commands/copy.c              |  27 ++-
 src/backend/commands/dbcommands.c        |   1 +
 src/backend/commands/indexcmds.c         |   2 +-
 src/backend/commands/matview.c           |   2 +-
 src/backend/commands/tablecmds.c         |  14 +-
 src/backend/commands/trigger.c           | 334 +++++++++++++++++--------------
 src/backend/executor/execReplication.c   |   7 +-
 src/backend/executor/execTuples.c        |  60 ++++++
 src/backend/executor/nodeModifyTable.c   |  57 ++++--
 src/backend/executor/spi.c               |  92 +++++++--
 src/backend/replication/logical/worker.c |   1 +
 src/backend/utils/adt/ri_triggers.c      | 230 ++++++++++-----------
 src/backend/utils/adt/ruleutils.c        |  50 +++--
 src/backend/utils/adt/trigfuncs.c        |   4 +-
 src/backend/utils/adt/tsquery_rewrite.c  |   4 +-
 src/backend/utils/adt/tsvector_op.c      |  26 ++-
 src/backend/utils/adt/xml.c              |   2 -
 src/include/access/htup.h                |   1 +
 src/include/commands/trigger.h           |  26 ++-
 src/include/executor/spi.h               |   8 +-
 src/include/executor/tuptable.h          |   6 +
 src/pl/plperl/plperl.c                   |  20 +-
 src/pl/plpgsql/src/pl_comp.c             |   2 +-
 src/pl/plpgsql/src/pl_exec.c             | 102 ++++------
 src/pl/plpgsql/src/plpgsql.h             |   2 +-
 src/pl/plpython/plpy_exec.c              |  14 +-
 src/pl/tcl/pltcl.c                       |  10 +-
 src/test/modules/worker_spi/worker_spi.c |   2 -
 src/test/regress/regress.c               |  55 +++--
 47 files changed, 779 insertions(+), 649 deletions(-)

diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index 81136b1..cad2039 100644
--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -107,7 +107,7 @@ static char *get_sql_delete(Relation rel, int *pkattnums, int pknumatts, char **
 static char *get_sql_update(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals, char **tgt_pkattvals);
 static char *quote_ident_cstr(char *rawstr);
 static int	get_attnum_pk_pos(int *pkattnums, int pknumatts, int key);
-static HeapTuple get_tuple_of_interest(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals);
+static TupleTableSlot* get_tuple_of_interest(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals);
 static Relation get_rel_from_relname(text *relname_text, LOCKMODE lockmode, AclMode aclmode);
 static char *generate_relation_name(Relation rel);
 static void dblink_connstr_check(const char *connstr);
@@ -2144,7 +2144,7 @@ static char *
 get_sql_insert(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals, char **tgt_pkattvals)
 {
 	char	   *relname;
-	HeapTuple	tuple;
+	TupleTableSlot *slot;
 	TupleDesc	tupdesc;
 	int			natts;
 	StringInfoData buf;
@@ -2158,15 +2158,15 @@ get_sql_insert(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals
 	/* get relation name including any needed schema prefix and quoting */
 	relname = generate_relation_name(rel);
 
-	tupdesc = rel->rd_att;
-	natts = tupdesc->natts;
-
-	tuple = get_tuple_of_interest(rel, pkattnums, pknumatts, src_pkattvals);
-	if (!tuple)
+	slot = get_tuple_of_interest(rel, pkattnums, pknumatts, src_pkattvals);
+	if (!slot)
 		ereport(ERROR,
 				(errcode(ERRCODE_CARDINALITY_VIOLATION),
 				 errmsg("source row not found")));
 
+	tupdesc = slot->tts_tupleDescriptor;
+	natts = tupdesc->natts;
+
 	appendStringInfo(&buf, "INSERT INTO %s(", relname);
 
 	needComma = false;
@@ -2202,7 +2202,7 @@ get_sql_insert(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals
 		if (key >= 0)
 			val = tgt_pkattvals[key] ? pstrdup(tgt_pkattvals[key]) : NULL;
 		else
-			val = SPI_getvalue(tuple, tupdesc, i + 1);
+			val = SPI_getvalue(slot, i + 1);
 
 		if (val != NULL)
 		{
@@ -2258,7 +2258,7 @@ static char *
 get_sql_update(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals, char **tgt_pkattvals)
 {
 	char	   *relname;
-	HeapTuple	tuple;
+	TupleTableSlot *slot;
 	TupleDesc	tupdesc;
 	int			natts;
 	StringInfoData buf;
@@ -2272,15 +2272,15 @@ get_sql_update(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals
 	/* get relation name including any needed schema prefix and quoting */
 	relname = generate_relation_name(rel);
 
-	tupdesc = rel->rd_att;
-	natts = tupdesc->natts;
-
-	tuple = get_tuple_of_interest(rel, pkattnums, pknumatts, src_pkattvals);
-	if (!tuple)
+	slot = get_tuple_of_interest(rel, pkattnums, pknumatts, src_pkattvals);
+	if (!slot)
 		ereport(ERROR,
 				(errcode(ERRCODE_CARDINALITY_VIOLATION),
 				 errmsg("source row not found")));
 
+	tupdesc = slot->tts_tupleDescriptor;
+	natts = tupdesc->natts;
+
 	appendStringInfo(&buf, "UPDATE %s SET ", relname);
 
 	/*
@@ -2303,7 +2303,7 @@ get_sql_update(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals
 		if (key >= 0)
 			val = tgt_pkattvals[key] ? pstrdup(tgt_pkattvals[key]) : NULL;
 		else
-			val = SPI_getvalue(tuple, tupdesc, i + 1);
+			val = SPI_getvalue(slot, i + 1);
 
 		if (val != NULL)
 		{
@@ -2372,7 +2372,7 @@ get_attnum_pk_pos(int *pkattnums, int pknumatts, int key)
 	return -1;
 }
 
-static HeapTuple
+static TupleTableSlot *
 get_tuple_of_interest(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals)
 {
 	char	   *relname;
@@ -2380,7 +2380,7 @@ get_tuple_of_interest(Relation rel, int *pkattnums, int pknumatts, char **src_pk
 	int			natts;
 	StringInfoData buf;
 	int			ret;
-	HeapTuple	tuple;
+	TupleTableSlot *slot;
 	int			i;
 
 	/*
@@ -2456,10 +2456,10 @@ get_tuple_of_interest(Relation rel, int *pkattnums, int pknumatts, char **src_pk
 	{
 		SPITupleTable *tuptable = SPI_tuptable;
 
-		tuple = SPI_copytuple(tuptable->vals[0]);
+		slot = tuptable->vals[0];
 		SPI_finish();
 
-		return tuple;
+		return slot;
 	}
 	else
 	{
diff --git a/contrib/lo/lo.c b/contrib/lo/lo.c
index 4585923..432bedb 100644
--- a/contrib/lo/lo.c
+++ b/contrib/lo/lo.c
@@ -27,10 +27,10 @@ lo_manage(PG_FUNCTION_ARGS)
 	int			attnum;			/* attribute number to monitor	*/
 	char	  **args;			/* Args containing attr name	*/
 	TupleDesc	tupdesc;		/* Tuple Descriptor				*/
-	HeapTuple	rettuple;		/* Tuple to be returned			*/
+	TupleTableSlot*	retslot;		/* Tuple to be returned			*/
 	bool		isdelete;		/* are we deleting?				*/
-	HeapTuple	newtuple;		/* The new value for tuple		*/
-	HeapTuple	trigtuple;		/* The original value of tuple	*/
+	TupleTableSlot*	newslot;		/* The new value for tuple		*/
+	TupleTableSlot*	trigslot;		/* The original value of tuple	*/
 
 	if (!CALLED_AS_TRIGGER(fcinfo)) /* internal error */
 		elog(ERROR, "%s: not fired by trigger manager",
@@ -43,8 +43,8 @@ lo_manage(PG_FUNCTION_ARGS)
 	/*
 	 * Fetch some values from trigdata
 	 */
-	newtuple = trigdata->tg_newtuple;
-	trigtuple = trigdata->tg_trigtuple;
+	newslot = trigdata->tg_newslot;
+	trigslot = trigdata->tg_trigslot;
 	tupdesc = trigdata->tg_relation->rd_att;
 	args = trigdata->tg_trigger->tgargs;
 
@@ -54,9 +54,9 @@ lo_manage(PG_FUNCTION_ARGS)
 
 	/* tuple to return to Executor */
 	if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
-		rettuple = newtuple;
+		retslot = newslot;
 	else
-		rettuple = trigtuple;
+		retslot = trigslot;
 
 	/* Are we deleting the row? */
 	isdelete = TRIGGER_FIRED_BY_DELETE(trigdata->tg_event);
@@ -74,10 +74,10 @@ lo_manage(PG_FUNCTION_ARGS)
 	 * Here, if the value of the monitored attribute changes, then the large
 	 * object associated with the original value is unlinked.
 	 */
-	if (newtuple != NULL)
+	if (newslot != NULL)
 	{
-		char	   *orig = SPI_getvalue(trigtuple, tupdesc, attnum);
-		char	   *newv = SPI_getvalue(newtuple, tupdesc, attnum);
+		char	   *orig = SPI_getvalue(trigslot, attnum);
+		char	   *newv = SPI_getvalue(newslot, attnum);
 
 		if (orig != NULL && (newv == NULL || strcmp(orig, newv) != 0))
 			DirectFunctionCall1(be_lo_unlink,
@@ -96,7 +96,7 @@ lo_manage(PG_FUNCTION_ARGS)
 	 */
 	if (isdelete)
 	{
-		char	   *orig = SPI_getvalue(trigtuple, tupdesc, attnum);
+		char	   *orig = SPI_getvalue(trigslot, attnum);
 
 		if (orig != NULL)
 		{
@@ -107,5 +107,5 @@ lo_manage(PG_FUNCTION_ARGS)
 		}
 	}
 
-	return PointerGetDatum(rettuple);
+	return PointerGetDatum(retslot->tts_storageam->slot_get_tuple(retslot));
 }
diff --git a/contrib/spi/autoinc.c b/contrib/spi/autoinc.c
index 8bf7422..b4a207e 100644
--- a/contrib/spi/autoinc.c
+++ b/contrib/spi/autoinc.c
@@ -28,7 +28,7 @@ autoinc(PG_FUNCTION_ARGS)
 	char	  **args;			/* arguments */
 	char	   *relname;		/* triggered relation name */
 	Relation	rel;			/* triggered relation */
-	HeapTuple	rettuple = NULL;
+	TupleTableSlot *retslot = NULL;
 	TupleDesc	tupdesc;		/* tuple description */
 	bool		isnull;
 	int			i;
@@ -44,9 +44,9 @@ autoinc(PG_FUNCTION_ARGS)
 		elog(ERROR, "must be fired before event");
 
 	if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
-		rettuple = trigdata->tg_trigtuple;
+		retslot = trigdata->tg_trigslot;
 	else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
-		rettuple = trigdata->tg_newtuple;
+		retslot = trigdata->tg_newslot;
 	else
 		/* internal error */
 		elog(ERROR, "cannot process DELETE events");
@@ -86,7 +86,7 @@ autoinc(PG_FUNCTION_ARGS)
 					 errmsg("attribute \"%s\" of \"%s\" must be type INT4",
 							args[i], relname)));
 
-		val = DatumGetInt32(SPI_getbinval(rettuple, tupdesc, attnum, &isnull));
+		val = DatumGetInt32(SPI_getbinval(retslot, attnum, &isnull));
 
 		if (!isnull && val != 0)
 		{
@@ -113,9 +113,9 @@ autoinc(PG_FUNCTION_ARGS)
 
 	if (chnattrs > 0)
 	{
-		rettuple = heap_modify_tuple_by_cols(rettuple, tupdesc,
-											 chnattrs, chattrs,
-											 newvals, newnulls);
+		retslot = heap_modify_slot_by_cols(retslot,
+											chnattrs, chattrs,
+											newvals, newnulls);
 	}
 
 	pfree(relname);
@@ -123,5 +123,5 @@ autoinc(PG_FUNCTION_ARGS)
 	pfree(newvals);
 	pfree(newnulls);
 
-	return PointerGetDatum(rettuple);
+	return PointerGetDatum(retslot->tts_tuple);
 }
diff --git a/contrib/spi/insert_username.c b/contrib/spi/insert_username.c
index a2e1747..f3093ed 100644
--- a/contrib/spi/insert_username.c
+++ b/contrib/spi/insert_username.c
@@ -29,7 +29,7 @@ insert_username(PG_FUNCTION_ARGS)
 	char	  **args;			/* arguments */
 	char	   *relname;		/* triggered relation name */
 	Relation	rel;			/* triggered relation */
-	HeapTuple	rettuple = NULL;
+	TupleTableSlot *retslot;
 	TupleDesc	tupdesc;		/* tuple description */
 	int			attnum;
 
@@ -45,9 +45,9 @@ insert_username(PG_FUNCTION_ARGS)
 		elog(ERROR, "insert_username: must be fired before event");
 
 	if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
-		rettuple = trigdata->tg_trigtuple;
+		retslot = trigdata->tg_trigslot;
 	else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
-		rettuple = trigdata->tg_newtuple;
+		retslot = trigdata->tg_newslot;
 	else
 		/* internal error */
 		elog(ERROR, "insert_username: cannot process DELETE events");
@@ -83,10 +83,10 @@ insert_username(PG_FUNCTION_ARGS)
 	newnull = false;
 
 	/* construct new tuple */
-	rettuple = heap_modify_tuple_by_cols(rettuple, tupdesc,
+	retslot = heap_modify_slot_by_cols(retslot,
 										 1, &attnum, &newval, &newnull);
 
 	pfree(relname);
 
-	return PointerGetDatum(rettuple);
+	return PointerGetDatum(retslot->tts_tuple);
 }
diff --git a/contrib/spi/moddatetime.c b/contrib/spi/moddatetime.c
index 70476f7..2ab74a7 100644
--- a/contrib/spi/moddatetime.c
+++ b/contrib/spi/moddatetime.c
@@ -39,7 +39,7 @@ moddatetime(PG_FUNCTION_ARGS)
 	char	  **args;			/* arguments */
 	char	   *relname;		/* triggered relation name */
 	Relation	rel;			/* triggered relation */
-	HeapTuple	rettuple = NULL;
+	TupleTableSlot *slot;
 	TupleDesc	tupdesc;		/* tuple description */
 
 	if (!CALLED_AS_TRIGGER(fcinfo))
@@ -58,7 +58,7 @@ moddatetime(PG_FUNCTION_ARGS)
 		/* internal error */
 		elog(ERROR, "moddatetime: cannot process INSERT events");
 	else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
-		rettuple = trigdata->tg_newtuple;
+		slot = trigdata->tg_newslot;
 	else
 		/* internal error */
 		elog(ERROR, "moddatetime: cannot process DELETE events");
@@ -120,11 +120,10 @@ moddatetime(PG_FUNCTION_ARGS)
 	newdtnull = false;
 
 	/* Replace the attnum'th column with newdt */
-	rettuple = heap_modify_tuple_by_cols(rettuple, tupdesc,
-										 1, &attnum, &newdt, &newdtnull);
+	slot = heap_modify_slot_by_cols(slot, 1, &attnum, &newdt, &newdtnull);
 
 	/* Clean up */
 	pfree(relname);
 
-	return PointerGetDatum(rettuple);
+	return PointerGetDatum(slot->tts_tuple);
 }
diff --git a/contrib/spi/refint.c b/contrib/spi/refint.c
index 46205c7..0d87f59 100644
--- a/contrib/spi/refint.c
+++ b/contrib/spi/refint.c
@@ -53,7 +53,7 @@ check_primary_key(PG_FUNCTION_ARGS)
 	Datum	   *kvals;			/* key values */
 	char	   *relname;		/* referenced relation name */
 	Relation	rel;			/* triggered relation */
-	HeapTuple	tuple = NULL;	/* tuple to return */
+	TupleTableSlot *slot = NULL; /* tuple to return */
 	TupleDesc	tupdesc;		/* tuple description */
 	EPlan	   *plan;			/* prepared plan */
 	Oid		   *argtypes = NULL;	/* key types to prepare execution plan */
@@ -82,7 +82,7 @@ check_primary_key(PG_FUNCTION_ARGS)
 
 	/* If INSERTion then must check Tuple to being inserted */
 	if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
-		tuple = trigdata->tg_trigtuple;
+		slot = trigdata->tg_trigslot;
 
 	/* Not should be called for DELETE */
 	else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
@@ -91,7 +91,7 @@ check_primary_key(PG_FUNCTION_ARGS)
 
 	/* If UPDATE, then must check new Tuple, not old one */
 	else
-		tuple = trigdata->tg_newtuple;
+		slot = trigdata->tg_newslot;
 
 	trigger = trigdata->tg_trigger;
 	nargs = trigger->tgnargs;
@@ -142,7 +142,7 @@ check_primary_key(PG_FUNCTION_ARGS)
 							args[i], SPI_getrelname(rel))));
 
 		/* Well, get binary (in internal format) value of column */
-		kvals[i] = SPI_getbinval(tuple, tupdesc, fnumber, &isnull);
+		kvals[i] = SPI_getbinval(slot, fnumber, &isnull);
 
 		/*
 		 * If it's NULL then nothing to do! DON'T FORGET call SPI_finish ()!
@@ -152,7 +152,7 @@ check_primary_key(PG_FUNCTION_ARGS)
 		if (isnull)
 		{
 			SPI_finish();
-			return PointerGetDatum(tuple);
+			return PointerGetDatum(slot->tts_tuple);
 		}
 
 		if (plan->nplans <= 0)	/* Get typeId of column */
@@ -217,7 +217,7 @@ check_primary_key(PG_FUNCTION_ARGS)
 
 	SPI_finish();
 
-	return PointerGetDatum(tuple);
+	return PointerGetDatum(slot->tts_tuple);
 }
 
 /*
@@ -248,8 +248,8 @@ check_foreign_key(PG_FUNCTION_ARGS)
 	Datum	   *kvals;			/* key values */
 	char	   *relname;		/* referencing relation name */
 	Relation	rel;			/* triggered relation */
-	HeapTuple	trigtuple = NULL;	/* tuple to being changed */
-	HeapTuple	newtuple = NULL;	/* tuple to return */
+	TupleTableSlot*	trigslot = NULL;		/* tuple to being changed */
+	TupleTableSlot*	newslot = NULL;	/* tuple to return */
 	TupleDesc	tupdesc;		/* tuple description */
 	EPlan	   *plan;			/* prepared plan(s) */
 	Oid		   *argtypes = NULL;	/* key types to prepare execution plan */
@@ -285,7 +285,7 @@ check_foreign_key(PG_FUNCTION_ARGS)
 		elog(ERROR, "check_foreign_key: cannot process INSERT events");
 
 	/* Have to check tg_trigtuple - tuple being deleted */
-	trigtuple = trigdata->tg_trigtuple;
+	trigslot = trigdata->tg_trigslot;
 
 	/*
 	 * But if this is UPDATE then we have to return tg_newtuple. Also, if key
@@ -294,7 +294,7 @@ check_foreign_key(PG_FUNCTION_ARGS)
 	is_update = 0;
 	if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
 	{
-		newtuple = trigdata->tg_newtuple;
+		newslot = trigdata->tg_newslot;
 		is_update = 1;
 	}
 	trigger = trigdata->tg_trigger;
@@ -369,7 +369,7 @@ check_foreign_key(PG_FUNCTION_ARGS)
 							args[i], SPI_getrelname(rel))));
 
 		/* Well, get binary (in internal format) value of column */
-		kvals[i] = SPI_getbinval(trigtuple, tupdesc, fnumber, &isnull);
+		kvals[i] = SPI_getbinval(trigslot, fnumber, &isnull);
 
 		/*
 		 * If it's NULL then nothing to do! DON'T FORGET call SPI_finish ()!
@@ -379,7 +379,8 @@ check_foreign_key(PG_FUNCTION_ARGS)
 		if (isnull)
 		{
 			SPI_finish();
-			return PointerGetDatum((newtuple == NULL) ? trigtuple : newtuple);
+			return PointerGetDatum((newslot == NULL) ?
+					trigslot->tts_tuple : newslot->tts_tuple);
 		}
 
 		/*
@@ -387,16 +388,16 @@ check_foreign_key(PG_FUNCTION_ARGS)
 		 * compare is this the same as old one. For the moment we use string
 		 * presentation of values...
 		 */
-		if (newtuple != NULL)
+		if (newslot != NULL)
 		{
-			char	   *oldval = SPI_getvalue(trigtuple, tupdesc, fnumber);
+			char	   *oldval = SPI_getvalue(trigslot, fnumber);
 			char	   *newval;
 
 			/* this shouldn't happen! SPI_ERROR_NOOUTFUNC ? */
 			if (oldval == NULL)
 				/* internal error */
 				elog(ERROR, "check_foreign_key: SPI_getvalue returned %d", SPI_result);
-			newval = SPI_getvalue(newtuple, tupdesc, fnumber);
+			newval = SPI_getvalue(newslot, fnumber);
 			if (newval == NULL || strcmp(oldval, newval) != 0)
 				isequal = false;
 		}
@@ -470,7 +471,7 @@ check_foreign_key(PG_FUNCTION_ARGS)
 
 						fn = SPI_fnumber(tupdesc, args_temp[k - 1]);
 						Assert(fn > 0); /* already checked above */
-						nv = SPI_getvalue(newtuple, tupdesc, fn);
+						nv = SPI_getvalue(newslot, fn);
 						type = SPI_gettype(tupdesc, fn);
 
 						if ((strcmp(type, "text") && strcmp(type, "varchar") &&
@@ -552,10 +553,10 @@ check_foreign_key(PG_FUNCTION_ARGS)
 	/*
 	 * If UPDATE and key is not changed ...
 	 */
-	if (newtuple != NULL && isequal)
+	if (newslot != NULL && isequal)
 	{
 		SPI_finish();
-		return PointerGetDatum(newtuple);
+		return PointerGetDatum(newslot->tts_tuple);
 	}
 
 	/*
@@ -604,7 +605,8 @@ check_foreign_key(PG_FUNCTION_ARGS)
 
 	SPI_finish();
 
-	return PointerGetDatum((newtuple == NULL) ? trigtuple : newtuple);
+	return PointerGetDatum((newslot == NULL) ?
+							trigslot->tts_tuple : newslot->tts_tuple);
 }
 
 static EPlan *
diff --git a/contrib/spi/timetravel.c b/contrib/spi/timetravel.c
index f7905e2..2127cd6 100644
--- a/contrib/spi/timetravel.c
+++ b/contrib/spi/timetravel.c
@@ -96,8 +96,8 @@ timetravel(PG_FUNCTION_ARGS)
 	char	   *cnulls;			/* column nulls */
 	char	   *relname;		/* triggered relation name */
 	Relation	rel;			/* triggered relation */
-	HeapTuple	trigtuple;
-	HeapTuple	newtuple = NULL;
+	TupleTableSlot *trigslot;
+	TupleTableSlot *newslot = NULL;
 	HeapTuple	rettuple;
 	TupleDesc	tupdesc;		/* tuple description */
 	int			natts;			/* # of attributes */
@@ -129,9 +129,9 @@ timetravel(PG_FUNCTION_ARGS)
 		isinsert = true;
 
 	if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
-		newtuple = trigdata->tg_newtuple;
+		newslot = trigdata->tg_newslot;
 
-	trigtuple = trigdata->tg_trigtuple;
+	trigslot = trigdata->tg_trigslot;
 
 	rel = trigdata->tg_relation;
 	relname = SPI_getrelname(rel);
@@ -141,7 +141,8 @@ timetravel(PG_FUNCTION_ARGS)
 	{
 		/* OFF - nothing to do */
 		pfree(relname);
-		return PointerGetDatum((newtuple != NULL) ? newtuple : trigtuple);
+		return PointerGetDatum((newslot != NULL) ?
+				newslot->tts_tuple : trigslot->tts_tuple);
 	}
 
 	trigger = trigdata->tg_trigger;
@@ -186,7 +187,7 @@ timetravel(PG_FUNCTION_ARGS)
 		Datum		newvals[MaxAttrNum];
 		bool		newnulls[MaxAttrNum];
 
-		oldtimeon = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_on], &isnull);
+		oldtimeon = SPI_getbinval(trigslot, attnum[a_time_on], &isnull);
 		if (isnull)
 		{
 			newvals[chnattrs] = GetCurrentAbsoluteTime();
@@ -195,7 +196,7 @@ timetravel(PG_FUNCTION_ARGS)
 			chnattrs++;
 		}
 
-		oldtimeoff = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_off], &isnull);
+		oldtimeoff = SPI_getbinval(trigslot, attnum[a_time_off], &isnull);
 		if (isnull)
 		{
 			if ((chnattrs == 0 && DatumGetInt32(oldtimeon) >= NOEND_ABSTIME) ||
@@ -215,7 +216,7 @@ timetravel(PG_FUNCTION_ARGS)
 
 		pfree(relname);
 		if (chnattrs <= 0)
-			return PointerGetDatum(trigtuple);
+			return PointerGetDatum(trigslot->tts_tuple);
 
 		if (argc == MaxAttrNum)
 		{
@@ -235,7 +236,8 @@ timetravel(PG_FUNCTION_ARGS)
 			chattrs[chnattrs] = attnum[a_ins_user];
 			chnattrs++;
 		}
-		rettuple = heap_modify_tuple_by_cols(trigtuple, tupdesc,
+		rettuple = heap_modify_tuple_by_cols(trigslot->tts_tuple,
+											 tupdesc,
 											 chnattrs, chattrs,
 											 newvals, newnulls);
 		return PointerGetDatum(rettuple);
@@ -243,11 +245,11 @@ timetravel(PG_FUNCTION_ARGS)
 	}
 
 	/* UPDATE/DELETE: */
-	oldtimeon = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_on], &isnull);
+	oldtimeon = SPI_getbinval(trigslot, attnum[a_time_on], &isnull);
 	if (isnull)
 		elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_on]);
 
-	oldtimeoff = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_off], &isnull);
+	oldtimeoff = SPI_getbinval(trigslot, attnum[a_time_off], &isnull);
 	if (isnull)
 		elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_off]);
 
@@ -255,13 +257,13 @@ timetravel(PG_FUNCTION_ARGS)
 	 * If DELETE/UPDATE of tuple with stop_date neq INFINITY then say upper
 	 * Executor to skip operation for this tuple
 	 */
-	if (newtuple != NULL)
+	if (newslot != NULL)
 	{							/* UPDATE */
-		newtimeon = SPI_getbinval(newtuple, tupdesc, attnum[a_time_on], &isnull);
+		newtimeon = SPI_getbinval(newslot, attnum[a_time_on], &isnull);
 		if (isnull)
 			elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_on]);
 
-		newtimeoff = SPI_getbinval(newtuple, tupdesc, attnum[a_time_off], &isnull);
+		newtimeoff = SPI_getbinval(newslot, attnum[a_time_off], &isnull);
 		if (isnull)
 			elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_off]);
 
@@ -286,7 +288,7 @@ timetravel(PG_FUNCTION_ARGS)
 	cnulls = (char *) palloc(natts * sizeof(char));
 	for (i = 0; i < natts; i++)
 	{
-		cvals[i] = SPI_getbinval(trigtuple, tupdesc, i + 1, &isnull);
+		cvals[i] = SPI_getbinval(trigslot, i + 1, &isnull);
 		cnulls[i] = (isnull) ? 'n' : ' ';
 	}
 
@@ -294,7 +296,7 @@ timetravel(PG_FUNCTION_ARGS)
 	cvals[attnum[a_time_off] - 1] = newtimeoff; /* stop_date eq current date */
 	cnulls[attnum[a_time_off] - 1] = ' ';
 
-	if (!newtuple)
+	if (!newslot)
 	{							/* DELETE */
 		if (argc == MaxAttrNum)
 		{
@@ -362,7 +364,7 @@ timetravel(PG_FUNCTION_ARGS)
 		elog(ERROR, "timetravel (%s): SPI_execp returned %d", relname, ret);
 
 	/* Tuple to return to upper Executor ... */
-	if (newtuple)
+	if (newslot)
 	{							/* UPDATE */
 		int			chnattrs = 0;
 		int			chattrs[MaxAttrNum];
@@ -402,11 +404,11 @@ timetravel(PG_FUNCTION_ARGS)
 		 * Use SPI_modifytuple() here because we are inside SPI environment
 		 * but rettuple must be allocated in caller's context.
 		 */
-		rettuple = SPI_modifytuple(rel, newtuple, chnattrs, chattrs, newvals, newnulls);
+		rettuple = SPI_modifytuple(rel, newslot->tts_tuple, chnattrs, chattrs, newvals, newnulls);
 	}
 	else
 		/* DELETE case */
-		rettuple = trigtuple;
+		rettuple = trigslot->tts_tuple;
 
 	SPI_finish();				/* don't forget say Bye to SPI mgr */
 
diff --git a/contrib/tablefunc/tablefunc.c b/contrib/tablefunc/tablefunc.c
index 0bc8177..cd11947 100644
--- a/contrib/tablefunc/tablefunc.c
+++ b/contrib/tablefunc/tablefunc.c
@@ -492,7 +492,7 @@ crosstab(PG_FUNCTION_ARGS)
 		 */
 		for (i = 0; i < num_categories; i++)
 		{
-			HeapTuple	spi_tuple;
+			TupleTableSlot*	spislot;
 			char	   *rowid;
 
 			/* see if we've gone too far already */
@@ -500,10 +500,10 @@ crosstab(PG_FUNCTION_ARGS)
 				break;
 
 			/* get the next sql result tuple */
-			spi_tuple = spi_tuptable->vals[call_cntr];
+			spislot = spi_tuptable->vals[call_cntr];
 
 			/* get the rowid from the current sql result tuple */
-			rowid = SPI_getvalue(spi_tuple, spi_tupdesc, 1);
+			rowid = SPI_getvalue(spislot, 1);
 
 			/*
 			 * If this is the first pass through the values for this rowid,
@@ -538,7 +538,7 @@ crosstab(PG_FUNCTION_ARGS)
 				 * Be careful to assign the value to the array index based on
 				 * which category we are presently processing.
 				 */
-				values[1 + i] = SPI_getvalue(spi_tuple, spi_tupdesc, 3);
+				values[1 + i] = SPI_getvalue(spislot, 3);
 
 				/*
 				 * increment the counter since we consume a row for each
@@ -756,13 +756,13 @@ load_categories_hash(char *cats_sql, MemoryContext per_query_ctx)
 		{
 			crosstab_cat_desc *catdesc;
 			char	   *catname;
-			HeapTuple	spi_tuple;
+			TupleTableSlot*	spislot;
 
 			/* get the next sql result tuple */
-			spi_tuple = spi_tuptable->vals[i];
+			spislot = spi_tuptable->vals[i];
 
 			/* get the category from the current sql result tuple */
-			catname = SPI_getvalue(spi_tuple, spi_tupdesc, 1);
+			catname = SPI_getvalue(spislot, 1);
 
 			SPIcontext = MemoryContextSwitchTo(per_query_ctx);
 
@@ -875,15 +875,15 @@ get_crosstab_tuplestore(char *sql,
 
 		for (i = 0; i < proc; i++)
 		{
-			HeapTuple	spi_tuple;
+			TupleTableSlot*	spislot;
 			crosstab_cat_desc *catdesc;
 			char	   *catname;
 
 			/* get the next sql result tuple */
-			spi_tuple = spi_tuptable->vals[i];
+			spislot = spi_tuptable->vals[i];
 
 			/* get the rowid from the current sql result tuple */
-			rowid = SPI_getvalue(spi_tuple, spi_tupdesc, 1);
+			rowid = SPI_getvalue(spislot, 1);
 
 			/*
 			 * if we're on a new output row, grab the column values up to
@@ -908,14 +908,14 @@ get_crosstab_tuplestore(char *sql,
 
 				values[0] = rowid;
 				for (j = 1; j < ncols - 2; j++)
-					values[j] = SPI_getvalue(spi_tuple, spi_tupdesc, j + 1);
+					values[j] = SPI_getvalue(spislot, j + 1);
 
 				/* we're no longer on the first pass */
 				firstpass = false;
 			}
 
 			/* look up the category and fill in the appropriate column */
-			catname = SPI_getvalue(spi_tuple, spi_tupdesc, ncols - 1);
+			catname = SPI_getvalue(spislot, ncols - 1);
 
 			if (catname != NULL)
 			{
@@ -923,7 +923,7 @@ get_crosstab_tuplestore(char *sql,
 
 				if (catdesc)
 					values[catdesc->attidx + ncols - 2] =
-						SPI_getvalue(spi_tuple, spi_tupdesc, ncols);
+						SPI_getvalue(spislot, ncols);
 			}
 
 			xpfree(lastrowid);
@@ -1310,7 +1310,7 @@ build_tuplestore_recursively(char *key_fld,
 	/* Check for qualifying tuples */
 	if ((ret == SPI_OK_SELECT) && (proc > 0))
 	{
-		HeapTuple	spi_tuple;
+		TupleTableSlot*	spislot;
 		SPITupleTable *tuptable = SPI_tuptable;
 		TupleDesc	spi_tupdesc = tuptable->tupdesc;
 		uint64		i;
@@ -1335,13 +1335,13 @@ build_tuplestore_recursively(char *key_fld,
 			appendStringInfo(&chk_branchstr, "%s%s%s", branch_delim, branch, branch_delim);
 
 			/* get the next sql result tuple */
-			spi_tuple = tuptable->vals[i];
+			spislot = tuptable->vals[i];
 
 			/* get the current key (might be NULL) */
-			current_key = SPI_getvalue(spi_tuple, spi_tupdesc, 1);
+			current_key = SPI_getvalue(spislot, 1);
 
 			/* get the parent key (might be NULL) */
-			current_key_parent = SPI_getvalue(spi_tuple, spi_tupdesc, 2);
+			current_key_parent = SPI_getvalue(spislot, 2);
 
 			/* get the current level */
 			sprintf(current_level, "%d", level);
diff --git a/contrib/tcn/tcn.c b/contrib/tcn/tcn.c
index 0b9acbf..c311a3b 100644
--- a/contrib/tcn/tcn.c
+++ b/contrib/tcn/tcn.c
@@ -58,7 +58,7 @@ triggered_change_notification(PG_FUNCTION_ARGS)
 	TriggerData *trigdata = (TriggerData *) fcinfo->context;
 	Trigger    *trigger;
 	int			nargs;
-	HeapTuple	trigtuple;
+	TupleTableSlot*	trigslot;
 	Relation	rel;
 	TupleDesc	tupdesc;
 	char	   *channel;
@@ -112,7 +112,7 @@ triggered_change_notification(PG_FUNCTION_ARGS)
 		channel = trigger->tgargs[0];
 
 	/* get tuple data */
-	trigtuple = trigdata->tg_trigtuple;
+	trigslot = trigdata->tg_trigslot;
 	rel = trigdata->tg_relation;
 	tupdesc = rel->rd_att;
 
@@ -157,7 +157,7 @@ triggered_change_notification(PG_FUNCTION_ARGS)
 					appendStringInfoCharMacro(payload, ',');
 					strcpy_quoted(payload, NameStr((tupdesc->attrs[colno - 1])->attname), '"');
 					appendStringInfoCharMacro(payload, '=');
-					strcpy_quoted(payload, SPI_getvalue(trigtuple, tupdesc, colno), '\'');
+					strcpy_quoted(payload, SPI_getvalue(trigslot, colno), '\'');
 				}
 
 				Async_Notify(channel, payload->data);
diff --git a/contrib/xml2/xpath.c b/contrib/xml2/xpath.c
index 95e580d..b344d1f 100644
--- a/contrib/xml2/xpath.c
+++ b/contrib/xml2/xpath.c
@@ -533,7 +533,7 @@ xpath_table(PG_FUNCTION_ARGS)
 
 	/* SPI (input tuple) support */
 	SPITupleTable *tuptable;
-	HeapTuple	spi_tuple;
+	TupleTableSlot*	spislot;
 	TupleDesc	spi_tupdesc;
 
 	/* Output tuple (tuplestore) support */
@@ -702,9 +702,9 @@ xpath_table(PG_FUNCTION_ARGS)
 			xmlXPathCompExprPtr comppath;
 
 			/* Extract the row data as C Strings */
-			spi_tuple = tuptable->vals[i];
-			pkey = SPI_getvalue(spi_tuple, spi_tupdesc, 1);
-			xmldoc = SPI_getvalue(spi_tuple, spi_tupdesc, 2);
+			spislot = tuptable->vals[i];
+			pkey = SPI_getvalue(spislot, 1);
+			xmldoc = SPI_getvalue(spislot, 2);
 
 			/*
 			 * Clear the values array, so that not-well-formed documents
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index b3f0b3c..d09a0fa 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -564,7 +564,7 @@ boot_openrel(char *relname)
 	struct typmap **app;
 	Relation	rel;
 	HeapScanDesc scan;
-	HeapTuple	tup;
+	HeapTuple tup;
 
 	if (strlen(relname) >= NAMEDATALEN)
 		relname[NAMEDATALEN - 1] = '\0';
@@ -880,7 +880,7 @@ gettype(char *type)
 	int			i;
 	Relation	rel;
 	HeapScanDesc scan;
-	HeapTuple	tup;
+	HeapTuple tup;
 	struct typmap **app;
 
 	if (Typ != NULL)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index ccde66a..38a1d06 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -789,7 +789,7 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 					ScanKeyData key[1];
 					Relation	rel;
 					HeapScanDesc scan;
-					HeapTuple	tuple;
+					HeapTuple tup;
 
 					ScanKeyInit(&key[0],
 								Anum_pg_proc_pronamespace,
@@ -799,9 +799,9 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 					rel = heap_open(ProcedureRelationId, AccessShareLock);
 					scan = heap_beginscan_catalog(rel, 1, key);
 
-					while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+					while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
 					{
-						objects = lappend_oid(objects, HeapTupleGetOid(tuple));
+						objects = lappend_oid(objects, HeapTupleGetOid(tup));
 					}
 
 					heap_endscan(scan);
@@ -830,7 +830,7 @@ getRelationsInNamespace(Oid namespaceId, char relkind)
 	ScanKeyData key[2];
 	Relation	rel;
 	HeapScanDesc scan;
-	HeapTuple	tuple;
+	HeapTuple tup;
 
 	ScanKeyInit(&key[0],
 				Anum_pg_class_relnamespace,
@@ -844,9 +844,9 @@ getRelationsInNamespace(Oid namespaceId, char relkind)
 	rel = heap_open(RelationRelationId, AccessShareLock);
 	scan = heap_beginscan_catalog(rel, 2, key);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
 	{
-		relations = lappend_oid(relations, HeapTupleGetOid(tuple));
+		relations = lappend_oid(relations, HeapTupleGetOid(tup));
 	}
 
 	heap_endscan(scan);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 027abd5..5f807ae 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2641,11 +2641,11 @@ IndexCheckExclusion(Relation heapRelation,
 					IndexInfo *indexInfo)
 {
 	HeapScanDesc scan;
-	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
 	ExprState  *predicate;
 	TupleTableSlot *slot;
+	HeapTuple heapTuple;
 	EState	   *estate;
 	ExprContext *econtext;
 	Snapshot	snapshot;
@@ -2715,7 +2715,7 @@ IndexCheckExclusion(Relation heapRelation,
 		 */
 		check_exclusion_constraint(heapRelation,
 								   indexRelation, indexInfo,
-								   &(heapTuple->t_self), values, isnull,
+								   &(slot->tts_tid), values, isnull,
 								   estate, true);
 	}
 
diff --git a/src/backend/catalog/pg_conversion.c b/src/backend/catalog/pg_conversion.c
index 5746dc3..5fbef3f 100644
--- a/src/backend/catalog/pg_conversion.c
+++ b/src/backend/catalog/pg_conversion.c
@@ -149,7 +149,7 @@ void
 RemoveConversionById(Oid conversionOid)
 {
 	Relation	rel;
-	HeapTuple	tuple;
+	HeapTuple tuple;
 	HeapScanDesc scan;
 	ScanKeyData scanKeyData;
 
diff --git a/src/backend/catalog/pg_db_role_setting.c b/src/backend/catalog/pg_db_role_setting.c
index 323471b..52e0de7 100644
--- a/src/backend/catalog/pg_db_role_setting.c
+++ b/src/backend/catalog/pg_db_role_setting.c
@@ -172,7 +172,7 @@ DropSetting(Oid databaseid, Oid roleid)
 	Relation	relsetting;
 	HeapScanDesc scan;
 	ScanKeyData keys[2];
-	HeapTuple	tup;
+	HeapTuple tup;
 	int			numkeys = 0;
 
 	relsetting = heap_open(DbRoleSettingRelationId, RowExclusiveLock);
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 3ef7ba8..59abef4 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;
-	HeapTuple	tuple;
+	HeapTuple tuple;
 	List	   *result = NIL;
 
 	classRel = heap_open(RelationRelationId, AccessShareLock);
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index fb53d71..a3eb79d 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -378,7 +378,7 @@ RemoveSubscriptionRel(Oid subid, Oid relid)
 	Relation	rel;
 	HeapScanDesc scan;
 	ScanKeyData skey[2];
-	HeapTuple	tup;
+	HeapTuple tup;
 	int			nkeys = 0;
 
 	rel = heap_open(SubscriptionRelRelationId, RowExclusiveLock);
diff --git a/src/backend/commands/constraint.c b/src/backend/commands/constraint.c
index e2544e5..02a8dc5 100644
--- a/src/backend/commands/constraint.c
+++ b/src/backend/commands/constraint.c
@@ -39,7 +39,7 @@ unique_key_recheck(PG_FUNCTION_ARGS)
 {
 	TriggerData *trigdata = castNode(TriggerData, fcinfo->context);
 	const char *funcname = "unique_key_recheck";
-	HeapTuple	new_row;
+	TupleTableSlot *newslot;
 	ItemPointerData tmptid;
 	Relation	indexRel;
 	IndexInfo  *indexInfo;
@@ -71,16 +71,16 @@ unique_key_recheck(PG_FUNCTION_ARGS)
 	 * Get the new data that was inserted/updated.
 	 */
 	if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
-		new_row = trigdata->tg_trigtuple;
+		newslot = trigdata->tg_trigslot;
 	else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
-		new_row = trigdata->tg_newtuple;
+		newslot = trigdata->tg_newslot;
 	else
 	{
 		ereport(ERROR,
 				(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
 				 errmsg("function \"%s\" must be fired for INSERT or UPDATE",
 						funcname)));
-		new_row = NULL;			/* keep compiler quiet */
+		newslot = NULL;			/* keep compiler quiet */
 	}
 
 	/*
@@ -101,7 +101,7 @@ unique_key_recheck(PG_FUNCTION_ARGS)
 	 * it's possible the index entry has also been marked dead, and even
 	 * removed.
 	 */
-	tmptid = new_row->t_self;
+	tmptid = newslot->tts_tid;
 	if (!heap_hot_search(&tmptid, trigdata->tg_relation, SnapshotSelf, NULL))
 	{
 		/*
@@ -124,7 +124,7 @@ unique_key_recheck(PG_FUNCTION_ARGS)
 	 */
 	slot = MakeSingleTupleTableSlot(RelationGetDescr(trigdata->tg_relation));
 
-	ExecStoreTuple(new_row, slot, InvalidBuffer, false);
+	ExecCopySlot(slot, newslot);
 
 	/*
 	 * Typically the index won't have expressions, but if it does we need an
@@ -164,7 +164,7 @@ unique_key_recheck(PG_FUNCTION_ARGS)
 		 * correct even if t_self is now dead, because that is the TID the
 		 * index will know about.
 		 */
-		index_insert(indexRel, values, isnull, &(new_row->t_self),
+		index_insert(indexRel, values, isnull, &tmptid,
 					 trigdata->tg_relation, UNIQUE_CHECK_EXISTING,
 					 indexInfo);
 	}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index fc5f4f6..5d3f22e 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2068,13 +2068,9 @@ CopyTo(CopyState cstate)
 
 	if (cstate->rel)
 	{
-		Datum	   *values;
-		bool	   *nulls;
 		HeapScanDesc scandesc;
-		HeapTuple	tuple;
-
-		values = (Datum *) palloc(num_phys_attrs * sizeof(Datum));
-		nulls = (bool *) palloc(num_phys_attrs * sizeof(bool));
+		HeapTuple tuple;
+		TupleTableSlot *slot = MakeSingleTupleTableSlot(RelationGetDescr(cstate->rel));
 
 		scandesc = heap_beginscan(cstate->rel, GetActiveSnapshot(), 0, NULL);
 
@@ -2083,18 +2079,15 @@ CopyTo(CopyState cstate)
 		{
 			CHECK_FOR_INTERRUPTS();
 
-			/* Deconstruct the tuple ... faster than repeated heap_getattr */
-			heap_deform_tuple(tuple, tupDesc, values, nulls);
+			ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+			slot_getallattrs(slot);
 
 			/* Format and send the data */
-			CopyOneRowTo(cstate, HeapTupleGetOid(tuple), values, nulls);
+			CopyOneRowTo(cstate, HeapTupleGetOid(slot->tts_tuple), slot->tts_values, slot->tts_isnull);
 			processed++;
 		}
 
 		heap_endscan(scandesc);
-
-		pfree(values);
-		pfree(nulls);
 	}
 	else
 	{
@@ -2752,6 +2745,7 @@ CopyFrom(CopyState cstate)
 					heap_insert(resultRelInfo->ri_RelationDesc, tuple, mycid,
 								hi_options, bistate);
 
+					ItemPointerCopy(&(tuple->t_self), &(slot->tts_tid));
 					if (resultRelInfo->ri_NumIndices > 0)
 						recheckIndexes = ExecInsertIndexTuples(slot,
 															   &(tuple->t_self),
@@ -2761,7 +2755,7 @@ CopyFrom(CopyState cstate)
 															   NIL);
 
 					/* AFTER ROW INSERT Triggers */
-					ExecARInsertTriggers(estate, resultRelInfo, tuple,
+					ExecARInsertTriggers(estate, resultRelInfo, slot,
 										 recheckIndexes, cstate->transition_capture);
 
 					list_free(recheckIndexes);
@@ -2910,11 +2904,12 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 
 			cstate->cur_lineno = firstBufferedLineNo + i;
 			ExecStoreTuple(bufferedTuples[i], myslot, InvalidBuffer, false);
+			ItemPointerCopy(&(bufferedTuples[i]->t_self), &(myslot->tts_tid));
 			recheckIndexes =
 				ExecInsertIndexTuples(myslot, &(bufferedTuples[i]->t_self),
 									  estate, false, NULL, NIL);
 			ExecARInsertTriggers(estate, resultRelInfo,
-								 bufferedTuples[i],
+								 myslot,
 								 recheckIndexes, cstate->transition_capture);
 			list_free(recheckIndexes);
 		}
@@ -2931,8 +2926,10 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 		for (i = 0; i < nBufferedTuples; i++)
 		{
 			cstate->cur_lineno = firstBufferedLineNo + i;
+			ExecStoreTuple(bufferedTuples[i], myslot, InvalidBuffer, false);
+			ItemPointerCopy(&(bufferedTuples[i]->t_self), &(myslot->tts_tid));
 			ExecARInsertTriggers(estate, resultRelInfo,
-								 bufferedTuples[i],
+								 myslot,
 								 NIL, cstate->transition_capture);
 		}
 	}
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index e138539..e12c1db 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -590,6 +590,7 @@ 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)
 		{
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 620704e..675b348 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1876,7 +1876,7 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	Relation	relationRelation;
 	HeapScanDesc scan;
 	ScanKeyData scan_keys[1];
-	HeapTuple	tuple;
+	HeapTuple tuple;
 	MemoryContext private_context;
 	MemoryContext old;
 	List	   *relids = NIL;
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 7d57f97..062665e 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -678,7 +678,7 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 				 errmsg("new data for materialized view \"%s\" contains duplicate rows without any null columns",
 						RelationGetRelationName(matviewRel)),
 				 errdetail("Row: %s",
-						   SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1))));
+			SPI_getvalue(SPI_tuptable->vals[0], 1))));
 	}
 
 	SetUserIdAndSecContext(relowner,
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index bb00858..c47a69f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8090,7 +8090,6 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	char	   *conbin;
 	Expr	   *origexpr;
 	ExprState  *exprstate;
-	TupleDesc	tupdesc;
 	HeapScanDesc scan;
 	HeapTuple	tuple;
 	ExprContext *econtext;
@@ -8127,8 +8126,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	exprstate = ExecPrepareExpr(origexpr, estate);
 
 	econtext = GetPerTupleExprContext(estate);
-	tupdesc = RelationGetDescr(rel);
-	slot = MakeSingleTupleTableSlot(tupdesc);
+	slot = MakeSingleTupleTableSlot(RelationGetDescr(rel));
 	econtext->ecxt_scantuple = slot;
 
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
@@ -8157,7 +8155,6 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	MemoryContextSwitchTo(oldcxt);
 	heap_endscan(scan);
 	UnregisterSnapshot(snapshot);
-	ExecDropSingleTupleTableSlot(slot);
 	FreeExecutorState(estate);
 }
 
@@ -8216,6 +8213,9 @@ validateForeignKeyConstraint(char *conname,
 	{
 		FunctionCallInfoData fcinfo;
 		TriggerData trigdata;
+		TupleTableSlot *slot = MakeSingleTupleTableSlot(RelationGetDescr(rel));
+
+		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
 		/*
 		 * Make a call to the trigger function
@@ -8230,11 +8230,9 @@ validateForeignKeyConstraint(char *conname,
 		trigdata.type = T_TriggerData;
 		trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW;
 		trigdata.tg_relation = rel;
-		trigdata.tg_trigtuple = tuple;
-		trigdata.tg_newtuple = NULL;
+		trigdata.tg_trigslot = slot;
+		trigdata.tg_newslot = NULL;
 		trigdata.tg_trigger = &trig;
-		trigdata.tg_trigtuplebuf = scan->rs_cbuf;
-		trigdata.tg_newtuplebuf = InvalidBuffer;
 
 		fcinfo.context = (Node *) &trigdata;
 
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index b502941..38b7215 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -88,7 +88,7 @@ static HeapTuple GetTupleForTrigger(EState *estate,
 static bool TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
 			   Trigger *trigger, TriggerEvent event,
 			   Bitmapset *modifiedCols,
-			   HeapTuple oldtup, HeapTuple newtup);
+			   TupleTableSlot *oldslot, TupleTableSlot *newslot);
 static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
 					int tgindx,
 					FmgrInfo *finfo,
@@ -96,7 +96,7 @@ static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
 					MemoryContext per_tuple_context);
 static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 					  int event, bool row_trigger,
-					  HeapTuple oldtup, HeapTuple newtup,
+					  TupleTableSlot *oldslot, TupleTableSlot *newslot,
 					  List *recheckIndexes, Bitmapset *modifiedCols,
 					  TransitionCaptureState *transition_capture);
 static void AfterTriggerEnlargeQueryState(void);
@@ -2298,12 +2298,10 @@ ExecBSInsertTriggers(EState *estate, ResultRelInfo *relinfo)
 	LocTriggerData.tg_event = TRIGGER_EVENT_INSERT |
 		TRIGGER_EVENT_BEFORE;
 	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
-	LocTriggerData.tg_trigtuple = NULL;
-	LocTriggerData.tg_newtuple = NULL;
+	LocTriggerData.tg_trigslot = NULL;
+	LocTriggerData.tg_newslot = NULL;
 	LocTriggerData.tg_oldtable = NULL;
 	LocTriggerData.tg_newtable = NULL;
-	LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
-	LocTriggerData.tg_newtuplebuf = InvalidBuffer;
 	for (i = 0; i < trigdesc->numtriggers; i++)
 	{
 		Trigger    *trigger = &trigdesc->triggers[i];
@@ -2359,10 +2357,9 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 		TRIGGER_EVENT_ROW |
 		TRIGGER_EVENT_BEFORE;
 	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
-	LocTriggerData.tg_newtuple = NULL;
+	LocTriggerData.tg_newslot = NULL;
 	LocTriggerData.tg_oldtable = NULL;
 	LocTriggerData.tg_newtable = NULL;
-	LocTriggerData.tg_newtuplebuf = InvalidBuffer;
 	for (i = 0; i < trigdesc->numtriggers; i++)
 	{
 		Trigger    *trigger = &trigdesc->triggers[i];
@@ -2373,21 +2370,22 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 								  TRIGGER_TYPE_INSERT))
 			continue;
 		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
-							NULL, NULL, newtuple))
+							NULL, NULL, slot))
 			continue;
 
-		LocTriggerData.tg_trigtuple = oldtuple = newtuple;
-		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
+		oldtuple = newtuple;
+		LocTriggerData.tg_trigslot = slot;
 		LocTriggerData.tg_trigger = trigger;
 		newtuple = ExecCallTriggerFunc(&LocTriggerData,
 									   i,
 									   relinfo->ri_TrigFunctions,
 									   relinfo->ri_TrigInstrument,
 									   GetPerTupleMemoryContext(estate));
-		if (oldtuple != newtuple && oldtuple != slottuple)
-			heap_freetuple(oldtuple);
 		if (newtuple == NULL)
 			return NULL;		/* "do nothing" */
+
+		if (oldtuple != newtuple) //hari
+			ExecStoreTuple(newtuple, slot, InvalidBuffer, true);
 	}
 
 	if (newtuple != slottuple)
@@ -2411,7 +2409,7 @@ 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;
@@ -2419,7 +2417,7 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 	if ((trigdesc && trigdesc->trig_insert_after_row) ||
 		(transition_capture && transition_capture->tcs_insert_new_table))
 		AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT,
-							  true, NULL, trigtuple,
+							  true, NULL, slot,
 							  recheckIndexes, NULL,
 							  transition_capture);
 }
@@ -2440,10 +2438,9 @@ ExecIRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 		TRIGGER_EVENT_ROW |
 		TRIGGER_EVENT_INSTEAD;
 	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
-	LocTriggerData.tg_newtuple = NULL;
+	LocTriggerData.tg_newslot = NULL;
 	LocTriggerData.tg_oldtable = NULL;
 	LocTriggerData.tg_newtable = NULL;
-	LocTriggerData.tg_newtuplebuf = InvalidBuffer;
 	for (i = 0; i < trigdesc->numtriggers; i++)
 	{
 		Trigger    *trigger = &trigdesc->triggers[i];
@@ -2454,21 +2451,22 @@ ExecIRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 								  TRIGGER_TYPE_INSERT))
 			continue;
 		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
-							NULL, NULL, newtuple))
+							NULL, NULL, slot))
 			continue;
 
-		LocTriggerData.tg_trigtuple = oldtuple = newtuple;
-		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
+		oldtuple = newtuple;
+		LocTriggerData.tg_trigslot = slot;
 		LocTriggerData.tg_trigger = trigger;
 		newtuple = ExecCallTriggerFunc(&LocTriggerData,
 									   i,
 									   relinfo->ri_TrigFunctions,
 									   relinfo->ri_TrigInstrument,
 									   GetPerTupleMemoryContext(estate));
-		if (oldtuple != newtuple && oldtuple != slottuple)
-			heap_freetuple(oldtuple);
 		if (newtuple == NULL)
 			return NULL;		/* "do nothing" */
+
+		if (oldtuple != newtuple) //hari
+			ExecStoreTuple(newtuple, slot, InvalidBuffer, true);
 	}
 
 	if (newtuple != slottuple)
@@ -2508,12 +2506,10 @@ ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
 	LocTriggerData.tg_event = TRIGGER_EVENT_DELETE |
 		TRIGGER_EVENT_BEFORE;
 	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
-	LocTriggerData.tg_trigtuple = NULL;
-	LocTriggerData.tg_newtuple = NULL;
+	LocTriggerData.tg_trigslot = NULL;
+	LocTriggerData.tg_newslot = NULL;
 	LocTriggerData.tg_oldtable = NULL;
 	LocTriggerData.tg_newtable = NULL;
-	LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
-	LocTriggerData.tg_newtuplebuf = InvalidBuffer;
 	for (i = 0; i < trigdesc->numtriggers; i++)
 	{
 		Trigger    *trigger = &trigdesc->triggers[i];
@@ -2557,7 +2553,7 @@ bool
 ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
 					 ResultRelInfo *relinfo,
 					 ItemPointer tupleid,
-					 HeapTuple fdw_trigtuple)
+					 TupleTableSlot *trigslot)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 	bool		result = true;
@@ -2565,28 +2561,31 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
 	HeapTuple	trigtuple;
 	HeapTuple	newtuple;
 	TupleTableSlot *newSlot;
+	TupleTableSlot *oldslot = trigslot;
 	int			i;
 
-	Assert(HeapTupleIsValid(fdw_trigtuple) ^ ItemPointerIsValid(tupleid));
-	if (fdw_trigtuple == NULL)
+	Assert(PointerIsValid(trigslot) ^ ItemPointerIsValid(tupleid));
+	if (trigslot == NULL)
 	{
 		trigtuple = GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
 									   LockTupleExclusive, &newSlot);
 		if (trigtuple == NULL)
 			return false;
+
+		oldslot = MakeSingleTupleTableSlot(RelationGetDescr(relinfo->ri_RelationDesc));
+		ExecStoreTuple(trigtuple, oldslot, InvalidBuffer, false);
 	}
 	else
-		trigtuple = fdw_trigtuple;
+		trigtuple = ExecMaterializeSlot(trigslot);
 
 	LocTriggerData.type = T_TriggerData;
 	LocTriggerData.tg_event = TRIGGER_EVENT_DELETE |
 		TRIGGER_EVENT_ROW |
 		TRIGGER_EVENT_BEFORE;
 	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
-	LocTriggerData.tg_newtuple = NULL;
+	LocTriggerData.tg_newslot = NULL;
 	LocTriggerData.tg_oldtable = NULL;
 	LocTriggerData.tg_newtable = NULL;
-	LocTriggerData.tg_newtuplebuf = InvalidBuffer;
 	for (i = 0; i < trigdesc->numtriggers; i++)
 	{
 		Trigger    *trigger = &trigdesc->triggers[i];
@@ -2597,11 +2596,10 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
 								  TRIGGER_TYPE_DELETE))
 			continue;
 		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
-							NULL, trigtuple, NULL))
+							NULL, oldslot, NULL))
 			continue;
 
-		LocTriggerData.tg_trigtuple = trigtuple;
-		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
+		LocTriggerData.tg_trigslot = oldslot;
 		LocTriggerData.tg_trigger = trigger;
 		newtuple = ExecCallTriggerFunc(&LocTriggerData,
 									   i,
@@ -2616,8 +2614,9 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
 		if (newtuple != trigtuple)
 			heap_freetuple(newtuple);
 	}
-	if (trigtuple != fdw_trigtuple)
-		heap_freetuple(trigtuple);
+
+	if (trigslot != oldslot)
+		ExecDropSingleTupleTableSlot(oldslot);
 
 	return result;
 }
@@ -2625,7 +2624,7 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
 void
 ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
 					 ItemPointer tupleid,
-					 HeapTuple fdw_trigtuple,
+					 TupleTableSlot *slot,
 					 TransitionCaptureState *transition_capture)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
@@ -2634,33 +2633,38 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
 		(transition_capture && transition_capture->tcs_delete_old_table))
 	{
 		HeapTuple	trigtuple;
+		TupleTableSlot *oldslot = slot;
 
-		Assert(HeapTupleIsValid(fdw_trigtuple) ^ ItemPointerIsValid(tupleid));
-		if (fdw_trigtuple == NULL)
+		Assert(PointerIsValid(slot) ^ ItemPointerIsValid(tupleid));
+		if (oldslot == NULL)
+		{
 			trigtuple = GetTupleForTrigger(estate,
 										   NULL,
 										   relinfo,
 										   tupleid,
 										   LockTupleExclusive,
 										   NULL);
-		else
-			trigtuple = fdw_trigtuple;
+			oldslot = MakeSingleTupleTableSlot(RelationGetDescr(relinfo->ri_RelationDesc));
+			ExecStoreTuple(trigtuple, oldslot, InvalidBuffer, false);
+		}
 
 		AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_DELETE,
-							  true, trigtuple, NULL, NIL, NULL,
+							  true, oldslot, NULL, NIL, NULL,
 							  transition_capture);
-		if (trigtuple != fdw_trigtuple)
-			heap_freetuple(trigtuple);
+
+		if (oldslot != slot)
+			ExecDropSingleTupleTableSlot(oldslot);
 	}
 }
 
 bool
 ExecIRDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
-					 HeapTuple trigtuple)
+					 TupleTableSlot *slot)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 	TriggerData LocTriggerData;
 	HeapTuple	rettuple;
+	HeapTuple	trigtuple = ExecMaterializeSlot(slot);
 	int			i;
 
 	LocTriggerData.type = T_TriggerData;
@@ -2668,10 +2672,9 @@ ExecIRDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
 		TRIGGER_EVENT_ROW |
 		TRIGGER_EVENT_INSTEAD;
 	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
-	LocTriggerData.tg_newtuple = NULL;
+	LocTriggerData.tg_newslot = NULL;
 	LocTriggerData.tg_oldtable = NULL;
 	LocTriggerData.tg_newtable = NULL;
-	LocTriggerData.tg_newtuplebuf = InvalidBuffer;
 	for (i = 0; i < trigdesc->numtriggers; i++)
 	{
 		Trigger    *trigger = &trigdesc->triggers[i];
@@ -2682,11 +2685,10 @@ ExecIRDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
 								  TRIGGER_TYPE_DELETE))
 			continue;
 		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
-							NULL, trigtuple, NULL))
+							NULL, slot, NULL))
 			continue;
 
-		LocTriggerData.tg_trigtuple = trigtuple;
-		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
+		LocTriggerData.tg_trigslot = slot;
 		LocTriggerData.tg_trigger = trigger;
 		rettuple = ExecCallTriggerFunc(&LocTriggerData,
 									   i,
@@ -2694,10 +2696,14 @@ ExecIRDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
 									   relinfo->ri_TrigInstrument,
 									   GetPerTupleMemoryContext(estate));
 		if (rettuple == NULL)
+		{
+			ExecDropSingleTupleTableSlot(slot);
 			return false;		/* Delete was suppressed */
+		}
 		if (rettuple != trigtuple)
 			heap_freetuple(rettuple);
 	}
+
 	return true;
 }
 
@@ -2722,12 +2728,10 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
 	LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
 		TRIGGER_EVENT_BEFORE;
 	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
-	LocTriggerData.tg_trigtuple = NULL;
-	LocTriggerData.tg_newtuple = NULL;
+	LocTriggerData.tg_trigslot = NULL;
+	LocTriggerData.tg_newslot = NULL;
 	LocTriggerData.tg_oldtable = NULL;
 	LocTriggerData.tg_newtable = NULL;
-	LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
-	LocTriggerData.tg_newtuplebuf = InvalidBuffer;
 	for (i = 0; i < trigdesc->numtriggers; i++)
 	{
 		Trigger    *trigger = &trigdesc->triggers[i];
@@ -2773,16 +2777,16 @@ TupleTableSlot *
 ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 					 ResultRelInfo *relinfo,
 					 ItemPointer tupleid,
-					 HeapTuple fdw_trigtuple,
+					 TupleTableSlot *trigslot,
 					 TupleTableSlot *slot)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 	HeapTuple	slottuple = ExecMaterializeSlot(slot);
 	HeapTuple	newtuple = slottuple;
 	TriggerData LocTriggerData;
-	HeapTuple	trigtuple;
 	HeapTuple	oldtuple;
 	TupleTableSlot *newSlot;
+	TupleTableSlot *oldSlot = trigslot;
 	int			i;
 	Bitmapset  *updatedCols;
 	LockTupleMode lockmode;
@@ -2790,18 +2794,22 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 	/* Determine lock mode to use */
 	lockmode = ExecUpdateLockMode(estate, relinfo);
 
-	Assert(HeapTupleIsValid(fdw_trigtuple) ^ ItemPointerIsValid(tupleid));
-	if (fdw_trigtuple == NULL)
+	Assert(PointerIsValid(trigslot) ^ ItemPointerIsValid(tupleid));
+	if (trigslot == NULL)
 	{
+		HeapTuple	trigtuple;
+
 		/* get a copy of the on-disk tuple we are planning to update */
 		trigtuple = GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
 									   lockmode, &newSlot);
 		if (trigtuple == NULL)
 			return NULL;		/* cancel the update action */
+
+		oldSlot = MakeSingleTupleTableSlot(RelationGetDescr(relinfo->ri_RelationDesc));
+		ExecStoreTuple(trigtuple, oldSlot, InvalidBuffer, true);
 	}
 	else
 	{
-		trigtuple = fdw_trigtuple;
 		newSlot = NULL;
 	}
 
@@ -2842,30 +2850,32 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 								  TRIGGER_TYPE_UPDATE))
 			continue;
 		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
-							updatedCols, trigtuple, newtuple))
+							updatedCols, oldSlot, slot))
 			continue;
 
-		LocTriggerData.tg_trigtuple = trigtuple;
-		LocTriggerData.tg_newtuple = oldtuple = newtuple;
-		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
-		LocTriggerData.tg_newtuplebuf = InvalidBuffer;
+		oldtuple = newtuple;
+		LocTriggerData.tg_trigslot = oldSlot;
+		LocTriggerData.tg_newslot = slot;
 		LocTriggerData.tg_trigger = trigger;
 		newtuple = ExecCallTriggerFunc(&LocTriggerData,
 									   i,
 									   relinfo->ri_TrigFunctions,
 									   relinfo->ri_TrigInstrument,
 									   GetPerTupleMemoryContext(estate));
-		if (oldtuple != newtuple && oldtuple != slottuple)
-			heap_freetuple(oldtuple);
 		if (newtuple == NULL)
 		{
-			if (trigtuple != fdw_trigtuple)
-				heap_freetuple(trigtuple);
+			if (oldSlot != trigslot)
+				ExecDropSingleTupleTableSlot(oldSlot);
+
 			return NULL;		/* "do nothing" */
 		}
+
+		if (oldtuple != newtuple) //hari
+			ExecStoreTuple(newtuple, slot, InvalidBuffer, true);
 	}
-	if (trigtuple != fdw_trigtuple)
-		heap_freetuple(trigtuple);
+
+	if (oldSlot != trigslot)
+		ExecDropSingleTupleTableSlot(oldSlot);
 
 	if (newtuple != slottuple)
 	{
@@ -2889,8 +2899,8 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 void
 ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 					 ItemPointer tupleid,
-					 HeapTuple fdw_trigtuple,
-					 HeapTuple newtuple,
+					 TupleTableSlot *oldslot,
+					 TupleTableSlot *slot,
 					 List *recheckIndexes,
 					 TransitionCaptureState *transition_capture)
 {
@@ -2902,30 +2912,35 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 		  transition_capture->tcs_update_new_table)))
 	{
 		HeapTuple	trigtuple;
+		TupleTableSlot *trigslot = oldslot;
 
-		Assert(HeapTupleIsValid(fdw_trigtuple) ^ ItemPointerIsValid(tupleid));
-		if (fdw_trigtuple == NULL)
+		Assert(PointerIsValid(oldslot) ^ ItemPointerIsValid(tupleid));
+		if (oldslot == NULL)
+		{
 			trigtuple = GetTupleForTrigger(estate,
 										   NULL,
 										   relinfo,
 										   tupleid,
 										   LockTupleExclusive,
 										   NULL);
-		else
-			trigtuple = fdw_trigtuple;
+
+			trigslot = MakeSingleTupleTableSlot(RelationGetDescr(relinfo->ri_RelationDesc));
+			ExecStoreTuple(trigtuple, trigslot, InvalidBuffer, false);
+		}
 
 		AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE,
-							  true, trigtuple, newtuple, recheckIndexes,
+							  true, trigslot, slot, recheckIndexes,
 							  GetUpdatedColumns(relinfo, estate),
 							  transition_capture);
-		if (trigtuple != fdw_trigtuple)
-			heap_freetuple(trigtuple);
+
+		if (oldslot != trigslot)
+			ExecDropSingleTupleTableSlot(trigslot);
 	}
 }
 
 TupleTableSlot *
 ExecIRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
-					 HeapTuple trigtuple, TupleTableSlot *slot)
+					 TupleTableSlot *oldslot, TupleTableSlot *slot)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 	HeapTuple	slottuple = ExecMaterializeSlot(slot);
@@ -2951,13 +2966,12 @@ ExecIRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 								  TRIGGER_TYPE_UPDATE))
 			continue;
 		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
-							NULL, trigtuple, newtuple))
+							NULL, oldslot, slot))
 			continue;
 
-		LocTriggerData.tg_trigtuple = trigtuple;
-		LocTriggerData.tg_newtuple = oldtuple = newtuple;
-		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
-		LocTriggerData.tg_newtuplebuf = InvalidBuffer;
+		oldtuple = newtuple;
+		LocTriggerData.tg_trigslot = oldslot;
+		LocTriggerData.tg_newslot = slot;
 		LocTriggerData.tg_trigger = trigger;
 		newtuple = ExecCallTriggerFunc(&LocTriggerData,
 									   i,
@@ -2968,6 +2982,8 @@ ExecIRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 			heap_freetuple(oldtuple);
 		if (newtuple == NULL)
 			return NULL;		/* "do nothing" */
+
+		ExecStoreTuple(newtuple, slot, InvalidBuffer, false);
 	}
 
 	if (newtuple != slottuple)
@@ -3007,12 +3023,10 @@ ExecBSTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
 	LocTriggerData.tg_event = TRIGGER_EVENT_TRUNCATE |
 		TRIGGER_EVENT_BEFORE;
 	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
-	LocTriggerData.tg_trigtuple = NULL;
-	LocTriggerData.tg_newtuple = NULL;
+	LocTriggerData.tg_trigslot = NULL;
+	LocTriggerData.tg_newslot = NULL;
 	LocTriggerData.tg_oldtable = NULL;
 	LocTriggerData.tg_newtable = NULL;
-	LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
-	LocTriggerData.tg_newtuplebuf = InvalidBuffer;
 	for (i = 0; i < trigdesc->numtriggers; i++)
 	{
 		Trigger    *trigger = &trigdesc->triggers[i];
@@ -3200,7 +3214,7 @@ static bool
 TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
 			   Trigger *trigger, TriggerEvent event,
 			   Bitmapset *modifiedCols,
-			   HeapTuple oldtup, HeapTuple newtup)
+			   TupleTableSlot *oldslot, TupleTableSlot *newslot)
 {
 	/* Check replication-role-dependent enable state */
 	if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
@@ -3245,8 +3259,8 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
 		TupleDesc	tupdesc = RelationGetDescr(relinfo->ri_RelationDesc);
 		ExprState **predicate;
 		ExprContext *econtext;
-		TupleTableSlot *oldslot = NULL;
-		TupleTableSlot *newslot = NULL;
+		TupleTableSlot *old_localslot = NULL;
+		TupleTableSlot *new_localslot = NULL;
 		MemoryContext oldContext;
 		int			i;
 
@@ -3290,7 +3304,7 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
 		 * These slots can be shared across the whole estate, but be careful
 		 * that they have the current resultrel's tupdesc.
 		 */
-		if (HeapTupleIsValid(oldtup))
+		if (PointerIsValid(oldslot))
 		{
 			if (estate->es_trig_oldtup_slot == NULL)
 			{
@@ -3298,12 +3312,12 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
 				estate->es_trig_oldtup_slot = ExecInitExtraTupleSlot(estate);
 				MemoryContextSwitchTo(oldContext);
 			}
-			oldslot = estate->es_trig_oldtup_slot;
-			if (oldslot->tts_tupleDescriptor != tupdesc)
-				ExecSetSlotDescriptor(oldslot, tupdesc);
-			ExecStoreTuple(oldtup, oldslot, InvalidBuffer, false);
+			old_localslot = estate->es_trig_oldtup_slot;
+			if (old_localslot->tts_tupleDescriptor != tupdesc)
+				ExecSetSlotDescriptor(old_localslot, tupdesc);
+			ExecStoreTuple(oldslot->tts_tuple, old_localslot, InvalidBuffer, false);
 		}
-		if (HeapTupleIsValid(newtup))
+		if (PointerIsValid(newslot))
 		{
 			if (estate->es_trig_newtup_slot == NULL)
 			{
@@ -3311,18 +3325,18 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
 				estate->es_trig_newtup_slot = ExecInitExtraTupleSlot(estate);
 				MemoryContextSwitchTo(oldContext);
 			}
-			newslot = estate->es_trig_newtup_slot;
-			if (newslot->tts_tupleDescriptor != tupdesc)
-				ExecSetSlotDescriptor(newslot, tupdesc);
-			ExecStoreTuple(newtup, newslot, InvalidBuffer, false);
+			new_localslot = estate->es_trig_newtup_slot;
+			if (new_localslot->tts_tupleDescriptor != tupdesc)
+				ExecSetSlotDescriptor(new_localslot, tupdesc);
+			ExecStoreTuple(newslot->tts_tuple, new_localslot, InvalidBuffer, false);
 		}
 
 		/*
 		 * Finally evaluate the expression, making the old and/or new tuples
 		 * available as INNER_VAR/OUTER_VAR respectively.
 		 */
-		econtext->ecxt_innertuple = oldslot;
-		econtext->ecxt_outertuple = newslot;
+		econtext->ecxt_innertuple = old_localslot;
+		econtext->ecxt_outertuple = new_localslot;
 		if (!ExecQual(*predicate, econtext))
 			return false;
 	}
@@ -3900,6 +3914,8 @@ AfterTriggerExecute(AfterTriggerEvent event,
 	TriggerData LocTriggerData;
 	HeapTupleData tuple1;
 	HeapTupleData tuple2;
+	HeapTuple	oldtuple = NULL;
+	HeapTuple	newtuple = NULL;
 	HeapTuple	rettuple;
 	Buffer		buffer1 = InvalidBuffer;
 	Buffer		buffer2 = InvalidBuffer;
@@ -3960,15 +3976,11 @@ AfterTriggerExecute(AfterTriggerEvent event,
 			 * because we start with a minimal tuple that ExecFetchSlotTuple()
 			 * must materialize anyway.
 			 */
-			LocTriggerData.tg_trigtuple =
-				ExecMaterializeSlot(trig_tuple_slot1);
-			LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
+			LocTriggerData.tg_trigslot = trig_tuple_slot1;
 
-			LocTriggerData.tg_newtuple =
+			LocTriggerData.tg_newslot =
 				((evtshared->ats_event & TRIGGER_EVENT_OPMASK) ==
-				 TRIGGER_EVENT_UPDATE) ?
-				ExecMaterializeSlot(trig_tuple_slot2) : NULL;
-			LocTriggerData.tg_newtuplebuf = InvalidBuffer;
+				 TRIGGER_EVENT_UPDATE) ? trig_tuple_slot2 : NULL;
 
 			break;
 
@@ -3978,13 +3990,12 @@ AfterTriggerExecute(AfterTriggerEvent event,
 				ItemPointerCopy(&(event->ate_ctid1), &(tuple1.t_self));
 				if (!heap_fetch(rel, SnapshotAny, &tuple1, &buffer1, false, NULL))
 					elog(ERROR, "failed to fetch tuple1 for AFTER trigger");
-				LocTriggerData.tg_trigtuple = &tuple1;
-				LocTriggerData.tg_trigtuplebuf = buffer1;
+				LocTriggerData.tg_trigslot = MakeSingleTupleTableSlot(RelationGetDescr(rel));
+				ExecStoreTuple(&tuple1, LocTriggerData.tg_trigslot, buffer1, false);
 			}
 			else
 			{
-				LocTriggerData.tg_trigtuple = NULL;
-				LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
+				LocTriggerData.tg_trigslot = NULL;
 			}
 
 			/* don't touch ctid2 if not there */
@@ -3995,13 +4006,12 @@ AfterTriggerExecute(AfterTriggerEvent event,
 				ItemPointerCopy(&(event->ate_ctid2), &(tuple2.t_self));
 				if (!heap_fetch(rel, SnapshotAny, &tuple2, &buffer2, false, NULL))
 					elog(ERROR, "failed to fetch tuple2 for AFTER trigger");
-				LocTriggerData.tg_newtuple = &tuple2;
-				LocTriggerData.tg_newtuplebuf = buffer2;
+				LocTriggerData.tg_newslot = MakeSingleTupleTableSlot(RelationGetDescr(rel));
+				ExecStoreTuple(&tuple2, LocTriggerData.tg_newslot, buffer2, false);
 			}
 			else
 			{
-				LocTriggerData.tg_newtuple = NULL;
-				LocTriggerData.tg_newtuplebuf = InvalidBuffer;
+				LocTriggerData.tg_newslot = NULL;
 			}
 	}
 
@@ -4039,6 +4049,12 @@ AfterTriggerExecute(AfterTriggerEvent event,
 		}
 	}
 
+	if (LocTriggerData.tg_trigslot != NULL)
+		oldtuple = ExecMaterializeSlot(LocTriggerData.tg_trigslot);
+
+	if (LocTriggerData.tg_newslot != NULL)
+		newtuple = ExecMaterializeSlot(LocTriggerData.tg_newslot);
+
 	/*
 	 * Setup the remaining trigger information
 	 */
@@ -4059,10 +4075,18 @@ AfterTriggerExecute(AfterTriggerEvent event,
 								   NULL,
 								   per_tuple_context);
 	if (rettuple != NULL &&
-		rettuple != LocTriggerData.tg_trigtuple &&
-		rettuple != LocTriggerData.tg_newtuple)
+		rettuple != oldtuple &&
+		rettuple != newtuple)
 		heap_freetuple(rettuple);
 
+	if ((LocTriggerData.tg_trigslot != NULL) &&
+			(LocTriggerData.tg_trigslot != trig_tuple_slot1))
+		ExecDropSingleTupleTableSlot(LocTriggerData.tg_trigslot);
+
+	if ((LocTriggerData.tg_newslot != NULL) &&
+			(LocTriggerData.tg_newslot != trig_tuple_slot2))
+		ExecDropSingleTupleTableSlot(LocTriggerData.tg_newslot);
+
 	/*
 	 * Release buffers
 	 */
@@ -5221,7 +5245,7 @@ AfterTriggerPendingOnRel(Oid relid)
 static void
 AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 					  int event, bool row_trigger,
-					  HeapTuple oldtup, HeapTuple newtup,
+					  TupleTableSlot *oldslot, TupleTableSlot *newslot,
 					  List *recheckIndexes, Bitmapset *modifiedCols,
 					  TransitionCaptureState *transition_capture)
 {
@@ -5265,25 +5289,27 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 		{
 			Tuplestorestate *old_tuplestore;
 
-			Assert(oldtup != NULL);
+			Assert(oldslot != NULL);
+			Assert(oldslot->tts_tuple != NULL);
 			old_tuplestore = transition_capture->tcs_old_tuplestore;
 
 			if (map != NULL)
 			{
-				HeapTuple	converted = do_convert_tuple(oldtup, map);
+				HeapTuple	converted = do_convert_tuple(oldslot->tts_tuple, map);
 
 				tuplestore_puttuple(old_tuplestore, converted);
 				pfree(converted);
 			}
 			else
-				tuplestore_puttuple(old_tuplestore, oldtup);
+				tuplestore_puttuple(old_tuplestore, oldslot->tts_tuple);
 		}
 		if ((event == TRIGGER_EVENT_INSERT && insert_new_table) ||
 			(event == TRIGGER_EVENT_UPDATE && update_new_table))
 		{
 			Tuplestorestate *new_tuplestore;
 
-			Assert(newtup != NULL);
+			Assert(newslot != NULL);
+			Assert(newslot->tts_tuple != NULL);
 			if (event == TRIGGER_EVENT_INSERT)
 				new_tuplestore = transition_capture->tcs_insert_tuplestore;
 			else
@@ -5293,13 +5319,13 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 				tuplestore_puttuple(new_tuplestore, original_insert_tuple);
 			else if (map != NULL)
 			{
-				HeapTuple	converted = do_convert_tuple(newtup, map);
+				HeapTuple	converted = do_convert_tuple(newslot->tts_tuple, map);
 
 				tuplestore_puttuple(new_tuplestore, converted);
 				pfree(converted);
 			}
 			else
-				tuplestore_puttuple(new_tuplestore, newtup);
+				tuplestore_puttuple(new_tuplestore, newslot->tts_tuple);
 		}
 
 		/* If transition tables are the only reason we're here, return. */
@@ -5323,15 +5349,15 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 			tgtype_event = TRIGGER_TYPE_INSERT;
 			if (row_trigger)
 			{
-				Assert(oldtup == NULL);
-				Assert(newtup != NULL);
-				ItemPointerCopy(&(newtup->t_self), &(new_event.ate_ctid1));
+				Assert(oldslot == NULL);
+				Assert(newslot != NULL);
+				ItemPointerCopy(&(newslot->tts_tid), &(new_event.ate_ctid1));
 				ItemPointerSetInvalid(&(new_event.ate_ctid2));
 			}
 			else
 			{
-				Assert(oldtup == NULL);
-				Assert(newtup == NULL);
+				Assert(oldslot == NULL);
+				Assert(newslot == NULL);
 				ItemPointerSetInvalid(&(new_event.ate_ctid1));
 				ItemPointerSetInvalid(&(new_event.ate_ctid2));
 			}
@@ -5340,15 +5366,15 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 			tgtype_event = TRIGGER_TYPE_DELETE;
 			if (row_trigger)
 			{
-				Assert(oldtup != NULL);
-				Assert(newtup == NULL);
-				ItemPointerCopy(&(oldtup->t_self), &(new_event.ate_ctid1));
+				Assert(oldslot != NULL);
+				Assert(newslot == NULL);
+				ItemPointerCopy(&(oldslot->tts_tid), &(new_event.ate_ctid1));
 				ItemPointerSetInvalid(&(new_event.ate_ctid2));
 			}
 			else
 			{
-				Assert(oldtup == NULL);
-				Assert(newtup == NULL);
+				Assert(oldslot == NULL);
+				Assert(newslot == NULL);
 				ItemPointerSetInvalid(&(new_event.ate_ctid1));
 				ItemPointerSetInvalid(&(new_event.ate_ctid2));
 			}
@@ -5357,23 +5383,23 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 			tgtype_event = TRIGGER_TYPE_UPDATE;
 			if (row_trigger)
 			{
-				Assert(oldtup != NULL);
-				Assert(newtup != NULL);
-				ItemPointerCopy(&(oldtup->t_self), &(new_event.ate_ctid1));
-				ItemPointerCopy(&(newtup->t_self), &(new_event.ate_ctid2));
+				Assert(oldslot != NULL);
+				Assert(newslot != NULL);
+				ItemPointerCopy(&(oldslot->tts_tid), &(new_event.ate_ctid1));
+				ItemPointerCopy(&(newslot->tts_tid), &(new_event.ate_ctid2));
 			}
 			else
 			{
-				Assert(oldtup == NULL);
-				Assert(newtup == NULL);
+				Assert(oldslot == NULL);
+				Assert(newslot == NULL);
 				ItemPointerSetInvalid(&(new_event.ate_ctid1));
 				ItemPointerSetInvalid(&(new_event.ate_ctid2));
 			}
 			break;
 		case TRIGGER_EVENT_TRUNCATE:
 			tgtype_event = TRIGGER_TYPE_TRUNCATE;
-			Assert(oldtup == NULL);
-			Assert(newtup == NULL);
+			Assert(oldslot == NULL);
+			Assert(newslot == NULL);
 			ItemPointerSetInvalid(&(new_event.ate_ctid1));
 			ItemPointerSetInvalid(&(new_event.ate_ctid2));
 			break;
@@ -5400,7 +5426,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 								  tgtype_event))
 			continue;
 		if (!TriggerEnabled(estate, relinfo, trigger, event,
-							modifiedCols, oldtup, newtup))
+							modifiedCols, oldslot, newslot))
 			continue;
 
 		if (relkind == RELKIND_FOREIGN_TABLE && row_trigger)
@@ -5429,7 +5455,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 				case RI_TRIGGER_PK:
 					/* Update on trigger's PK table */
 					if (!RI_FKey_pk_upd_check_required(trigger, rel,
-													   oldtup, newtup))
+													   oldslot, newslot))
 					{
 						/* skip queuing this event */
 						continue;
@@ -5439,7 +5465,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 				case RI_TRIGGER_FK:
 					/* Update on trigger's FK table */
 					if (!RI_FKey_fk_upd_check_required(trigger, rel,
-													   oldtup, newtup))
+													   oldslot, newslot))
 					{
 						/* skip queuing this event */
 						continue;
@@ -5487,10 +5513,10 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 	 */
 	if (fdw_tuplestore)
 	{
-		if (oldtup != NULL)
-			tuplestore_puttuple(fdw_tuplestore, oldtup);
-		if (newtup != NULL)
-			tuplestore_puttuple(fdw_tuplestore, newtup);
+		if (oldslot != NULL)
+			tuplestore_puttuple(fdw_tuplestore, oldslot->tts_tuple);
+		if (newslot != NULL)
+			tuplestore_puttuple(fdw_tuplestore, newslot->tts_tuple);
 	}
 }
 
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index bc53d07..ab4a6c5 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -409,14 +409,14 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 
 		/* OK, store the tuple and create index entries for it */
 		simple_heap_insert(rel, tuple);
-
+		ItemPointerCopy(&(tuple->t_self), &(slot->tts_tid));
 		if (resultRelInfo->ri_NumIndices > 0)
 			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
 												   estate, false, NULL,
 												   NIL);
 
 		/* AFTER ROW INSERT Triggers */
-		ExecARInsertTriggers(estate, resultRelInfo, tuple,
+		ExecARInsertTriggers(estate, resultRelInfo, slot,
 							 recheckIndexes, NULL);
 
 		/*
@@ -482,10 +482,11 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 												   estate, false, NULL,
 												   NIL);
 
+		ItemPointerCopy(&(tuple->t_self), &(slot->tts_tid));
 		/* AFTER ROW UPDATE Triggers */
 		ExecARUpdateTriggers(estate, resultRelInfo,
 							 &searchslot->tts_tuple->t_self,
-							 NULL, tuple, recheckIndexes, NULL);
+							 NULL, slot, recheckIndexes, NULL);
 
 		list_free(recheckIndexes);
 	}
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 7ae70a8..fabb628 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -116,6 +116,7 @@ MakeTupleTableSlot(void)
 	slot->tts_shouldFree = false;
 	slot->tts_shouldFreeMin = false;
 	slot->tts_tuple = NULL;
+	ItemPointerSetInvalid(&(slot->tts_tid));
 	slot->tts_tupleDescriptor = NULL;
 	slot->tts_mcxt = CurrentMemoryContext;
 	slot->tts_buffer = InvalidBuffer;
@@ -369,6 +370,8 @@ ExecStoreTuple(HeapTuple tuple,
 			IncrBufferRefCount(buffer);
 	}
 
+	ItemPointerCopy(&(tuple->t_self), &(slot->tts_tid));
+
 	return slot;
 }
 
@@ -427,6 +430,63 @@ ExecStoreMinimalTuple(MinimalTuple mtup,
 	return slot;
 }
 
+/*
+ * heap_modify_slot_by_cols
+ *		form a new tuple from an old tuple and a set of replacement values.
+ *
+ * This is like heap_modify_tuple, except that instead of specifying which
+ * column(s) to replace by a boolean map, an array of target column numbers
+ * is used.  This is often more convenient when a fixed number of columns
+ * are to be replaced.  The replCols, replValues, and replIsnull arrays must
+ * be of length nCols.  Target column numbers are indexed from 1.
+ *
+ * The result is allocated in the current memory context.
+ */
+TupleTableSlot*
+heap_modify_slot_by_cols(TupleTableSlot *slot,
+						 int nCols,
+						 int *replCols,
+						 Datum *replValues,
+						 bool *replIsnull)
+{
+	int			numberOfAttributes = slot->tts_tupleDescriptor->natts;
+	HeapTuple	newTuple;
+	int			i;
+
+	slot_getallattrs(slot);
+
+	for (i = 0; i < nCols; i++)
+	{
+		int			attnum = replCols[i];
+
+		if (attnum <= 0 || attnum > numberOfAttributes)
+			elog(ERROR, "invalid column number %d", attnum);
+		slot->tts_values[attnum - 1] = replValues[i];
+		slot->tts_isnull[attnum - 1] = replIsnull[i];
+	}
+
+	/*
+	 * create a new tuple from the values and isnull arrays
+	 */
+	newTuple = heap_form_tuple(slot->tts_tupleDescriptor, slot->tts_values, slot->tts_isnull);
+
+	/*
+	 * copy the identification info of the old tuple: t_ctid, t_self, and OID
+	 * (if any)
+	 */
+	newTuple->t_data->t_ctid = slot->tts_tuple->t_data->t_ctid;
+	newTuple->t_self = slot->tts_tuple->t_self;
+	newTuple->t_tableOid = slot->tts_tuple->t_tableOid;
+	if (slot->tts_tupleDescriptor->tdhasoid)
+		HeapTupleSetOid(newTuple, HeapTupleGetOid(slot->tts_tuple));
+
+	if (slot->tts_shouldFree)
+		heap_freetuple(slot->tts_tuple);
+
+	slot->tts_tuple = newTuple;
+	return slot;
+}
+
 /* --------------------------------
  *		ExecClearTuple
  *
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 8d17425..387c059 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -557,6 +557,7 @@ ExecInsert(ModifyTableState *mtstate,
 								HEAP_INSERT_SPECULATIVE,
 								NULL);
 
+			ItemPointerCopy(&(tuple->t_self), &(slot->tts_tid));
 			/* insert index entries for tuple */
 			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
 												   estate, true, &specConflict,
@@ -602,6 +603,7 @@ ExecInsert(ModifyTableState *mtstate,
 								estate->es_output_cid,
 								0, NULL);
 
+			ItemPointerCopy(&(tuple->t_self), &(slot->tts_tid));
 			/* insert index entries for tuple */
 			if (resultRelInfo->ri_NumIndices > 0)
 				recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
@@ -618,7 +620,7 @@ ExecInsert(ModifyTableState *mtstate,
 	}
 
 	/* AFTER ROW INSERT Triggers */
-	ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes,
+	ExecARInsertTriggers(estate, resultRelInfo, slot, recheckIndexes,
 						 mtstate->mt_transition_capture);
 
 	list_free(recheckIndexes);
@@ -669,7 +671,7 @@ ExecInsert(ModifyTableState *mtstate,
 static TupleTableSlot *
 ExecDelete(ModifyTableState *mtstate,
 		   ItemPointer tupleid,
-		   HeapTuple oldtuple,
+		   TupleTableSlot *oldslot,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate,
@@ -694,7 +696,7 @@ ExecDelete(ModifyTableState *mtstate,
 		bool		dodelete;
 
 		dodelete = ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
-										tupleid, oldtuple);
+										tupleid, oldslot);
 
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
@@ -706,8 +708,8 @@ ExecDelete(ModifyTableState *mtstate,
 	{
 		bool		dodelete;
 
-		Assert(oldtuple != NULL);
-		dodelete = ExecIRDeleteTriggers(estate, resultRelInfo, oldtuple);
+		Assert(oldslot != NULL);
+		dodelete = ExecIRDeleteTriggers(estate, resultRelInfo, oldslot);
 
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
@@ -844,8 +846,11 @@ ldelete:;
 	if (canSetTag)
 		(estate->es_processed)++;
 
+	if (oldslot != NULL)
+		ItemPointerCopy(tupleid, &(oldslot->tts_tid));
+
 	/* AFTER ROW DELETE Triggers */
-	ExecARDeleteTriggers(estate, resultRelInfo, tupleid, oldtuple,
+	ExecARDeleteTriggers(estate, resultRelInfo, tupleid, oldslot,
 						 mtstate->mt_transition_capture);
 
 	/* Process RETURNING if present */
@@ -868,10 +873,12 @@ ldelete:;
 		else
 		{
 			slot = estate->es_trig_tuple_slot;
-			if (oldtuple != NULL)
+			if (oldslot != NULL)
 			{
-				deltuple = *oldtuple;
-				delbuffer = InvalidBuffer;
+				ExecCopySlot(slot, oldslot);
+
+				if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
+					ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
 			}
 			else
 			{
@@ -879,11 +886,11 @@ ldelete:;
 				if (!heap_fetch(resultRelationDesc, 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);
+				if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
+					ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
+				ExecStoreTuple(&deltuple, slot, InvalidBuffer, false);
+			}
 		}
 
 		rslot = ExecProcessReturning(resultRelInfo, slot, planSlot);
@@ -929,7 +936,7 @@ ldelete:;
 static TupleTableSlot *
 ExecUpdate(ModifyTableState *mtstate,
 		   ItemPointer tupleid,
-		   HeapTuple oldtuple,
+		   TupleTableSlot *oldslot,
 		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
@@ -966,7 +973,7 @@ ExecUpdate(ModifyTableState *mtstate,
 		resultRelInfo->ri_TrigDesc->trig_update_before_row)
 	{
 		slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
-									tupleid, oldtuple, slot);
+									tupleid, oldslot, slot);
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
@@ -980,7 +987,7 @@ ExecUpdate(ModifyTableState *mtstate,
 		resultRelInfo->ri_TrigDesc->trig_update_instead_row)
 	{
 		slot = ExecIRUpdateTriggers(estate, resultRelInfo,
-									oldtuple, slot);
+									oldslot, slot);
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
@@ -1138,6 +1145,7 @@ lreplace:;
 		 * All we do here is insert new index tuples.  -cim 9/27/89
 		 */
 
+		ItemPointerCopy(&(tuple->t_self), &(slot->tts_tid));
 		/*
 		 * insert index entries for tuple
 		 *
@@ -1155,7 +1163,7 @@ lreplace:;
 		(estate->es_processed)++;
 
 	/* AFTER ROW UPDATE Triggers */
-	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldtuple, tuple,
+	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldslot, slot,
 						 recheckIndexes,
 						 mtstate->mt_transition_capture);
 
@@ -1550,6 +1558,7 @@ ExecModifyTable(ModifyTableState *node)
 	ItemPointerData tuple_ctid;
 	HeapTupleData oldtupdata;
 	HeapTuple	oldtuple;
+	TupleTableSlot *oldslot = NULL;
 
 	/*
 	 * This should NOT get called during EvalPlanQual; we should have passed a
@@ -1659,7 +1668,8 @@ ExecModifyTable(ModifyTableState *node)
 		}
 
 		EvalPlanQualSetSlot(&node->mt_epqstate, planSlot);
-		slot = planSlot;
+		slot = MakeSingleTupleTableSlot(planSlot->tts_tupleDescriptor);
+		ExecCopySlot(slot, planSlot);
 
 		oldtuple = NULL;
 		if (junkfilter != NULL)
@@ -1721,6 +1731,10 @@ ExecModifyTable(ModifyTableState *node)
 						RelationGetRelid(resultRelInfo->ri_RelationDesc);
 
 					oldtuple = &oldtupdata;
+
+					ItemPointerCopy(tupleid, &(oldslot->tts_tid));
+					oldslot = MakeSingleTupleTableSlot(RelationGetDescr(resultRelInfo->ri_RelationDesc));
+					ExecStoreTuple(oldtuple, oldslot, InvalidBuffer, false);
 				}
 				else
 					Assert(relkind == RELKIND_FOREIGN_TABLE);
@@ -1741,11 +1755,11 @@ ExecModifyTable(ModifyTableState *node)
 								  estate, node->canSetTag);
 				break;
 			case CMD_UPDATE:
-				slot = ExecUpdate(node, tupleid, oldtuple, slot, planSlot,
+				slot = ExecUpdate(node, tupleid, oldslot, slot, planSlot,
 								  &node->mt_epqstate, estate, node->canSetTag);
 				break;
 			case CMD_DELETE:
-				slot = ExecDelete(node, tupleid, oldtuple, planSlot,
+				slot = ExecDelete(node, tupleid, oldslot, planSlot,
 								  &node->mt_epqstate, estate, node->canSetTag);
 				break;
 			default:
@@ -1753,6 +1767,9 @@ ExecModifyTable(ModifyTableState *node)
 				break;
 		}
 
+		if (oldslot)
+			ExecDropSingleTupleTableSlot(oldslot);
+
 		/*
 		 * If we got a RETURNING result, return it to caller.  We'll continue
 		 * the work on next call.
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index cd00a6d..79ffa52 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -23,6 +23,7 @@
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "executor/spi_priv.h"
+#include "executor/tuptable.h"
 #include "miscadmin.h"
 #include "tcop/pquery.h"
 #include "tcop/utility.h"
@@ -757,6 +758,72 @@ SPI_modifytuple(Relation rel, HeapTuple tuple, int natts, int *attnum,
 	return mtuple;
 }
 
+TupleTableSlot *
+SPI_modifyslot(TupleTableSlot *slot, int natts, int *attnum,
+				Datum *Values, const char *Nulls)
+{
+	MemoryContext oldcxt;
+	HeapTuple	mtuple;
+	int			numberOfAttributes;
+	int			i;
+
+	if (slot == NULL || natts < 0 || attnum == NULL || Values == NULL)
+	{
+		SPI_result = SPI_ERROR_ARGUMENT;
+		return NULL;
+	}
+
+	if (_SPI_current == NULL)
+	{
+		SPI_result = SPI_ERROR_UNCONNECTED;
+		return NULL;
+	}
+
+	oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt);
+
+	SPI_result = 0;
+
+	numberOfAttributes = slot->tts_tupleDescriptor->natts;
+
+	/* replace values and nulls */
+	for (i = 0; i < natts; i++)
+	{
+		if (attnum[i] <= 0 || attnum[i] > numberOfAttributes)
+			break;
+		slot->tts_values[attnum[i] - 1] = Values[i];
+		slot->tts_isnull[attnum[i] - 1] = (Nulls && Nulls[i] == 'n') ? true : false;
+	}
+
+	if (i == natts)				/* no errors in *attnum */
+	{
+		mtuple = heap_form_tuple(slot->tts_tupleDescriptor, slot->tts_values, slot->tts_isnull);
+
+		/*
+		 * copy the identification info of the old tuple: t_ctid, t_self, and
+		 * OID (if any)
+		 */
+		mtuple->t_data->t_ctid = slot->tts_tuple->t_data->t_ctid;
+		mtuple->t_self = slot->tts_tuple->t_self;
+		mtuple->t_tableOid = slot->tts_tuple->t_tableOid;
+		if (slot->tts_tupleDescriptor->tdhasoid)
+			HeapTupleSetOid(mtuple, HeapTupleGetOid(slot->tts_tuple));
+	}
+	else
+	{
+		mtuple = NULL;
+		SPI_result = SPI_ERROR_NOATTRIBUTE;
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+
+	if (slot->tts_shouldFree)
+		heap_freetuple(slot->tts_tuple);
+
+	slot->tts_tuple = mtuple;
+
+	return slot;
+}
+
 int
 SPI_fnumber(TupleDesc tupdesc, const char *fname)
 {
@@ -801,7 +868,7 @@ SPI_fname(TupleDesc tupdesc, int fnumber)
 }
 
 char *
-SPI_getvalue(HeapTuple tuple, TupleDesc tupdesc, int fnumber)
+SPI_getvalue(TupleTableSlot *slot, int fnumber)
 {
 	Datum		val;
 	bool		isnull;
@@ -811,19 +878,19 @@ SPI_getvalue(HeapTuple tuple, TupleDesc tupdesc, int fnumber)
 
 	SPI_result = 0;
 
-	if (fnumber > tupdesc->natts || fnumber == 0 ||
+	if (fnumber > slot->tts_tupleDescriptor->natts || fnumber == 0 ||
 		fnumber <= FirstLowInvalidHeapAttributeNumber)
 	{
 		SPI_result = SPI_ERROR_NOATTRIBUTE;
 		return NULL;
 	}
 
-	val = heap_getattr(tuple, fnumber, tupdesc, &isnull);
+	val = slot_getattr(slot, fnumber, &isnull);
 	if (isnull)
 		return NULL;
 
 	if (fnumber > 0)
-		typoid = tupdesc->attrs[fnumber - 1]->atttypid;
+		typoid = slot->tts_tupleDescriptor->attrs[fnumber - 1]->atttypid;
 	else
 		typoid = (SystemAttributeDefinition(fnumber, true))->atttypid;
 
@@ -833,11 +900,11 @@ SPI_getvalue(HeapTuple tuple, TupleDesc tupdesc, int fnumber)
 }
 
 Datum
-SPI_getbinval(HeapTuple tuple, TupleDesc tupdesc, int fnumber, bool *isnull)
+SPI_getbinval(TupleTableSlot *slot, int fnumber, bool *isnull)
 {
 	SPI_result = 0;
 
-	if (fnumber > tupdesc->natts || fnumber == 0 ||
+	if (fnumber > slot->tts_tupleDescriptor->natts || fnumber == 0 ||
 		fnumber <= FirstLowInvalidHeapAttributeNumber)
 	{
 		SPI_result = SPI_ERROR_NOATTRIBUTE;
@@ -845,7 +912,7 @@ SPI_getbinval(HeapTuple tuple, TupleDesc tupdesc, int fnumber, bool *isnull)
 		return (Datum) NULL;
 	}
 
-	return heap_getattr(tuple, fnumber, tupdesc, isnull);
+	return slot_getattr(slot, fnumber, isnull);
 }
 
 char *
@@ -1681,7 +1748,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 = (TupleTableSlot **) palloc(tuptable->alloced * sizeof(TupleTableSlot *));
 	tuptable->tupdesc = CreateTupleDescCopy(typeinfo);
 
 	MemoryContextSwitchTo(oldcxt);
@@ -1697,6 +1764,7 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self)
 {
 	SPITupleTable *tuptable;
 	MemoryContext oldcxt;
+	TupleTableSlot *dstslot;
 
 	if (_SPI_current == NULL)
 		elog(ERROR, "spi_printtup called while not connected to SPI");
@@ -1712,12 +1780,12 @@ 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 = (TupleTableSlot **) repalloc_huge(tuptable->vals,
+									  tuptable->alloced * sizeof(TupleTableSlot *));
 	}
 
-	tuptable->vals[tuptable->alloced - tuptable->free] =
-		ExecCopySlotTuple(slot);
+	dstslot = MakeSingleTupleTableSlot(tuptable->tupdesc);
+	tuptable->vals[tuptable->alloced - tuptable->free] = ExecCopySlot(dstslot, slot);
 	(tuptable->free)--;
 
 	MemoryContextSwitchTo(oldcxt);
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 0d48dfa..4a01fbd 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -726,6 +726,7 @@ apply_handle_update(StringInfo s)
 		/* Process and store remote tuple in the slot */
 		oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
 		ExecStoreTuple(localslot->tts_tuple, remoteslot, InvalidBuffer, false);
+		ItemPointerCopy(&(localslot->tts_tuple->t_self), &(remoteslot->tts_tid));
 		slot_modify_cstrings(remoteslot, rel, newtup.values, newtup.changed);
 		MemoryContextSwitchTo(oldctx);
 
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index c2891e6..01714f4 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -194,7 +194,7 @@ static int	ri_constraint_cache_valid_count = 0;
  * ----------
  */
 static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
-				  HeapTuple old_row,
+		 	 	  TupleTableSlot *oldslot,
 				  const RI_ConstraintInfo *riinfo);
 static Datum ri_restrict_del(TriggerData *trigdata, bool is_no_action);
 static Datum ri_restrict_upd(TriggerData *trigdata, bool is_no_action);
@@ -207,12 +207,12 @@ static void ri_GenerateQual(StringInfo buf,
 				const char *rightop, Oid rightoptype);
 static void ri_add_cast_to(StringInfo buf, Oid typid);
 static void ri_GenerateQualCollation(StringInfo buf, Oid collation);
-static int ri_NullCheck(HeapTuple tup,
+static int ri_NullCheck(TupleTableSlot *slot,
 			 const RI_ConstraintInfo *riinfo, bool rel_is_pk);
 static void ri_BuildQueryKey(RI_QueryKey *key,
 				 const RI_ConstraintInfo *riinfo,
 				 int32 constr_queryno);
-static bool ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup,
+static bool ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
 			 const RI_ConstraintInfo *riinfo, bool rel_is_pk);
 static bool ri_AttributesEqual(Oid eq_opr, Oid typeid,
 				   Datum oldvalue, Datum newvalue);
@@ -234,14 +234,13 @@ static SPIPlanPtr ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
 static bool ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 				RI_QueryKey *qkey, SPIPlanPtr qplan,
 				Relation fk_rel, Relation pk_rel,
-				HeapTuple old_tuple, HeapTuple new_tuple,
+				TupleTableSlot *oldslot, TupleTableSlot *newslot,
 				bool detectNewRows, int expect_OK);
-static void ri_ExtractValues(Relation rel, HeapTuple tup,
-				 const RI_ConstraintInfo *riinfo, bool rel_is_pk,
-				 Datum *vals, char *nulls);
+static void ri_ExtractValues(TupleTableSlot *slot, const RI_ConstraintInfo *riinfo,
+				bool rel_is_pk, Datum *vals, char *nulls);
 static void ri_ReportViolation(const RI_ConstraintInfo *riinfo,
 				   Relation pk_rel, Relation fk_rel,
-				   HeapTuple violator, TupleDesc tupdesc,
+				   TupleTableSlot* violator, TupleDesc tupdesc,
 				   int queryno, bool spi_err);
 
 
@@ -257,8 +256,7 @@ RI_FKey_check(TriggerData *trigdata)
 	const RI_ConstraintInfo *riinfo;
 	Relation	fk_rel;
 	Relation	pk_rel;
-	HeapTuple	new_row;
-	Buffer		new_row_buf;
+	TupleTableSlot *newslot;
 	RI_QueryKey qkey;
 	SPIPlanPtr	qplan;
 	int			i;
@@ -270,15 +268,9 @@ RI_FKey_check(TriggerData *trigdata)
 									trigdata->tg_relation, false);
 
 	if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
-	{
-		new_row = trigdata->tg_newtuple;
-		new_row_buf = trigdata->tg_newtuplebuf;
-	}
+		newslot = trigdata->tg_newslot;
 	else
-	{
-		new_row = trigdata->tg_trigtuple;
-		new_row_buf = trigdata->tg_trigtuplebuf;
-	}
+		newslot = trigdata->tg_trigslot;
 
 	/*
 	 * We should not even consider checking the row if it is no longer valid,
@@ -288,13 +280,8 @@ RI_FKey_check(TriggerData *trigdata)
 	 * and lock on the buffer to call HeapTupleSatisfiesVisibility.  Caller
 	 * should be holding pin, but not lock.
 	 */
-	LockBuffer(new_row_buf, BUFFER_LOCK_SHARE);
-	if (!HeapTupleSatisfiesVisibility(new_row, SnapshotSelf, new_row_buf))
-	{
-		LockBuffer(new_row_buf, BUFFER_LOCK_UNLOCK);
+	if (!HeapTupleSatisfiesVisibility(newslot->tts_tuple, SnapshotSelf, InvalidBuffer))
 		return PointerGetDatum(NULL);
-	}
-	LockBuffer(new_row_buf, BUFFER_LOCK_UNLOCK);
 
 	/*
 	 * Get the relation descriptors of the FK and PK tables.
@@ -310,7 +297,7 @@ RI_FKey_check(TriggerData *trigdata)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("MATCH PARTIAL not yet implemented")));
 
-	switch (ri_NullCheck(new_row, riinfo, false))
+	switch (ri_NullCheck(newslot, riinfo, false))
 	{
 		case RI_KEYS_ALL_NULL:
 
@@ -440,7 +427,7 @@ RI_FKey_check(TriggerData *trigdata)
 	 */
 	ri_PerformCheck(riinfo, &qkey, qplan,
 					fk_rel, pk_rel,
-					NULL, new_row,
+					NULL, newslot,
 					false,
 					SPI_OK_SELECT);
 
@@ -508,7 +495,7 @@ RI_FKey_check_upd(PG_FUNCTION_ARGS)
  */
 static bool
 ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
-				  HeapTuple old_row,
+				  TupleTableSlot *oldslot,
 				  const RI_ConstraintInfo *riinfo)
 {
 	SPIPlanPtr	qplan;
@@ -517,7 +504,7 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 	bool		result;
 
 	/* Only called for non-null rows */
-	Assert(ri_NullCheck(old_row, riinfo, true) == RI_KEYS_NONE_NULL);
+	Assert(ri_NullCheck(oldslot, riinfo, true) == RI_KEYS_NONE_NULL);
 
 	if (SPI_connect() != SPI_OK_CONNECT)
 		elog(ERROR, "SPI_connect failed");
@@ -575,8 +562,8 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 	 */
 	result = ri_PerformCheck(riinfo, &qkey, qplan,
 							 fk_rel, pk_rel,
-							 old_row, NULL,
-							 true,	/* treat like update */
+							 oldslot, NULL,
+							 true,		/* treat like update */
 							 SPI_OK_SELECT);
 
 	if (SPI_finish() != SPI_OK_FINISH)
@@ -645,7 +632,7 @@ ri_restrict_del(TriggerData *trigdata, bool is_no_action)
 	const RI_ConstraintInfo *riinfo;
 	Relation	fk_rel;
 	Relation	pk_rel;
-	HeapTuple	old_row;
+	TupleTableSlot *oldslot;
 	RI_QueryKey qkey;
 	SPIPlanPtr	qplan;
 	int			i;
@@ -664,7 +651,7 @@ ri_restrict_del(TriggerData *trigdata, bool is_no_action)
 	 */
 	fk_rel = heap_open(riinfo->fk_relid, RowShareLock);
 	pk_rel = trigdata->tg_relation;
-	old_row = trigdata->tg_trigtuple;
+	oldslot = trigdata->tg_trigslot;
 
 	switch (riinfo->confmatchtype)
 	{
@@ -677,7 +664,7 @@ ri_restrict_del(TriggerData *trigdata, bool is_no_action)
 			 */
 		case FKCONSTR_MATCH_SIMPLE:
 		case FKCONSTR_MATCH_FULL:
-			switch (ri_NullCheck(old_row, riinfo, true))
+			switch (ri_NullCheck(oldslot, riinfo, true))
 			{
 				case RI_KEYS_ALL_NULL:
 				case RI_KEYS_SOME_NULL:
@@ -704,7 +691,7 @@ ri_restrict_del(TriggerData *trigdata, bool is_no_action)
 			 * allow another row to be substituted.
 			 */
 			if (is_no_action &&
-				ri_Check_Pk_Match(pk_rel, fk_rel, old_row, riinfo))
+				ri_Check_Pk_Match(pk_rel, fk_rel, oldslot, riinfo))
 			{
 				heap_close(fk_rel, RowShareLock);
 				return PointerGetDatum(NULL);
@@ -767,8 +754,8 @@ ri_restrict_del(TriggerData *trigdata, bool is_no_action)
 			 */
 			ri_PerformCheck(riinfo, &qkey, qplan,
 							fk_rel, pk_rel,
-							old_row, NULL,
-							true,	/* must detect new rows */
+							oldslot, NULL,
+							true,		/* must detect new rows */
 							SPI_OK_SELECT);
 
 			if (SPI_finish() != SPI_OK_FINISH)
@@ -857,8 +844,8 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
 	const RI_ConstraintInfo *riinfo;
 	Relation	fk_rel;
 	Relation	pk_rel;
-	HeapTuple	new_row;
-	HeapTuple	old_row;
+	TupleTableSlot *newslot;
+	TupleTableSlot *oldslot;
 	RI_QueryKey qkey;
 	SPIPlanPtr	qplan;
 	int			i;
@@ -878,8 +865,8 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
 	 */
 	fk_rel = heap_open(riinfo->fk_relid, RowShareLock);
 	pk_rel = trigdata->tg_relation;
-	new_row = trigdata->tg_newtuple;
-	old_row = trigdata->tg_trigtuple;
+	newslot = trigdata->tg_newslot;
+	oldslot = trigdata->tg_trigslot;
 
 	switch (riinfo->confmatchtype)
 	{
@@ -892,7 +879,7 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
 			 */
 		case FKCONSTR_MATCH_SIMPLE:
 		case FKCONSTR_MATCH_FULL:
-			switch (ri_NullCheck(old_row, riinfo, true))
+			switch (ri_NullCheck(oldslot, riinfo, true))
 			{
 				case RI_KEYS_ALL_NULL:
 				case RI_KEYS_SOME_NULL:
@@ -915,7 +902,7 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
 			/*
 			 * No need to check anything if old and new keys are equal
 			 */
-			if (ri_KeysEqual(pk_rel, old_row, new_row, riinfo, true))
+			if (ri_KeysEqual(pk_rel, oldslot, newslot, riinfo, true))
 			{
 				heap_close(fk_rel, RowShareLock);
 				return PointerGetDatum(NULL);
@@ -928,7 +915,7 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
 			 * allow another row to be substituted.
 			 */
 			if (is_no_action &&
-				ri_Check_Pk_Match(pk_rel, fk_rel, old_row, riinfo))
+				ri_Check_Pk_Match(pk_rel, fk_rel, oldslot, riinfo))
 			{
 				heap_close(fk_rel, RowShareLock);
 				return PointerGetDatum(NULL);
@@ -990,8 +977,8 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
 			 */
 			ri_PerformCheck(riinfo, &qkey, qplan,
 							fk_rel, pk_rel,
-							old_row, NULL,
-							true,	/* must detect new rows */
+							oldslot, NULL,
+							true,		/* must detect new rows */
 							SPI_OK_SELECT);
 
 			if (SPI_finish() != SPI_OK_FINISH)
@@ -1034,7 +1021,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 	const RI_ConstraintInfo *riinfo;
 	Relation	fk_rel;
 	Relation	pk_rel;
-	HeapTuple	old_row;
+	TupleTableSlot *oldslot;
 	RI_QueryKey qkey;
 	SPIPlanPtr	qplan;
 	int			i;
@@ -1058,7 +1045,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 	 */
 	fk_rel = heap_open(riinfo->fk_relid, RowExclusiveLock);
 	pk_rel = trigdata->tg_relation;
-	old_row = trigdata->tg_trigtuple;
+	oldslot = trigdata->tg_trigslot;
 
 	switch (riinfo->confmatchtype)
 	{
@@ -1071,7 +1058,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 			 */
 		case FKCONSTR_MATCH_SIMPLE:
 		case FKCONSTR_MATCH_FULL:
-			switch (ri_NullCheck(old_row, riinfo, true))
+			switch (ri_NullCheck(oldslot, riinfo, true))
 			{
 				case RI_KEYS_ALL_NULL:
 				case RI_KEYS_SOME_NULL:
@@ -1146,8 +1133,8 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 			 */
 			ri_PerformCheck(riinfo, &qkey, qplan,
 							fk_rel, pk_rel,
-							old_row, NULL,
-							true,	/* must detect new rows */
+							oldslot, NULL,
+							true,		/* must detect new rows */
 							SPI_OK_DELETE);
 
 			if (SPI_finish() != SPI_OK_FINISH)
@@ -1190,8 +1177,8 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 	const RI_ConstraintInfo *riinfo;
 	Relation	fk_rel;
 	Relation	pk_rel;
-	HeapTuple	new_row;
-	HeapTuple	old_row;
+	TupleTableSlot *newslot;
+	TupleTableSlot *oldslot;
 	RI_QueryKey qkey;
 	SPIPlanPtr	qplan;
 	int			i;
@@ -1217,8 +1204,8 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 	 */
 	fk_rel = heap_open(riinfo->fk_relid, RowExclusiveLock);
 	pk_rel = trigdata->tg_relation;
-	new_row = trigdata->tg_newtuple;
-	old_row = trigdata->tg_trigtuple;
+	newslot = trigdata->tg_newslot;
+	oldslot = trigdata->tg_trigslot;
 
 	switch (riinfo->confmatchtype)
 	{
@@ -1231,7 +1218,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 			 */
 		case FKCONSTR_MATCH_SIMPLE:
 		case FKCONSTR_MATCH_FULL:
-			switch (ri_NullCheck(old_row, riinfo, true))
+			switch (ri_NullCheck(oldslot, riinfo, true))
 			{
 				case RI_KEYS_ALL_NULL:
 				case RI_KEYS_SOME_NULL:
@@ -1254,7 +1241,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 			/*
 			 * No need to do anything if old and new keys are equal
 			 */
-			if (ri_KeysEqual(pk_rel, old_row, new_row, riinfo, true))
+			if (ri_KeysEqual(pk_rel, oldslot, newslot, riinfo, true))
 			{
 				heap_close(fk_rel, RowExclusiveLock);
 				return PointerGetDatum(NULL);
@@ -1327,8 +1314,8 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 			 */
 			ri_PerformCheck(riinfo, &qkey, qplan,
 							fk_rel, pk_rel,
-							old_row, new_row,
-							true,	/* must detect new rows */
+							oldslot, newslot,
+							true,		/* must detect new rows */
 							SPI_OK_UPDATE);
 
 			if (SPI_finish() != SPI_OK_FINISH)
@@ -1371,7 +1358,7 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
 	const RI_ConstraintInfo *riinfo;
 	Relation	fk_rel;
 	Relation	pk_rel;
-	HeapTuple	old_row;
+	TupleTableSlot *oldslot;
 	RI_QueryKey qkey;
 	SPIPlanPtr	qplan;
 	int			i;
@@ -1395,7 +1382,7 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
 	 */
 	fk_rel = heap_open(riinfo->fk_relid, RowExclusiveLock);
 	pk_rel = trigdata->tg_relation;
-	old_row = trigdata->tg_trigtuple;
+	oldslot = trigdata->tg_trigslot;
 
 	switch (riinfo->confmatchtype)
 	{
@@ -1408,7 +1395,7 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
 			 */
 		case FKCONSTR_MATCH_SIMPLE:
 		case FKCONSTR_MATCH_FULL:
-			switch (ri_NullCheck(old_row, riinfo, true))
+			switch (ri_NullCheck(oldslot, riinfo, true))
 			{
 				case RI_KEYS_ALL_NULL:
 				case RI_KEYS_SOME_NULL:
@@ -1492,8 +1479,8 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
 			 */
 			ri_PerformCheck(riinfo, &qkey, qplan,
 							fk_rel, pk_rel,
-							old_row, NULL,
-							true,	/* must detect new rows */
+							oldslot, NULL,
+							true,		/* must detect new rows */
 							SPI_OK_UPDATE);
 
 			if (SPI_finish() != SPI_OK_FINISH)
@@ -1536,8 +1523,8 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
 	const RI_ConstraintInfo *riinfo;
 	Relation	fk_rel;
 	Relation	pk_rel;
-	HeapTuple	new_row;
-	HeapTuple	old_row;
+	TupleTableSlot *newslot;
+	TupleTableSlot *oldslot;
 	RI_QueryKey qkey;
 	SPIPlanPtr	qplan;
 	int			i;
@@ -1561,8 +1548,8 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
 	 */
 	fk_rel = heap_open(riinfo->fk_relid, RowExclusiveLock);
 	pk_rel = trigdata->tg_relation;
-	new_row = trigdata->tg_newtuple;
-	old_row = trigdata->tg_trigtuple;
+	newslot = trigdata->tg_newslot;
+	oldslot = trigdata->tg_trigslot;
 
 	switch (riinfo->confmatchtype)
 	{
@@ -1575,7 +1562,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
 			 */
 		case FKCONSTR_MATCH_SIMPLE:
 		case FKCONSTR_MATCH_FULL:
-			switch (ri_NullCheck(old_row, riinfo, true))
+			switch (ri_NullCheck(oldslot, riinfo, true))
 			{
 				case RI_KEYS_ALL_NULL:
 				case RI_KEYS_SOME_NULL:
@@ -1598,7 +1585,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
 			/*
 			 * No need to do anything if old and new keys are equal
 			 */
-			if (ri_KeysEqual(pk_rel, old_row, new_row, riinfo, true))
+			if (ri_KeysEqual(pk_rel, oldslot, newslot, riinfo, true))
 			{
 				heap_close(fk_rel, RowExclusiveLock);
 				return PointerGetDatum(NULL);
@@ -1668,8 +1655,8 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
 			 */
 			ri_PerformCheck(riinfo, &qkey, qplan,
 							fk_rel, pk_rel,
-							old_row, NULL,
-							true,	/* must detect new rows */
+							oldslot, NULL,
+							true,		/* must detect new rows */
 							SPI_OK_UPDATE);
 
 			if (SPI_finish() != SPI_OK_FINISH)
@@ -1712,7 +1699,7 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
 	const RI_ConstraintInfo *riinfo;
 	Relation	fk_rel;
 	Relation	pk_rel;
-	HeapTuple	old_row;
+	TupleTableSlot *oldslot;
 	RI_QueryKey qkey;
 	SPIPlanPtr	qplan;
 
@@ -1735,7 +1722,7 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
 	 */
 	fk_rel = heap_open(riinfo->fk_relid, RowExclusiveLock);
 	pk_rel = trigdata->tg_relation;
-	old_row = trigdata->tg_trigtuple;
+	oldslot = trigdata->tg_trigslot;
 
 	switch (riinfo->confmatchtype)
 	{
@@ -1748,7 +1735,7 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
 			 */
 		case FKCONSTR_MATCH_SIMPLE:
 		case FKCONSTR_MATCH_FULL:
-			switch (ri_NullCheck(old_row, riinfo, true))
+			switch (ri_NullCheck(oldslot, riinfo, true))
 			{
 				case RI_KEYS_ALL_NULL:
 				case RI_KEYS_SOME_NULL:
@@ -1834,8 +1821,8 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
 			 */
 			ri_PerformCheck(riinfo, &qkey, qplan,
 							fk_rel, pk_rel,
-							old_row, NULL,
-							true,	/* must detect new rows */
+							oldslot, NULL,
+							true,		/* must detect new rows */
 							SPI_OK_UPDATE);
 
 			if (SPI_finish() != SPI_OK_FINISH)
@@ -1892,8 +1879,8 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
 	const RI_ConstraintInfo *riinfo;
 	Relation	fk_rel;
 	Relation	pk_rel;
-	HeapTuple	new_row;
-	HeapTuple	old_row;
+	TupleTableSlot *newslot;
+	TupleTableSlot *oldslot;
 	RI_QueryKey qkey;
 	SPIPlanPtr	qplan;
 
@@ -1916,8 +1903,8 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
 	 */
 	fk_rel = heap_open(riinfo->fk_relid, RowExclusiveLock);
 	pk_rel = trigdata->tg_relation;
-	new_row = trigdata->tg_newtuple;
-	old_row = trigdata->tg_trigtuple;
+	newslot = trigdata->tg_newslot;
+	oldslot = trigdata->tg_trigslot;
 
 	switch (riinfo->confmatchtype)
 	{
@@ -1930,7 +1917,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
 			 */
 		case FKCONSTR_MATCH_SIMPLE:
 		case FKCONSTR_MATCH_FULL:
-			switch (ri_NullCheck(old_row, riinfo, true))
+			switch (ri_NullCheck(oldslot, riinfo, true))
 			{
 				case RI_KEYS_ALL_NULL:
 				case RI_KEYS_SOME_NULL:
@@ -1953,7 +1940,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
 			/*
 			 * No need to do anything if old and new keys are equal
 			 */
-			if (ri_KeysEqual(pk_rel, old_row, new_row, riinfo, true))
+			if (ri_KeysEqual(pk_rel, oldslot, newslot, riinfo, true))
 			{
 				heap_close(fk_rel, RowExclusiveLock);
 				return PointerGetDatum(NULL);
@@ -2025,8 +2012,8 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
 			 */
 			ri_PerformCheck(riinfo, &qkey, qplan,
 							fk_rel, pk_rel,
-							old_row, NULL,
-							true,	/* must detect new rows */
+							oldslot, NULL,
+							true,		/* must detect new rows */
 							SPI_OK_UPDATE);
 
 			if (SPI_finish() != SPI_OK_FINISH)
@@ -2082,7 +2069,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
  */
 bool
 RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel,
-							  HeapTuple old_row, HeapTuple new_row)
+							  TupleTableSlot *oldslot, TupleTableSlot *newslot)
 {
 	const RI_ConstraintInfo *riinfo;
 
@@ -2100,11 +2087,11 @@ RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel,
 			 * If any old key value is NULL, the row could not have been
 			 * referenced by an FK row, so no check is needed.
 			 */
-			if (ri_NullCheck(old_row, riinfo, true) != RI_KEYS_NONE_NULL)
+			if (ri_NullCheck(oldslot, riinfo, true) != RI_KEYS_NONE_NULL)
 				return false;
 
 			/* If all old and new key values are equal, no check is needed */
-			if (ri_KeysEqual(pk_rel, old_row, new_row, riinfo, true))
+			if (ri_KeysEqual(pk_rel, oldslot, newslot, riinfo, true))
 				return false;
 
 			/* Else we need to fire the trigger. */
@@ -2139,7 +2126,7 @@ RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel,
  */
 bool
 RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel,
-							  HeapTuple old_row, HeapTuple new_row)
+							TupleTableSlot *oldslot, TupleTableSlot *newslot)
 {
 	const RI_ConstraintInfo *riinfo;
 
@@ -2156,7 +2143,7 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel,
 			 * If any new key value is NULL, the row must satisfy the
 			 * constraint, so no check is needed.
 			 */
-			if (ri_NullCheck(new_row, riinfo, false) != RI_KEYS_NONE_NULL)
+			if (ri_NullCheck(newslot, riinfo, false) != RI_KEYS_NONE_NULL)
 				return false;
 
 			/*
@@ -2167,11 +2154,11 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel,
 			 * UPDATE check.  (We could skip this if we knew the INSERT
 			 * trigger already fired, but there is no easy way to know that.)
 			 */
-			if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(old_row->t_data)))
+			if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(oldslot->tts_tuple->t_data)))
 				return true;
 
 			/* If all old and new key values are equal, no check is needed */
-			if (ri_KeysEqual(fk_rel, old_row, new_row, riinfo, false))
+			if (ri_KeysEqual(fk_rel, oldslot, newslot, riinfo, false))
 				return false;
 
 			/* Else we need to fire the trigger. */
@@ -2187,7 +2174,7 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel,
 			 * invalidated before the constraint is to be checked, but we
 			 * should queue the event to apply the check later.
 			 */
-			switch (ri_NullCheck(new_row, riinfo, false))
+			switch (ri_NullCheck(newslot, riinfo, false))
 			{
 				case RI_KEYS_ALL_NULL:
 					return false;
@@ -2205,11 +2192,11 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel,
 			 * UPDATE check.  (We could skip this if we knew the INSERT
 			 * trigger already fired, but there is no easy way to know that.)
 			 */
-			if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(old_row->t_data)))
+			if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(oldslot->tts_tuple->t_data)))
 				return true;
 
 			/* If all old and new key values are equal, no check is needed */
-			if (ri_KeysEqual(fk_rel, old_row, new_row, riinfo, false))
+			if (ri_KeysEqual(fk_rel, oldslot, newslot, riinfo, false))
 				return false;
 
 			/* Else we need to fire the trigger. */
@@ -2458,7 +2445,7 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	/* Did we find a tuple violating the constraint? */
 	if (SPI_processed > 0)
 	{
-		HeapTuple	tuple = SPI_tuptable->vals[0];
+		TupleTableSlot *slot = SPI_tuptable->vals[0];
 		TupleDesc	tupdesc = SPI_tuptable->tupdesc;
 		RI_ConstraintInfo fake_riinfo;
 
@@ -2481,7 +2468,7 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 		 * disallows partially-null FK rows.
 		 */
 		if (fake_riinfo.confmatchtype == FKCONSTR_MATCH_FULL &&
-			ri_NullCheck(tuple, &fake_riinfo, false) != RI_KEYS_NONE_NULL)
+			ri_NullCheck(slot, &fake_riinfo, false) != RI_KEYS_NONE_NULL)
 			ereport(ERROR,
 					(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
 					 errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
@@ -2498,7 +2485,7 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 		 */
 		ri_ReportViolation(&fake_riinfo,
 						   pk_rel, fk_rel,
-						   tuple, tupdesc,
+						   slot, tupdesc,
 						   RI_PLAN_CHECK_LOOKUPPK, false);
 	}
 
@@ -3038,11 +3025,10 @@ static bool
 ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 				RI_QueryKey *qkey, SPIPlanPtr qplan,
 				Relation fk_rel, Relation pk_rel,
-				HeapTuple old_tuple, HeapTuple new_tuple,
+				TupleTableSlot *oldslot, TupleTableSlot *newslot,
 				bool detectNewRows, int expect_OK)
 {
-	Relation	query_rel,
-				source_rel;
+	Relation	query_rel;
 	bool		source_is_pk;
 	Snapshot	test_snapshot;
 	Snapshot	crosscheck_snapshot;
@@ -3070,28 +3056,22 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	 * need some less klugy way to determine this.
 	 */
 	if (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK)
-	{
-		source_rel = fk_rel;
 		source_is_pk = false;
-	}
 	else
-	{
-		source_rel = pk_rel;
 		source_is_pk = true;
-	}
 
 	/* Extract the parameters to be passed into the query */
-	if (new_tuple)
+	if (newslot)
 	{
-		ri_ExtractValues(source_rel, new_tuple, riinfo, source_is_pk,
+		ri_ExtractValues(newslot, riinfo, source_is_pk,
 						 vals, nulls);
-		if (old_tuple)
-			ri_ExtractValues(source_rel, old_tuple, riinfo, source_is_pk,
+		if (oldslot)
+			ri_ExtractValues(oldslot, riinfo, source_is_pk,
 							 vals + riinfo->nkeys, nulls + riinfo->nkeys);
 	}
 	else
 	{
-		ri_ExtractValues(source_rel, old_tuple, riinfo, source_is_pk,
+		ri_ExtractValues(oldslot, riinfo, source_is_pk,
 						 vals, nulls);
 	}
 
@@ -3149,7 +3129,7 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	if (expect_OK >= 0 && spi_result != expect_OK)
 		ri_ReportViolation(riinfo,
 						   pk_rel, fk_rel,
-						   new_tuple ? new_tuple : old_tuple,
+						   newslot ? newslot : oldslot,
 						   NULL,
 						   qkey->constr_queryno, true);
 
@@ -3159,7 +3139,7 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 		(SPI_processed == 0) == (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK))
 		ri_ReportViolation(riinfo,
 						   pk_rel, fk_rel,
-						   new_tuple ? new_tuple : old_tuple,
+						   newslot ? newslot : oldslot,
 						   NULL,
 						   qkey->constr_queryno, false);
 
@@ -3170,11 +3150,9 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
  * Extract fields from a tuple into Datum/nulls arrays
  */
 static void
-ri_ExtractValues(Relation rel, HeapTuple tup,
-				 const RI_ConstraintInfo *riinfo, bool rel_is_pk,
-				 Datum *vals, char *nulls)
+ri_ExtractValues(TupleTableSlot *slot, const RI_ConstraintInfo *riinfo,
+				bool rel_is_pk, Datum *vals, char *nulls)
 {
-	TupleDesc	tupdesc = rel->rd_att;
 	const int16 *attnums;
 	int			i;
 	bool		isnull;
@@ -3186,8 +3164,7 @@ ri_ExtractValues(Relation rel, HeapTuple tup,
 
 	for (i = 0; i < riinfo->nkeys; i++)
 	{
-		vals[i] = heap_getattr(tup, attnums[i], tupdesc,
-							   &isnull);
+		vals[i] = slot_getattr(slot, attnums[i], &isnull);
 		nulls[i] = isnull ? 'n' : ' ';
 	}
 }
@@ -3204,7 +3181,7 @@ ri_ExtractValues(Relation rel, HeapTuple tup,
 static void
 ri_ReportViolation(const RI_ConstraintInfo *riinfo,
 				   Relation pk_rel, Relation fk_rel,
-				   HeapTuple violator, TupleDesc tupdesc,
+				   TupleTableSlot *violator, TupleDesc tupdesc,
 				   int queryno, bool spi_err)
 {
 	StringInfoData key_names;
@@ -3292,7 +3269,7 @@ ri_ReportViolation(const RI_ConstraintInfo *riinfo,
 					   *val;
 
 			name = SPI_fname(tupdesc, fnum);
-			val = SPI_getvalue(violator, tupdesc, fnum);
+			val = SPI_getvalue(violator, fnum);
 			if (!val)
 				val = "null";
 
@@ -3345,7 +3322,7 @@ ri_ReportViolation(const RI_ConstraintInfo *riinfo,
  * ----------
  */
 static int
-ri_NullCheck(HeapTuple tup,
+ri_NullCheck(TupleTableSlot *slot,
 			 const RI_ConstraintInfo *riinfo, bool rel_is_pk)
 {
 	const int16 *attnums;
@@ -3360,7 +3337,7 @@ ri_NullCheck(HeapTuple tup,
 
 	for (i = 0; i < riinfo->nkeys; i++)
 	{
-		if (heap_attisnull(tup, attnums[i]))
+		if (slot_attisnull(slot, attnums[i]))
 			nonenull = false;
 		else
 			allnull = false;
@@ -3511,10 +3488,9 @@ ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan)
  * ----------
  */
 static bool
-ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup,
+ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
 			 const RI_ConstraintInfo *riinfo, bool rel_is_pk)
 {
-	TupleDesc	tupdesc = RelationGetDescr(rel);
 	const int16 *attnums;
 	const Oid  *eq_oprs;
 	int			i;
@@ -3539,14 +3515,14 @@ ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup,
 		/*
 		 * Get one attribute's oldvalue. If it is NULL - they're not equal.
 		 */
-		oldvalue = heap_getattr(oldtup, attnums[i], tupdesc, &isnull);
+		oldvalue = slot_getattr(oldslot, attnums[i], &isnull);
 		if (isnull)
 			return false;
 
 		/*
 		 * Get one attribute's newvalue. If it is NULL - they're not equal.
 		 */
-		newvalue = heap_getattr(newtup, attnums[i], tupdesc, &isnull);
+		newvalue = slot_getattr(newslot, attnums[i], &isnull);
 		if (isnull)
 			return false;
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 5cfb916..54a31ce 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -362,9 +362,8 @@ static void push_ancestor_plan(deparse_namespace *dpns, ListCell *ancestor_cell,
 				   deparse_namespace *save_dpns);
 static void pop_ancestor_plan(deparse_namespace *dpns,
 				  deparse_namespace *save_dpns);
-static void make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
-			 int prettyFlags);
-static void make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
+static void make_ruledef(StringInfo buf, TupleTableSlot *ruleslot, int prettyFlags);
+static void make_viewdef(StringInfo buf, TupleTableSlot *ruleslot,
 			 int prettyFlags, int wrapColumn);
 static void get_query_def(Query *query, StringInfo buf, List *parentnamespace,
 			  TupleDesc resultDesc,
@@ -515,8 +514,7 @@ pg_get_ruledef_worker(Oid ruleoid, int prettyFlags)
 	Datum		args[1];
 	char		nulls[1];
 	int			spirc;
-	HeapTuple	ruletup;
-	TupleDesc	rulettc;
+	TupleTableSlot	*ruleslot;
 	StringInfoData buf;
 
 	/*
@@ -568,9 +566,8 @@ pg_get_ruledef_worker(Oid ruleoid, int prettyFlags)
 		/*
 		 * Get the rule's definition and put it into executor's memory
 		 */
-		ruletup = SPI_tuptable->vals[0];
-		rulettc = SPI_tuptable->tupdesc;
-		make_ruledef(&buf, ruletup, rulettc, prettyFlags);
+		ruleslot = SPI_tuptable->vals[0];
+		make_ruledef(&buf, ruleslot, prettyFlags);
 	}
 
 	/*
@@ -708,8 +705,7 @@ pg_get_viewdef_worker(Oid viewoid, int prettyFlags, int wrapColumn)
 	Datum		args[2];
 	char		nulls[2];
 	int			spirc;
-	HeapTuple	ruletup;
-	TupleDesc	rulettc;
+	TupleTableSlot *ruleslot;
 	StringInfoData buf;
 
 	/*
@@ -764,9 +760,8 @@ pg_get_viewdef_worker(Oid viewoid, int prettyFlags, int wrapColumn)
 		/*
 		 * Get the rule's definition and put it into executor's memory
 		 */
-		ruletup = SPI_tuptable->vals[0];
-		rulettc = SPI_tuptable->tupdesc;
-		make_viewdef(&buf, ruletup, rulettc, prettyFlags, wrapColumn);
+		ruleslot = SPI_tuptable->vals[0];
+		make_viewdef(&buf, ruleslot, prettyFlags, wrapColumn);
 	}
 
 	/*
@@ -4580,8 +4575,7 @@ pop_ancestor_plan(deparse_namespace *dpns, deparse_namespace *save_dpns)
  * ----------
  */
 static void
-make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
-			 int prettyFlags)
+make_ruledef(StringInfo buf, TupleTableSlot *ruleslot, int prettyFlags)
 {
 	char	   *rulename;
 	char		ev_type;
@@ -4593,36 +4587,37 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
 	int			fno;
 	Datum		dat;
 	bool		isnull;
+	TupleDesc	rulettc = ruleslot->tts_tupleDescriptor;
 
 	/*
 	 * Get the attribute values from the rules tuple
 	 */
 	fno = SPI_fnumber(rulettc, "rulename");
-	dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
+	dat = SPI_getbinval(ruleslot, fno, &isnull);
 	Assert(!isnull);
 	rulename = NameStr(*(DatumGetName(dat)));
 
 	fno = SPI_fnumber(rulettc, "ev_type");
-	dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
+	dat = SPI_getbinval(ruleslot, fno, &isnull);
 	Assert(!isnull);
 	ev_type = DatumGetChar(dat);
 
 	fno = SPI_fnumber(rulettc, "ev_class");
-	dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
+	dat = SPI_getbinval(ruleslot, fno, &isnull);
 	Assert(!isnull);
 	ev_class = DatumGetObjectId(dat);
 
 	fno = SPI_fnumber(rulettc, "is_instead");
-	dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
+	dat = SPI_getbinval(ruleslot, fno, &isnull);
 	Assert(!isnull);
 	is_instead = DatumGetBool(dat);
 
 	/* these could be nulls */
 	fno = SPI_fnumber(rulettc, "ev_qual");
-	ev_qual = SPI_getvalue(ruletup, rulettc, fno);
+	ev_qual = SPI_getvalue(ruleslot, fno);
 
 	fno = SPI_fnumber(rulettc, "ev_action");
-	ev_action = SPI_getvalue(ruletup, rulettc, fno);
+	ev_action = SPI_getvalue(ruleslot, fno);
 	if (ev_action != NULL)
 		actions = (List *) stringToNode(ev_action);
 
@@ -4762,7 +4757,7 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
  * ----------
  */
 static void
-make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
+make_viewdef(StringInfo buf, TupleTableSlot *ruleslot,
 			 int prettyFlags, int wrapColumn)
 {
 	Query	   *query;
@@ -4775,24 +4770,25 @@ make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
 	Relation	ev_relation;
 	int			fno;
 	bool		isnull;
+	TupleDesc rulettc = ruleslot->tts_tupleDescriptor;
 
 	/*
 	 * Get the attribute values from the rules tuple
 	 */
 	fno = SPI_fnumber(rulettc, "ev_type");
-	ev_type = (char) SPI_getbinval(ruletup, rulettc, fno, &isnull);
+	ev_type = (char) SPI_getbinval(ruleslot, fno, &isnull);
 
 	fno = SPI_fnumber(rulettc, "ev_class");
-	ev_class = (Oid) SPI_getbinval(ruletup, rulettc, fno, &isnull);
+	ev_class = (Oid) SPI_getbinval(ruleslot, fno, &isnull);
 
 	fno = SPI_fnumber(rulettc, "is_instead");
-	is_instead = (bool) SPI_getbinval(ruletup, rulettc, fno, &isnull);
+	is_instead = (bool) SPI_getbinval(ruleslot, fno, &isnull);
 
 	fno = SPI_fnumber(rulettc, "ev_qual");
-	ev_qual = SPI_getvalue(ruletup, rulettc, fno);
+	ev_qual = SPI_getvalue(ruleslot, fno);
 
 	fno = SPI_fnumber(rulettc, "ev_action");
-	ev_action = SPI_getvalue(ruletup, rulettc, fno);
+	ev_action = SPI_getvalue(ruleslot, fno);
 	if (ev_action != NULL)
 		actions = (List *) stringToNode(ev_action);
 
diff --git a/src/backend/utils/adt/trigfuncs.c b/src/backend/utils/adt/trigfuncs.c
index 50ea6d9..7709acd 100644
--- a/src/backend/utils/adt/trigfuncs.c
+++ b/src/backend/utils/adt/trigfuncs.c
@@ -60,8 +60,8 @@ suppress_redundant_updates_trigger(PG_FUNCTION_ARGS)
 				 errmsg("suppress_redundant_updates_trigger: must be called for each row")));
 
 	/* get tuple data, set default result */
-	rettuple = newtuple = trigdata->tg_newtuple;
-	oldtuple = trigdata->tg_trigtuple;
+	rettuple = newtuple = trigdata->tg_newslot->tts_tuple;
+	oldtuple = trigdata->tg_trigslot->tts_tuple;
 
 	newheader = newtuple->t_data;
 	oldheader = oldtuple->t_data;
diff --git a/src/backend/utils/adt/tsquery_rewrite.c b/src/backend/utils/adt/tsquery_rewrite.c
index 1bd3dea..e90b702 100644
--- a/src/backend/utils/adt/tsquery_rewrite.c
+++ b/src/backend/utils/adt/tsquery_rewrite.c
@@ -326,13 +326,13 @@ tsquery_rewrite_query(PG_FUNCTION_ARGS)
 
 		for (i = 0; i < SPI_processed && tree; i++)
 		{
-			Datum		qdata = SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 1, &isnull);
+			Datum		qdata = SPI_getbinval(SPI_tuptable->vals[i], 1, &isnull);
 			Datum		sdata;
 
 			if (isnull)
 				continue;
 
-			sdata = SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 2, &isnull);
+			sdata = SPI_getbinval(SPI_tuptable->vals[i], 2, &isnull);
 
 			if (!isnull)
 			{
diff --git a/src/backend/utils/adt/tsvector_op.c b/src/backend/utils/adt/tsvector_op.c
index 2d7407c..448a2af 100644
--- a/src/backend/utils/adt/tsvector_op.c
+++ b/src/backend/utils/adt/tsvector_op.c
@@ -2357,7 +2357,7 @@ ts_stat_sql(MemoryContext persistentContext, text *txt, text *ws)
 
 		for (i = 0; i < SPI_processed; i++)
 		{
-			Datum		data = SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 1, &isnull);
+			Datum		data = SPI_getbinval(SPI_tuptable->vals[i], 1, &isnull);
 
 			if (!isnull)
 				stat = ts_accum(persistentContext, stat, data);
@@ -2456,7 +2456,7 @@ tsvector_update_trigger(PG_FUNCTION_ARGS, bool config_column)
 	TriggerData *trigdata;
 	Trigger    *trigger;
 	Relation	rel;
-	HeapTuple	rettuple = NULL;
+	TupleTableSlot *retslot = NULL;
 	int			tsvector_attr_num,
 				i;
 	ParsedText	prs;
@@ -2476,9 +2476,9 @@ tsvector_update_trigger(PG_FUNCTION_ARGS, bool config_column)
 		elog(ERROR, "tsvector_update_trigger: must be fired BEFORE event");
 
 	if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
-		rettuple = trigdata->tg_trigtuple;
+		retslot = trigdata->tg_trigslot;
 	else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
-		rettuple = trigdata->tg_newtuple;
+		retslot = trigdata->tg_newslot;
 	else
 		elog(ERROR, "tsvector_update_trigger: must be fired for INSERT or UPDATE");
 
@@ -2521,7 +2521,7 @@ tsvector_update_trigger(PG_FUNCTION_ARGS, bool config_column)
 					 errmsg("column \"%s\" is not of regconfig type",
 							trigger->tgargs[1])));
 
-		datum = SPI_getbinval(rettuple, rel->rd_att, config_attr_num, &isnull);
+		datum = SPI_getbinval(retslot, config_attr_num, &isnull);
 		if (isnull)
 			ereport(ERROR,
 					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
@@ -2566,7 +2566,7 @@ tsvector_update_trigger(PG_FUNCTION_ARGS, bool config_column)
 					 errmsg("column \"%s\" is not of a character type",
 							trigger->tgargs[i])));
 
-		datum = SPI_getbinval(rettuple, rel->rd_att, numattr, &isnull);
+		datum = SPI_getbinval(retslot, numattr, &isnull);
 		if (isnull)
 			continue;
 
@@ -2583,9 +2583,11 @@ tsvector_update_trigger(PG_FUNCTION_ARGS, bool config_column)
 	{
 		datum = PointerGetDatum(make_tsvector(&prs));
 		isnull = false;
-		rettuple = heap_modify_tuple_by_cols(rettuple, rel->rd_att,
-											 1, &tsvector_attr_num,
-											 &datum, &isnull);
+
+		Assert(retslot->tts_tupleDescriptor != rel->rd_att);
+		retslot = heap_modify_slot_by_cols(retslot,
+									    	1, &tsvector_attr_num,
+											&datum, &isnull);
 		pfree(DatumGetPointer(datum));
 	}
 	else
@@ -2596,11 +2598,13 @@ tsvector_update_trigger(PG_FUNCTION_ARGS, bool config_column)
 		out->size = 0;
 		datum = PointerGetDatum(out);
 		isnull = false;
-		rettuple = heap_modify_tuple_by_cols(rettuple, rel->rd_att,
+
+		Assert(retslot->tts_tupleDescriptor != rel->rd_att);
+		retslot = heap_modify_slot_by_cols(retslot,
 											 1, &tsvector_attr_num,
 											 &datum, &isnull);
 		pfree(prs.words);
 	}
 
-	return PointerGetDatum(rettuple);
+	return PointerGetDatum(retslot->tts_tuple);
 }
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 323614c..e0deec1 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -2331,7 +2331,6 @@ query_to_oid_list(const char *query)
 		bool		isnull;
 
 		oid = SPI_getbinval(SPI_tuptable->vals[i],
-							SPI_tuptable->tupdesc,
 							1,
 							&isnull);
 		if (!isnull)
@@ -3638,7 +3637,6 @@ SPI_sql_row_to_xmlelement(uint64 rownum, StringInfo result, char *tablename,
 		colname = map_sql_identifier_to_xml_name(SPI_fname(SPI_tuptable->tupdesc, i),
 												 true, false);
 		colval = SPI_getbinval(SPI_tuptable->vals[rownum],
-							   SPI_tuptable->tupdesc,
 							   i,
 							   &isnull);
 		if (isnull)
diff --git a/src/include/access/htup.h b/src/include/access/htup.h
index 61b3e68..cb3f9f0 100644
--- a/src/include/access/htup.h
+++ b/src/include/access/htup.h
@@ -68,6 +68,7 @@ typedef struct HeapTupleData
 } HeapTupleData;
 
 typedef HeapTupleData *HeapTuple;
+typedef void* StorageTuple;
 
 #define HEAPTUPLESIZE	MAXALIGN(sizeof(HeapTupleData))
 
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 36c1134..7bf6e41 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -32,11 +32,9 @@ typedef struct TriggerData
 	NodeTag		type;
 	TriggerEvent tg_event;
 	Relation	tg_relation;
-	HeapTuple	tg_trigtuple;
-	HeapTuple	tg_newtuple;
+	TupleTableSlot*	tg_trigslot;
+	TupleTableSlot*	tg_newslot;
 	Trigger    *tg_trigger;
-	Buffer		tg_trigtuplebuf;
-	Buffer		tg_newtuplebuf;
 	Tuplestorestate *tg_oldtable;
 	Tuplestorestate *tg_newtable;
 } TriggerData;
@@ -188,7 +186,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,
@@ -203,15 +201,15 @@ extern bool ExecBRDeleteTriggers(EState *estate,
 					 EPQState *epqstate,
 					 ResultRelInfo *relinfo,
 					 ItemPointer tupleid,
-					 HeapTuple fdw_trigtuple);
+					 TupleTableSlot *trigslot);
 extern void ExecARDeleteTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
 					 ItemPointer tupleid,
-					 HeapTuple fdw_trigtuple,
+					 TupleTableSlot *slot,
 					 TransitionCaptureState *transition_capture);
 extern bool ExecIRDeleteTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
-					 HeapTuple trigtuple);
+					 TupleTableSlot *slot);
 extern void ExecBSUpdateTriggers(EState *estate,
 					 ResultRelInfo *relinfo);
 extern void ExecASUpdateTriggers(EState *estate,
@@ -221,18 +219,18 @@ extern TupleTableSlot *ExecBRUpdateTriggers(EState *estate,
 					 EPQState *epqstate,
 					 ResultRelInfo *relinfo,
 					 ItemPointer tupleid,
-					 HeapTuple fdw_trigtuple,
+					 TupleTableSlot *trigslot,
 					 TupleTableSlot *slot);
 extern void ExecARUpdateTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
 					 ItemPointer tupleid,
-					 HeapTuple fdw_trigtuple,
-					 HeapTuple newtuple,
+					 TupleTableSlot *oldslot,
+					 TupleTableSlot *slot,
 					 List *recheckIndexes,
 					 TransitionCaptureState *transition_capture);
 extern TupleTableSlot *ExecIRUpdateTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
-					 HeapTuple trigtuple,
+					 TupleTableSlot *oldslot,
 					 TupleTableSlot *slot);
 extern void ExecBSTruncateTriggers(EState *estate,
 					   ResultRelInfo *relinfo);
@@ -254,9 +252,9 @@ extern bool AfterTriggerPendingOnRel(Oid relid);
  * in utils/adt/ri_triggers.c
  */
 extern bool RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel,
-							  HeapTuple old_row, HeapTuple new_row);
+							TupleTableSlot *oldslot, TupleTableSlot *newslot);
 extern bool RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel,
-							  HeapTuple old_row, HeapTuple new_row);
+							TupleTableSlot *oldslot, TupleTableSlot *newslot);
 extern bool RI_Initial_Check(Trigger *trigger,
 				 Relation fk_rel, Relation pk_rel);
 
diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h
index acade7e..ca93f89 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 */
+	TupleTableSlot **vals;			/* tuples */
 	slist_node	next;			/* link for internal bookkeeping */
 	SubTransactionId subid;		/* subxact in which tuptable was created */
 } SPITupleTable;
@@ -121,10 +121,12 @@ 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 TupleTableSlot* SPI_modifyslot(TupleTableSlot *slot, 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);
-extern Datum SPI_getbinval(HeapTuple tuple, TupleDesc tupdesc, int fnumber, bool *isnull);
+extern char *SPI_getvalue(TupleTableSlot *slot, int fnumber);
+extern Datum SPI_getbinval(TupleTableSlot *slot, int fnumber, bool *isnull);
 extern char *SPI_gettype(TupleDesc tupdesc, int fnumber);
 extern Oid	SPI_gettypeid(TupleDesc tupdesc, int fnumber);
 extern char *SPI_getrelname(Relation rel);
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index 55f4cce..13706aa 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -117,6 +117,7 @@ typedef struct TupleTableSlot
 	bool		tts_shouldFree; /* should pfree tts_tuple? */
 	bool		tts_shouldFreeMin;	/* should pfree tts_mintuple? */
 	bool		tts_slow;		/* saved state for slot_deform_tuple */
+	ItemPointerData tts_tid;
 	HeapTuple	tts_tuple;		/* physical tuple, or NULL if virtual */
 	TupleDesc	tts_tupleDescriptor;	/* slot's tuple descriptor */
 	MemoryContext tts_mcxt;		/* slot itself is in this context */
@@ -152,6 +153,11 @@ extern TupleTableSlot *ExecStoreTuple(HeapTuple tuple,
 extern TupleTableSlot *ExecStoreMinimalTuple(MinimalTuple mtup,
 					  TupleTableSlot *slot,
 					  bool shouldFree);
+TupleTableSlot* heap_modify_slot_by_cols(TupleTableSlot *slot,
+						 int nCols,
+						 int *replCols,
+						 Datum *replValues,
+						 bool *replIsnull);
 extern TupleTableSlot *ExecClearTuple(TupleTableSlot *slot);
 extern TupleTableSlot *ExecStoreVirtualTuple(TupleTableSlot *slot);
 extern TupleTableSlot *ExecStoreAllNullTuple(TupleTableSlot *slot);
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index 5a45e3e..a760a24 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -1584,7 +1584,7 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo)
 		event = "INSERT";
 		if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event))
 			hv_store_string(hv, "new",
-							plperl_hash_from_tuple(tdata->tg_trigtuple,
+							plperl_hash_from_tuple(tdata->tg_trigslot,
 												   tupdesc));
 	}
 	else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event))
@@ -1592,7 +1592,7 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo)
 		event = "DELETE";
 		if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event))
 			hv_store_string(hv, "old",
-							plperl_hash_from_tuple(tdata->tg_trigtuple,
+							plperl_hash_from_tuple(tdata->tg_trigslot,
 												   tupdesc));
 	}
 	else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event))
@@ -1601,10 +1601,10 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo)
 		if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event))
 		{
 			hv_store_string(hv, "old",
-							plperl_hash_from_tuple(tdata->tg_trigtuple,
+							plperl_hash_from_tuple(tdata->tg_trigslot,
 												   tupdesc));
 			hv_store_string(hv, "new",
-							plperl_hash_from_tuple(tdata->tg_newtuple,
+							plperl_hash_from_tuple(tdata->tg_newslot,
 												   tupdesc));
 		}
 	}
@@ -2486,13 +2486,13 @@ plperl_trigger_handler(PG_FUNCTION_ARGS)
 		TriggerData *trigdata = ((TriggerData *) fcinfo->context);
 
 		if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
-			retval = (Datum) trigdata->tg_trigtuple;
+			retval = (Datum) trigdata->tg_trigslot;
 		else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
-			retval = (Datum) trigdata->tg_newtuple;
+			retval = (Datum) trigdata->tg_newslot;
 		else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
-			retval = (Datum) trigdata->tg_trigtuple;
+			retval = (Datum) trigdata->tg_trigslot;
 		else if (TRIGGER_FIRED_BY_TRUNCATE(trigdata->tg_event))
-			retval = (Datum) trigdata->tg_trigtuple;
+			retval = (Datum) trigdata->tg_trigslot;
 		else
 			retval = (Datum) 0; /* can this happen? */
 	}
@@ -2511,10 +2511,10 @@ plperl_trigger_handler(PG_FUNCTION_ARGS)
 
 			if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
 				trv = plperl_modify_tuple(hvTD, trigdata,
-										  trigdata->tg_trigtuple);
+										  trigdata->tg_trigslot);
 			else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
 				trv = plperl_modify_tuple(hvTD, trigdata,
-										  trigdata->tg_newtuple);
+										  trigdata->tg_newslot);
 			else
 			{
 				ereport(WARNING,
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 662b3c9..019404d 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -1949,7 +1949,7 @@ plpgsql_build_record(const char *refname, int lineno, bool add2namespace)
 	rec->dtype = PLPGSQL_DTYPE_REC;
 	rec->refname = pstrdup(refname);
 	rec->lineno = lineno;
-	rec->tup = NULL;
+	rec->slot = NULL;
 	rec->tupdesc = NULL;
 	rec->freetup = false;
 	rec->freetupdesc = false;
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index c98492b..6f50937 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -275,7 +275,7 @@ static void plpgsql_param_fetch(ParamListInfo params, int paramid);
 static void exec_move_row(PLpgSQL_execstate *estate,
 			  PLpgSQL_rec *rec,
 			  PLpgSQL_row *row,
-			  HeapTuple tup, TupleDesc tupdesc);
+			  TupleTableSlot *slot, TupleDesc tupdesc);
 static HeapTuple make_tuple_from_row(PLpgSQL_execstate *estate,
 					PLpgSQL_row *row,
 					TupleDesc tupdesc);
@@ -612,7 +612,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
  *				trigger execution.
  * ----------
  */
-HeapTuple
+TupleTableSlot *
 plpgsql_exec_trigger(PLpgSQL_function *func,
 					 TriggerData *trigdata)
 {
@@ -668,23 +668,23 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
 		/*
 		 * Per-statement triggers don't use OLD/NEW variables
 		 */
-		rec_new->tup = NULL;
-		rec_old->tup = NULL;
+		rec_new->slot = NULL;
+		rec_old->slot = NULL;
 	}
 	else if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
 	{
-		rec_new->tup = trigdata->tg_trigtuple;
-		rec_old->tup = NULL;
+		rec_new->slot = trigdata->tg_trigslot;
+		rec_old->slot = NULL;
 	}
 	else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
 	{
-		rec_new->tup = trigdata->tg_newtuple;
-		rec_old->tup = trigdata->tg_trigtuple;
+		rec_new->slot = trigdata->tg_newslot;
+		rec_old->slot = trigdata->tg_trigslot;
 	}
 	else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
 	{
-		rec_new->tup = NULL;
-		rec_old->tup = trigdata->tg_trigtuple;
+		rec_new->slot = NULL;
+		rec_old->slot = trigdata->tg_trigslot;
 	}
 	else
 		elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, or UPDATE");
@@ -1054,7 +1054,7 @@ copy_plpgsql_datum(PLpgSQL_datum *datum)
 
 				memcpy(new, datum, sizeof(PLpgSQL_rec));
 				/* should be preset to null/non-freeable */
-				Assert(new->tup == NULL);
+				Assert(new->slot == NULL);
 				Assert(new->tupdesc == NULL);
 				Assert(!new->freetup);
 				Assert(!new->freetupdesc);
@@ -1240,7 +1240,7 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
 
 					if (rec->freetup)
 					{
-						heap_freetuple(rec->tup);
+						ExecDropSingleTupleTableSlot(rec->slot);
 						rec->freetup = false;
 					}
 					if (rec->freetupdesc)
@@ -1248,7 +1248,7 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
 						FreeTupleDesc(rec->tupdesc);
 						rec->freetupdesc = false;
 					}
-					rec->tup = NULL;
+					rec->slot = NULL;
 					rec->tupdesc = NULL;
 				}
 				break;
@@ -2695,11 +2695,11 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
 					PLpgSQL_rec *rec = (PLpgSQL_rec *) retvar;
 					int32		rettypmod;
 
-					if (HeapTupleIsValid(rec->tup))
+					if (HeapTupleIsValid(rec->slot))
 					{
 						if (estate->retistuple)
 						{
-							estate->retval = PointerGetDatum(rec->tup);
+							estate->retval = PointerGetDatum(rec->slot->tts_tuple);
 							estate->rettupdesc = rec->tupdesc;
 							estate->retisnull = false;
 						}
@@ -2872,7 +2872,7 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
 					PLpgSQL_rec *rec = (PLpgSQL_rec *) retvar;
 					TupleConversionMap *tupmap;
 
-					if (!HeapTupleIsValid(rec->tup))
+					if (!HeapTupleIsValid(rec->slot))
 						ereport(ERROR,
 								(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 								 errmsg("record \"%s\" is not assigned yet",
@@ -2885,7 +2885,7 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
 					tupmap = convert_tuples_by_position(rec->tupdesc,
 														tupdesc,
 														gettext_noop("wrong record type supplied in RETURN NEXT"));
-					tuple = rec->tup;
+					tuple = rec->slot->tts_tuple;
 					if (tupmap)
 						tuple = do_convert_tuple(tuple, tupmap);
 					tuplestore_puttuple(estate->tuple_store, tuple);
@@ -3058,10 +3058,11 @@ exec_stmt_return_query(PLpgSQL_execstate *estate,
 
 		for (i = 0; i < SPI_processed; i++)
 		{
-			HeapTuple	tuple = SPI_tuptable->vals[i];
+			TupleTableSlot *slot = SPI_tuptable->vals[i];
+			HeapTuple	tuple;
 
 			if (tupmap)
-				tuple = do_convert_tuple(tuple, tupmap);
+				tuple = do_convert_tuple(slot->tts_tuple, tupmap);
 			tuplestore_puttuple(estate->tuple_store, tuple);
 			if (tupmap)
 				heap_freetuple(tuple);
@@ -4552,7 +4553,6 @@ exec_assign_value(PLpgSQL_execstate *estate,
 				PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target;
 				PLpgSQL_rec *rec;
 				int			fno;
-				HeapTuple	newtup;
 				int			colnums[1];
 				Datum		values[1];
 				bool		nulls[1];
@@ -4566,7 +4566,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
 				 * that because records don't have any predefined field
 				 * structure.
 				 */
-				if (!HeapTupleIsValid(rec->tup))
+				if (!HeapTupleIsValid(rec->slot))
 					ereport(ERROR,
 							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							 errmsg("record \"%s\" is not assigned yet",
@@ -4600,15 +4600,9 @@ exec_assign_value(PLpgSQL_execstate *estate,
 											atttypmod);
 				nulls[0] = isNull;
 
-				newtup = heap_modify_tuple_by_cols(rec->tup, rec->tupdesc,
+				rec->slot = heap_modify_slot_by_cols(rec->slot,
 												   1, colnums, values, nulls);
-
-				if (rec->freetup)
-					heap_freetuple(rec->tup);
-
-				rec->tup = newtup;
-				rec->freetup = true;
-
+				rec->freetup = false;
 				break;
 			}
 
@@ -4874,7 +4868,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
 			{
 				PLpgSQL_rec *rec = (PLpgSQL_rec *) datum;
 
-				if (!HeapTupleIsValid(rec->tup))
+				if (!HeapTupleIsValid(rec->slot))
 					ereport(ERROR,
 							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							 errmsg("record \"%s\" is not assigned yet",
@@ -4887,7 +4881,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
 				oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
 				*typeid = rec->tupdesc->tdtypeid;
 				*typetypmod = rec->tupdesc->tdtypmod;
-				*value = heap_copy_tuple_as_datum(rec->tup, rec->tupdesc);
+				*value = heap_copy_tuple_as_datum(rec->slot->tts_tuple, rec->tupdesc);
 				*isnull = false;
 				MemoryContextSwitchTo(oldcontext);
 				break;
@@ -4900,7 +4894,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
 				int			fno;
 
 				rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
-				if (!HeapTupleIsValid(rec->tup))
+				if (!HeapTupleIsValid(rec->slot))
 					ereport(ERROR,
 							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 							 errmsg("record \"%s\" is not assigned yet",
@@ -4917,7 +4911,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
 					*typetypmod = rec->tupdesc->attrs[fno - 1]->atttypmod;
 				else
 					*typetypmod = -1;
-				*value = SPI_getbinval(rec->tup, rec->tupdesc, fno, isnull);
+				*value = SPI_getbinval(rec->slot, fno, isnull);
 				break;
 			}
 
@@ -5236,8 +5230,7 @@ exec_eval_expr(PLpgSQL_execstate *estate,
 	/*
 	 * Return the single result Datum.
 	 */
-	return SPI_getbinval(estate->eval_tuptable->vals[0],
-						 estate->eval_tuptable->tupdesc, 1, isNull);
+	return SPI_getbinval(estate->eval_tuptable->vals[0], 1, isNull);
 }
 
 
@@ -5936,7 +5929,7 @@ static void
 exec_move_row(PLpgSQL_execstate *estate,
 			  PLpgSQL_rec *rec,
 			  PLpgSQL_row *row,
-			  HeapTuple tup, TupleDesc tupdesc)
+			  TupleTableSlot *slot, TupleDesc tupdesc)
 {
 	/*
 	 * Record is simple - just copy the tuple and its descriptor into the
@@ -5944,30 +5937,13 @@ exec_move_row(PLpgSQL_execstate *estate,
 	 */
 	if (rec != NULL)
 	{
-		/*
-		 * Copy input first, just in case it is pointing at variable's value
-		 */
-		if (HeapTupleIsValid(tup))
-			tup = heap_copytuple(tup);
-		else if (tupdesc)
-		{
-			/* If we have a tupdesc but no data, form an all-nulls tuple */
-			bool	   *nulls;
-
-			nulls = (bool *)
-				eval_mcontext_alloc(estate, tupdesc->natts * sizeof(bool));
-			memset(nulls, true, tupdesc->natts * sizeof(bool));
-
-			tup = heap_form_tuple(tupdesc, NULL, nulls);
-		}
-
 		if (tupdesc)
 			tupdesc = CreateTupleDescCopy(tupdesc);
 
 		/* Free the old value ... */
 		if (rec->freetup)
 		{
-			heap_freetuple(rec->tup);
+			ExecDropSingleTupleTableSlot(rec->slot);
 			rec->freetup = false;
 		}
 		if (rec->freetupdesc)
@@ -5977,13 +5953,13 @@ exec_move_row(PLpgSQL_execstate *estate,
 		}
 
 		/* ... and install the new */
-		if (HeapTupleIsValid(tup))
+		if (HeapTupleIsValid(slot))
 		{
-			rec->tup = tup;
+			ExecCopySlot(rec->slot, slot);
 			rec->freetup = true;
 		}
 		else
-			rec->tup = NULL;
+			rec->slot = NULL;
 
 		if (tupdesc)
 		{
@@ -6019,8 +5995,8 @@ exec_move_row(PLpgSQL_execstate *estate,
 		int			fnum;
 		int			anum;
 
-		if (HeapTupleIsValid(tup))
-			t_natts = HeapTupleHeaderGetNatts(tup->t_data);
+		if (HeapTupleIsValid(slot))
+			t_natts = HeapTupleHeaderGetNatts(slot->tts_tuple->t_data);
 		else
 			t_natts = 0;
 
@@ -6044,7 +6020,7 @@ exec_move_row(PLpgSQL_execstate *estate,
 			if (anum < td_natts)
 			{
 				if (anum < t_natts)
-					value = SPI_getbinval(tup, tupdesc, anum + 1, &isnull);
+					value = SPI_getbinval(slot, anum + 1, &isnull);
 				else
 				{
 					value = (Datum) 0;
@@ -6188,6 +6164,7 @@ exec_move_row_from_datum(PLpgSQL_execstate *estate,
 	int32		tupTypmod;
 	TupleDesc	tupdesc;
 	HeapTupleData tmptup;
+	TupleTableSlot *slot;
 
 	/* Extract rowtype info and find a tupdesc */
 	tupType = HeapTupleHeaderGetTypeId(td);
@@ -6200,8 +6177,11 @@ exec_move_row_from_datum(PLpgSQL_execstate *estate,
 	tmptup.t_tableOid = InvalidOid;
 	tmptup.t_data = td;
 
+	slot = MakeSingleTupleTableSlot(tupdesc);
+	slot->tts_tuple = &tmptup;
+
 	/* Do the move */
-	exec_move_row(estate, rec, row, &tmptup, tupdesc);
+	exec_move_row(estate, rec, row, slot, tupdesc);
 
 	/* Release tupdesc usage count */
 	ReleaseTupleDesc(tupdesc);
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 2b19948..fc46206 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -304,7 +304,7 @@ typedef struct PLpgSQL_rec
 	char	   *refname;
 	int			lineno;
 
-	HeapTuple	tup;
+	TupleTableSlot *slot;
 	TupleDesc	tupdesc;
 	bool		freetup;
 	bool		freetupdesc;
diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c
index c6938d0..63cdc31 100644
--- a/src/pl/plpython/plpy_exec.c
+++ b/src/pl/plpython/plpy_exec.c
@@ -790,36 +790,36 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r
 				pltevent = PyString_FromString("INSERT");
 
 				PyDict_SetItemString(pltdata, "old", Py_None);
-				pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple,
+				pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_trigslot,
 										   tdata->tg_relation->rd_att);
 				PyDict_SetItemString(pltdata, "new", pytnew);
 				Py_DECREF(pytnew);
-				*rv = tdata->tg_trigtuple;
+				*rv = tdata->tg_trigslot;
 			}
 			else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event))
 			{
 				pltevent = PyString_FromString("DELETE");
 
 				PyDict_SetItemString(pltdata, "new", Py_None);
-				pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple,
+				pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigslot,
 										   tdata->tg_relation->rd_att);
 				PyDict_SetItemString(pltdata, "old", pytold);
 				Py_DECREF(pytold);
-				*rv = tdata->tg_trigtuple;
+				*rv = tdata->tg_trigslot;
 			}
 			else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event))
 			{
 				pltevent = PyString_FromString("UPDATE");
 
-				pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_newtuple,
+				pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_newslot,
 										   tdata->tg_relation->rd_att);
 				PyDict_SetItemString(pltdata, "new", pytnew);
 				Py_DECREF(pytnew);
-				pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple,
+				pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigslot,
 										   tdata->tg_relation->rd_att);
 				PyDict_SetItemString(pltdata, "old", pytold);
 				Py_DECREF(pytold);
-				*rv = tdata->tg_newtuple;
+				*rv = tdata->tg_newslot;
 			}
 			else
 			{
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index ed494e1..c335eef 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -1134,7 +1134,7 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
 									 Tcl_NewStringObj("ROW", -1));
 
 			/* Build the data list for the trigtuple */
-			tcl_trigtup = pltcl_build_tuple_argument(trigdata->tg_trigtuple,
+			tcl_trigtup = pltcl_build_tuple_argument(trigdata->tg_trigslot,
 													 tupdesc);
 
 			/*
@@ -1149,7 +1149,7 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
 				Tcl_ListObjAppendElement(NULL, tcl_cmd, tcl_trigtup);
 				Tcl_ListObjAppendElement(NULL, tcl_cmd, Tcl_NewObj());
 
-				rettup = trigdata->tg_trigtuple;
+				rettup = trigdata->tg_trigslot;
 			}
 			else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
 			{
@@ -1159,20 +1159,20 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
 				Tcl_ListObjAppendElement(NULL, tcl_cmd, Tcl_NewObj());
 				Tcl_ListObjAppendElement(NULL, tcl_cmd, tcl_trigtup);
 
-				rettup = trigdata->tg_trigtuple;
+				rettup = trigdata->tg_trigslot;
 			}
 			else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
 			{
 				Tcl_ListObjAppendElement(NULL, tcl_cmd,
 										 Tcl_NewStringObj("UPDATE", -1));
 
-				tcl_newtup = pltcl_build_tuple_argument(trigdata->tg_newtuple,
+				tcl_newtup = pltcl_build_tuple_argument(trigdata->tg_newslot,
 														tupdesc);
 
 				Tcl_ListObjAppendElement(NULL, tcl_cmd, tcl_newtup);
 				Tcl_ListObjAppendElement(NULL, tcl_cmd, tcl_trigtup);
 
-				rettup = trigdata->tg_newtuple;
+				rettup = trigdata->tg_newslot;
 			}
 			else
 				elog(ERROR, "unrecognized OP tg_event: %u", trigdata->tg_event);
diff --git a/src/test/modules/worker_spi/worker_spi.c b/src/test/modules/worker_spi/worker_spi.c
index 12c8cd5..cb61dcf 100644
--- a/src/test/modules/worker_spi/worker_spi.c
+++ b/src/test/modules/worker_spi/worker_spi.c
@@ -126,7 +126,6 @@ initialize_worker_spi(worktable *table)
 		elog(FATAL, "not a singleton result");
 
 	ntup = DatumGetInt64(SPI_getbinval(SPI_tuptable->vals[0],
-									   SPI_tuptable->tupdesc,
 									   1, &isnull));
 	if (isnull)
 		elog(FATAL, "null result");
@@ -281,7 +280,6 @@ worker_spi_main(Datum main_arg)
 			int32		val;
 
 			val = DatumGetInt32(SPI_getbinval(SPI_tuptable->vals[0],
-											  SPI_tuptable->tupdesc,
 											  1, &isnull));
 			if (!isnull)
 				elog(LOG, "%s: count in %s.%s is now %d",
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index b73bcce..5e0a941 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -347,7 +347,7 @@ funny_dup17(PG_FUNCTION_ARGS)
 	bool	   *recursion;
 	Relation	rel;
 	TupleDesc	tupdesc;
-	HeapTuple	tuple;
+	TupleTableSlot *slot;
 	char	   *query,
 			   *fieldval,
 			   *fieldtype;
@@ -359,7 +359,7 @@ funny_dup17(PG_FUNCTION_ARGS)
 	if (!CALLED_AS_TRIGGER(fcinfo))
 		elog(ERROR, "funny_dup17: not fired by trigger manager");
 
-	tuple = trigdata->tg_trigtuple;
+	slot = trigdata->tg_trigslot;
 	rel = trigdata->tg_relation;
 	tupdesc = rel->rd_att;
 	if (TRIGGER_FIRED_BEFORE(trigdata->tg_event))
@@ -387,17 +387,17 @@ funny_dup17(PG_FUNCTION_ARGS)
 	if (*level == 17)
 	{
 		*recursion = false;
-		return PointerGetDatum(tuple);
+		return PointerGetDatum(slot->tts_tuple);
 	}
 
 	if (!(*recursion))
-		return PointerGetDatum(tuple);
+		return PointerGetDatum(slot->tts_tuple);
 
 	(*level)++;
 
 	SPI_connect();
 
-	fieldval = SPI_getvalue(tuple, tupdesc, 1);
+	fieldval = SPI_getvalue(slot, 1);
 	fieldtype = SPI_gettype(tupdesc, 1);
 
 	query = (char *) palloc(100 + NAMEDATALEN * 3 +
@@ -426,11 +426,9 @@ funny_dup17(PG_FUNCTION_ARGS)
 	if (SPI_processed > 0)
 	{
 		selected = DatumGetInt32(DirectFunctionCall1(int4in,
-													 CStringGetDatum(SPI_getvalue(
-																				  SPI_tuptable->vals[0],
-																				  SPI_tuptable->tupdesc,
-																				  1
-																				  ))));
+												CStringGetDatum(SPI_getvalue(
+													   SPI_tuptable->vals[0],
+													   1))));
 	}
 
 	elog(DEBUG4, "funny_dup17 (fired %s) on level %3d: " UINT64_FORMAT "/%d tuples inserted/selected",
@@ -443,7 +441,7 @@ funny_dup17(PG_FUNCTION_ARGS)
 	if (*level == 0)
 		*xid = InvalidTransactionId;
 
-	return PointerGetDatum(tuple);
+	return PointerGetDatum(slot->tts_tuple);
 }
 
 #define TTDUMMY_INFINITY	999999
@@ -468,8 +466,8 @@ ttdummy(PG_FUNCTION_ARGS)
 	char	   *cnulls;			/* column nulls */
 	char	   *relname;		/* triggered relation name */
 	Relation	rel;			/* triggered relation */
-	HeapTuple	trigtuple;
-	HeapTuple	newtuple = NULL;
+	TupleTableSlot *trigslot;
+	TupleTableSlot *newslot = NULL;
 	HeapTuple	rettuple;
 	TupleDesc	tupdesc;		/* tuple description */
 	int			natts;			/* # of attributes */
@@ -486,9 +484,9 @@ ttdummy(PG_FUNCTION_ARGS)
 	if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
 		elog(ERROR, "ttdummy: cannot process INSERT event");
 	if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
-		newtuple = trigdata->tg_newtuple;
+		newslot = trigdata->tg_newslot;
 
-	trigtuple = trigdata->tg_trigtuple;
+	trigslot = trigdata->tg_trigslot;
 
 	rel = trigdata->tg_relation;
 	relname = SPI_getrelname(rel);
@@ -497,7 +495,8 @@ ttdummy(PG_FUNCTION_ARGS)
 	if (ttoff)					/* OFF - nothing to do */
 	{
 		pfree(relname);
-		return PointerGetDatum((newtuple != NULL) ? newtuple : trigtuple);
+		return PointerGetDatum((newslot != NULL) ?
+				newslot->tts_tuple : trigslot->tts_tuple);
 	}
 
 	trigger = trigdata->tg_trigger;
@@ -521,20 +520,20 @@ ttdummy(PG_FUNCTION_ARGS)
 				 relname, args[i]);
 	}
 
-	oldon = SPI_getbinval(trigtuple, tupdesc, attnum[0], &isnull);
+	oldon = SPI_getbinval(trigslot, attnum[0], &isnull);
 	if (isnull)
 		elog(ERROR, "ttdummy (%s): %s must be NOT NULL", relname, args[0]);
 
-	oldoff = SPI_getbinval(trigtuple, tupdesc, attnum[1], &isnull);
+	oldoff = SPI_getbinval(trigslot, attnum[1], &isnull);
 	if (isnull)
 		elog(ERROR, "ttdummy (%s): %s must be NOT NULL", relname, args[1]);
 
-	if (newtuple != NULL)		/* UPDATE */
+	if (newslot != NULL)		/* UPDATE */
 	{
-		newon = SPI_getbinval(newtuple, tupdesc, attnum[0], &isnull);
+		newon = SPI_getbinval(newslot, attnum[0], &isnull);
 		if (isnull)
 			elog(ERROR, "ttdummy (%s): %s must be NOT NULL", relname, args[0]);
-		newoff = SPI_getbinval(newtuple, tupdesc, attnum[1], &isnull);
+		newoff = SPI_getbinval(newslot, attnum[1], &isnull);
 		if (isnull)
 			elog(ERROR, "ttdummy (%s): %s must be NOT NULL", relname, args[1]);
 
@@ -569,13 +568,13 @@ ttdummy(PG_FUNCTION_ARGS)
 	cnulls = (char *) palloc(natts * sizeof(char));
 	for (i = 0; i < natts; i++)
 	{
-		cvals[i] = SPI_getbinval((newtuple != NULL) ? newtuple : trigtuple,
-								 tupdesc, i + 1, &isnull);
+		cvals[i] = SPI_getbinval((newslot != NULL) ? newslot : trigslot,
+								 i + 1, &isnull);
 		cnulls[i] = (isnull) ? 'n' : ' ';
 	}
 
 	/* change date column(s) */
-	if (newtuple)				/* UPDATE */
+	if (newslot)				/* UPDATE */
 	{
 		cvals[attnum[0] - 1] = newoff;	/* start_date eq current date */
 		cnulls[attnum[0] - 1] = ' ';
@@ -628,10 +627,10 @@ ttdummy(PG_FUNCTION_ARGS)
 		elog(ERROR, "ttdummy (%s): SPI_execp returned %d", relname, ret);
 
 	/* Tuple to return to upper Executor ... */
-	if (newtuple)				/* UPDATE */
-		rettuple = SPI_modifytuple(rel, trigtuple, 1, &(attnum[1]), &newoff, NULL);
-	else						/* DELETE */
-		rettuple = trigtuple;
+	if (newslot)				/* UPDATE */
+		trigslot = SPI_modifyslot(trigslot, 1, &(attnum[1]), &newoff, NULL);
+
+	rettuple = trigslot->tts_tuple;
 
 	SPI_finish();				/* don't forget say Bye to SPI mgr */
 
-- 
1.8.3.1

#46Robert Haas
robertmhaas@gmail.com
In reply to: Alexander Korotkov (#24)
Re: Pluggable storage

On Thu, Jun 22, 2017 at 9:30 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

If #1 is only about changing tuple and page formats, then could be much
simpler than the patch upthread? We can implement "page format access
methods" with routines for insertion, update, pruning and deletion of tuples
*in particular page*. There is no need to redefine high-level logic for
scanning heap, doing updates and so on...

That assumes that every tuple format does those things in the same
way, which I suspect is not likely to be the case. I think that
pruning and vacuum are artifacts of the current heap format, and they
may be nonexistent or take some altogether different form in some
other storage engine. InnoDB isn't much like the PostgreSQL heap, and
neither is SQL Server, IIUC. If we're creating a heap format that can
only be different in trivial ways from what we have now, and anything
that really changes the paradigm is off-limits, well, that's not
really interesting enough to justify the work of creating a heap
storage API.

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#47Robert Haas
robertmhaas@gmail.com
In reply to: Tomas Vondra (#26)
Re: Pluggable storage

On Thu, Jun 22, 2017 at 4:27 PM, Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

Can you elaborate a bit more about this TID bit pattern issues? I do
remember that not all TIDs are valid due to safeguards on individual fields,
like for example

Assert(iptr->ip_posid < (1 << MaxHeapTuplesPerPageBits))

But perhaps there are some other issues?

I think the other issues that I know about have largely already been
mentioned by others, but since this question was addressed to me:

1. TID bitmaps assume that the page and offset parts of the TID
contain just those things. As Tom pointed out, tidbitmap.c isn't cool
with TIDs that have ip_posid out of range. More broadly, we assume
lossification is a sensible way of keeping a TID bitmap down to a
reasonable size without losing too much efficiency, and that sorting
tuple references by the block ID is likely to produce a sequential I/O
pattern. Those things won't necessarily be true if TIDs are treated
as opaque tuple identifiers.

2. Apparently, GIN uses the structure of TIDs to compress posting
lists. (I'm not personally familiar with this code.)

In general, it's a fairly dangerous thing to suppose that you can
repurpose a value as widely used as a TID and not break anything. I'm
not saying it can't be done, but we use TIDs in an awful lot of places
and rooting out all of the places somebody may have made an assumption
about the structure of them may not be trivial. I tend to think it's
an ugly kludge to shove some other kind of value into a TID, anyway.
If we need to store something that's not a TID, I think we should have
a purpose-built mechanism for that, not just hammer on the existing
system until it sorta works.

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#48Robert Haas
robertmhaas@gmail.com
In reply to: Haribabu Kommi (#45)
Re: Pluggable storage

On Fri, Jul 14, 2017 at 8:35 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

To replace tuple with slot, I took trigger and SPI calls as the first step
in modifying
from tuple to slot, Here I attached a WIP patch. The notable changes are,

1. Replace most of the HeapTuple with Slot in SPI interface functions.
2. In SPITupleTable, Instead of HeapTuple, it is changed to TupleTableSlot.
But this change may not be a proper approach, because a duplicate copy of
TupleTableSlot is generated and stored.
3. Changed all trigger interfaces to accept TupleTableSlot Instead of
HeapTuple.
4. ItemPointerData is added as a member to the TupleTableSlot structure.
5. Modified the ExecInsert and others to work directly on TupleTableSlot
instead
of tuple(not completely).

What problem are you trying to solve with these changes? I'm not
saying that it's a bad idea, but I think you should spell out the
motivation a little more explicitly.

I think performance is likely to be a key concern here. Maybe these
interfaces are Just Better with TupleTableSlots and the patch is a win
regardless of pluggable storage -- but if the reverse turns out to be
true, and this slows things down in cases that anyone cares about, I
think that's going to be a problem.

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#49Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Robert Haas (#46)
Re: Pluggable storage

On Sat, Jul 15, 2017 at 5:14 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Jun 22, 2017 at 9:30 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

If #1 is only about changing tuple and page formats, then could be much
simpler than the patch upthread? We can implement "page format access
methods" with routines for insertion, update, pruning and deletion of

tuples

*in particular page*. There is no need to redefine high-level logic for
scanning heap, doing updates and so on...

That assumes that every tuple format does those things in the same
way, which I suspect is not likely to be the case. I think that
pruning and vacuum are artifacts of the current heap format, and they
may be nonexistent or take some altogether different form in some
other storage engine.

I think that pruning and vacuum are artifacts of not only current heap
formats, but they are also artifacts of current index AM API. And this is
more significant circumstance given that we're going to preserve
compatibility of new storage engines with current index AMs. Our current
index AM API assumes that we can delete from index only in bulk manner.
Our payload to index key is TID, not arbitrary piece of data. And that
payload can't be updated.

InnoDB isn't much like the PostgreSQL heap, and

neither is SQL Server, IIUC. If we're creating a heap format that can
only be different in trivial ways from what we have now, and anything
that really changes the paradigm is off-limits, well, that's not
really interesting enough to justify the work of creating a heap
storage API.

My concern is that we probably can't do anything that really changes
paradigm while preserving compatibility with index AM API. If you don't
agree with that, it would be good to provide some examples. It seems
unlikely for me that we're going to have something like InnoDB or SQL
Server table with our current index AM API. InnoDB utilizes
index-organized tables where primary and secondary indexes are versioned
independently. SQL Server utilizes flat data structure similar to our
heap, but MVCC implementation also seems very different.

I think in general there are two ways dealing with out index AM API
limitation. One of them is to extend index AM API. At least, we would
need a method for deletion of individual index tuple (for sure, we already
have kill_prior_tuple but it's just a hint for now). Also, it would be
nice to have arbitrary payload to index tuples instead of TID, and method
to update that payload. But that would be quite big amount of work.
Alternatively, we could allow pluggable storages to have their own index
AMs, and that will move this amount of work to the pluggable storage side.

The thing which we would evade is storage API, which would be invasive like
something changing paradigm, but actually allowing just trivial changes in
heap format. Mechanical replacement of heap methods with storage API
methods could lead us there.

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

In reply to: Alexander Korotkov (#49)
Re: Pluggable storage

On Sat, Jul 15, 2017 at 3:36 PM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

I think that pruning and vacuum are artifacts of not only current heap
formats, but they are also artifacts of current index AM API. And this is
more significant circumstance given that we're going to preserve
compatibility of new storage engines with current index AMs. Our current
index AM API assumes that we can delete from index only in bulk manner. Our
payload to index key is TID, not arbitrary piece of data. And that payload
can't be updated.

I agree that this is a big set of problems. This is where I think we
can get the most benefit.

One nice thing about having a fully logical TID is that you don't have
to bloat indexes just because a HOT update was not possible. You bloat
something else instead, certainly, but that can be optimized for
garbage collection. Not all bloat is equal. MVCC based on UNDO can be
very fast because UNDO is very well optimized for garbage collection,
and so can be bloated with no long term consequences, and minor short
term consequences. Still, it isn't fair to blame the Postgres VACUUM
design for the fact that Postgres bloats indexes so easily. Nothing
stops us from adding a new layer of indirection, so that bloating an
index degrades into something that is not even as bad as bloating the
heap [1]postgr.es/m/CAH2-Wzmf6intNY1ggiNzOziiO5Eq=DsXfeptODGxO=2j-i1NGQ@mail.gmail.com. We may just have a data structure problem, which is not
nearly the same thing as a fundamental design problem in the storage
layer.

This approach could pay off soon if we start with unique indexes,
where there is "queue" of row versions that can be pruned with simple
logic, temporal locality helps a lot, only zero or one versions can be
visible to your MVCC snapshot, etc. This might require only minimal
revisions the index AM API, to help nbtree. We could later improve
this so that you bloat UNDO instead of bloating a heap-like structure,
both for indexes and for the actual heap. That seems less urgent.

To repeat myself, for emphasis: *Not all bloat is equal*. Index bloat
makes the way a B-Tree's keyspace is split up far too granular, making
pages sparely packed, a problem that is more or less *irreversible* by
VACUUM or any garbage collection process [2]https://wiki.postgresql.org/wiki/Key_normalization#VACUUM_and_nbtree_page_deletion -- Peter Geoghegan. That's how B-Trees work
-- they're optimized for concurrency, not for garbage collection.

InnoDB isn't much like the PostgreSQL heap, and
neither is SQL Server, IIUC. If we're creating a heap format that can
only be different in trivial ways from what we have now, and anything
that really changes the paradigm is off-limits, well, that's not
really interesting enough to justify the work of creating a heap
storage API.

My concern is that we probably can't do anything that really changes
paradigm while preserving compatibility with index AM API. If you don't
agree with that, it would be good to provide some examples. It seems
unlikely for me that we're going to have something like InnoDB or SQL Server
table with our current index AM API. InnoDB utilizes index-organized tables
where primary and secondary indexes are versioned independently. SQL Server
utilizes flat data structure similar to our heap, but MVCC implementation
also seems very different.

I strongly agree. I simply don't understand how you can adopt UNDO for
MVCC, and yet expect to get a benefit commensurate with the effort
without also implementing "retail index tuple deletion" first.
Pursuing UNDO this way has the same problem that WARM likely has -- it
doesn't really help with the worst case, where users get big,
unpleasant surprises. Postgres is probably the only major database
system that doesn't support retail index tuple deletion. It's a basic
thing, that has nothing to do with MVCC. Really, what do we have to
lose?

The biggest weakness of the current design is IMV how it fails to
prevent index bloat in the first place, but avoiding bloating index
leaf pages in the first place doesn't seem incompatible with how
VACUUM works. Or at least, let's not assume that it is. We should
avoid throwing the baby out with the bathwater.

I think in general there are two ways dealing with out index AM API
limitation. One of them is to extend index AM API. At least, we would need
a method for deletion of individual index tuple (for sure, we already have
kill_prior_tuple but it's just a hint for now).

kill_prior_tuple can work well, but, like HOT, it works
inconsistently, in a way that is hard to predict.

Also, it would be nice to
have arbitrary payload to index tuples instead of TID, and method to update
that payload. But that would be quite big amount of work. Alternatively,
we could allow pluggable storages to have their own index AMs, and that will
move this amount of work to the pluggable storage side.

I agree with Robert that being able to store an arbitrary payload as a
TID is probably not going to ever work very well. However, I don't
think that that's a reason to give up on the underlying idea: creating
a new layer of indirection for secondary indexes, that allows updates
that now have to create new index tuples to instead just update the
indirection layer metadata.

You can create a mapping of a TID-like 6 byte integer to a primary key
value. Techniques exist. That seems a lot more practical. Of course,
TID is what is sometimes called a "physiological" identifier -- it has
a "physical" component (block number) and "logical" component (item
offset). Nothing I can think of prevents us from creating an
alternative, entirely logical identifier that fits in the same 6
bytes. It can map to a versioning indirection layer, for unique
indexes, or to a primary key value, for secondary indirect indexes.

[1]: postgr.es/m/CAH2-Wzmf6intNY1ggiNzOziiO5Eq=DsXfeptODGxO=2j-i1NGQ@mail.gmail.com
[2]: https://wiki.postgresql.org/wiki/Key_normalization#VACUUM_and_nbtree_page_deletion -- Peter Geoghegan
--
Peter Geoghegan

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#51Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Peter Geoghegan (#50)
Re: Pluggable storage

On Sun, Jul 16, 2017 at 3:58 AM, Peter Geoghegan <pg@bowt.ie> wrote:

I strongly agree. I simply don't understand how you can adopt UNDO for
MVCC, and yet expect to get a benefit commensurate with the effort
without also implementing "retail index tuple deletion" first.
Pursuing UNDO this way has the same problem that WARM likely has -- it
doesn't really help with the worst case, where users get big,
unpleasant surprises. Postgres is probably the only major database
system that doesn't support retail index tuple deletion. It's a basic
thing, that has nothing to do with MVCC. Really, what do we have to
lose?

I think that "retail index tuple deletion" is the feature which could give
us some advantages even independently from pluggable storages. For
example, imagine very large table with only small amount of dead tuples.
In this case, it would be cheaper to delete index links to those dead
tuples one by one using "retail index tuple deletion", rather than do full
scan of every index to perform "bulk delete" of index tuples. One may
argue that you shouldn't do vacuum of large table when only small amount of
tuples are dead. But in terms of index bloat mitigation, very aggressive
vacuum strategy could be justified.

I agree with Robert that being able to store an arbitrary payload as a

TID is probably not going to ever work very well.

Support of arbitrary payload as a TID doesn't sound easy. However, that
doesn't mean it's unachievable. For me, it's more like long way which could
be traveled step by step. Some of our existing index access methods
(B-tree, hash, GiST, SP-GiST) may support arbitrary payload relatively
easy, because they are not relying on its internal structure. For others
(GIN, BRIN) arbitrary payload is much harder to support, but I wouldn't say
it's impossible. However, if we make arbitrary payload support an option
of index AM and implement this support for first group of index AMs, it
would be already great step forward. So, for sample, it would be possible
to use indirect indexes when primary key is not 6-bytes, if index AM
supports arbitrary payload.

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

In reply to: Alexander Korotkov (#51)
Re: Pluggable storage

On Mon, Jul 17, 2017 at 3:22 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

I think that "retail index tuple deletion" is the feature which could give
us some advantages even independently from pluggable storages. For example,
imagine very large table with only small amount of dead tuples. In this
case, it would be cheaper to delete index links to those dead tuples one by
one using "retail index tuple deletion", rather than do full scan of every
index to perform "bulk delete" of index tuples. One may argue that you
shouldn't do vacuum of large table when only small amount of tuples are
dead. But in terms of index bloat mitigation, very aggressive vacuum
strategy could be justified.

Yes, definitely. Especially with the visibility map. Even still, I
tend to think that for unique indexes, true duplicates should be
disallowed, and dealt with with an additional layer of indirection. So
this would be for secondary indexes.

I agree with Robert that being able to store an arbitrary payload as a
TID is probably not going to ever work very well.

Support of arbitrary payload as a TID doesn't sound easy. However, that
doesn't mean it's unachievable. For me, it's more like long way which could
be traveled step by step.

To be fair, it probably is achievable. Where there is a will, there is
a way. I just think that it will be easier to find a different way of
realizing similar benefits. I'm mostly talking about benefits around
making it cheap to have many secondary indexes by having logical
indirection instead of physical pointers (doesn't *have* to be
user-visible primary key values). HOT simply isn't effective enough at
preventing UPDATE index tuple insertions for indexes on unchanged
attributes, often just because pruning can fail to happen in time,
which WARM will not fix.

--
Peter Geoghegan

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#53Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Peter Geoghegan (#52)
Re: Pluggable storage

On Mon, Jul 17, 2017 at 7:51 PM, Peter Geoghegan <pg@bowt.ie> wrote:

On Mon, Jul 17, 2017 at 3:22 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

I think that "retail index tuple deletion" is the feature which could

give

us some advantages even independently from pluggable storages. For

example,

imagine very large table with only small amount of dead tuples. In this
case, it would be cheaper to delete index links to those dead tuples one

by

one using "retail index tuple deletion", rather than do full scan of

every

index to perform "bulk delete" of index tuples. One may argue that you
shouldn't do vacuum of large table when only small amount of tuples are
dead. But in terms of index bloat mitigation, very aggressive vacuum
strategy could be justified.

Yes, definitely. Especially with the visibility map. Even still, I
tend to think that for unique indexes, true duplicates should be
disallowed, and dealt with with an additional layer of indirection. So
this would be for secondary indexes.

It's probably depends on particular storage (once we have pluggable
storages). Some storages would have additional level of indirection while
others wouldn't. But even if unique index contain no true duplicates, it's
still possible that true delete happen. Then we still have to delete tuple
even from unique index.

I agree with Robert that being able to store an arbitrary payload as a

TID is probably not going to ever work very well.

Support of arbitrary payload as a TID doesn't sound easy. However, that
doesn't mean it's unachievable. For me, it's more like long way which

could

be traveled step by step.

To be fair, it probably is achievable. Where there is a will, there is
a way. I just think that it will be easier to find a different way of
realizing similar benefits. I'm mostly talking about benefits around
making it cheap to have many secondary indexes by having logical
indirection instead of physical pointers (doesn't *have* to be
user-visible primary key values).

It's possible to add indirection layer "on demand". Thus, initially index
tuples point directly to the heap tuple. If tuple gets updates and doesn't
fit to the page anymore, then it's moved to another place with redirect in
the old place. I think that if carefully designed, it's possible to
guarantee there is at most one redirect.

But I sill think that evading arbitrary payload for indexes is delaying of
inevitable, if only we want pluggable storages and want them to reuse
existing index AMs. So, for example, arbitrary payload together with
ability to update this payload allows us to make indexes separately
versioned (have separate garbage collection process more or less unrelated
to heap). Despite overhead caused by MVCC attributes, I think such indexes
could give significant advantages in various workloads.

HOT simply isn't effective enough at
preventing UPDATE index tuple insertions for indexes on unchanged
attributes, often just because pruning can fail to happen in time,
which WARM will not fix.

Right. I think HOT and WARM depend on factors which are hard to control:
distribution of UPDATEs between heap pages, oldest snapshot and so on.
It's quite hard for DBA to understand why table starts getting bloat while
it didn't before.

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

In reply to: Alexander Korotkov (#53)
Re: Pluggable storage

On Mon, Jul 17, 2017 at 1:24 PM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

It's probably depends on particular storage (once we have pluggable
storages). Some storages would have additional level of indirection while
others wouldn't.

Agreed. Like kill_prior_tuple, it's an optional capability, and where
implemented is implemented in a fairly consistent way.

But even if unique index contain no true duplicates, it's
still possible that true delete happen. Then we still have to delete tuple
even from unique index.

I think I agree. I've been looking over the ARIES paper [1]https://pdfs.semanticscholar.org/39e3/d058a5987cb643e000bce555676d71be1c80.pdf again
today. They say this:

"For index updates, in the interest of increasing concurrency, we do
not want to prevent the space released by one transaction from being
consumed by another before the commit of the first transaction."

You can literally reclaim space from an index tuple deletion
*immediately* with their design, which matters because you want to
reclaim space as early as possible, before a page spit is needed.
Obviously they understand how important this is.

This might not work so well with an MVCC system, where there are no
2PL predicate locks. You need to keep a "ghost record", even for
non-unique indexes, and the deletion can only happen when the xact
commits. Remember, the block number cannot be used to see if there was
changes against the page, unlike the heap, because you have to worry
about page splits and page merges/deletion. UNDO is entirely logical
for indexes for this reason. (This is why UNDO does not actually undo
page splits, relation extension, etc. Only REDO/WAL always works at
the level of individual pages in all cases. UNDO for MVCC is not as
different to our design as I once thought.).

The reason I want to at least start with unique indexes is because you
need a TID to make non-unique/secondary indexes have unique keys
(unique keys are always needed if retail index tuple insertion is
always supported). For unique indexes, you really can do an update in
the index (see my design below for one example of how that can work),
but I think you need something more like a deletion followed by an
insertion for non-unique indexes, because there the physical/heap TID
changed, and that's part of the key, and that might belong on a
different page. You therefore haven't really fixed the problem with
secondary indexes sometimes needing new index tuples even though user
visible attributes weren't updated.

You haven't fixed the problem with secondary index, unless, of course,
all secondary indexes have logical pointers to begin with, such as the
PK value. Then you only need to "insert and delete, not update" when
the PK value is updated or when a secondary index needs a new index
tuple with distinct user visible attribute values to the previous
version's -- you fix the secondary index problem. And, while your
"version chain overflow indirection" structure is basically something
that lives outside the heap, it is still only needed for one index,
and not all of them.

This new indirection structure is a really nice target for pruning,
because you can prune physical TIDs that no possible snapshot could
use, unlike with the heap, where EvalPlanQual() could make any heap
tuple visible to snapshots at or after the minimal snapshot horizon
implied by RecentGlobalXmin. And, because index scans on any index can
prune for everyone.

You could also do "true index deletes", as you suggest, but you'd need
to have ghost records there too, and you'd need an asynchronous
cleanup process to do the cleanup when the deleting xact committed.
I'm not sure if it's worth doing that eagerly. It may or may not be
better to hope for kill_prior_tuple to do the job for us. Not sure
where this leaves index-only scans on secondary indexes..."true index
deletes" might be justified by making index only scans work more often
in general, especially for secondary indexes with logical pointers.

I'm starting to think that you were right all along about indirect
indexes needing to store PK values. Perhaps we should just bite the
bullet...it's not like places like the bufpage.c index routines
actually know or care about whether or not the index tuple has a TID,
what a TID is, etc. They care about stuff like the header values of
index tuples, and the page ItemId array, but TID is, as you put it,
merely payload.

It's possible to add indirection layer "on demand". Thus, initially index
tuples point directly to the heap tuple. If tuple gets updates and doesn't
fit to the page anymore, then it's moved to another place with redirect in
the old place. I think that if carefully designed, it's possible to
guarantee there is at most one redirect.

This is actually what I was thinking. Here is a sketch:

When you start out, index tuples in nbtree are the same as today --
one physical pointer (TID). But, on the first update to a PK index,
they grow a new pointer, but this is not a physical/heap TID. It's a
pointer to some kind of indirection structure that manages version
chains. You end up with an index with almost exactly the same external
interface as today, with one difference: you tell nbtree if something
is an insert or update, at least for unique indexes. Of course, you
need to have something to update in the index if it's an update, and
nbtree needs to be informed what that is.

My first guess is that we should limit the number of TIDs to two in
all cases, and start with only one physical TID, because:

* The first TID can always be the latest version, which in practice is
all most snapshots care about.

* We want to sharply limit the worst case page bloat, because
otherwise you have the same basic problem. Some queries might be a bit
slower, but it's worth it to be confident that bloat can only get so
bad.

* Simpler "1/3 of a page" enforcement. We simply add
"sizeof(ItemPointerData)" to the calculation.

* Gray says that split points are sometimes better if they're the
average of the min and max keys on the page, rather than the point at
which each half gets the most even share of space. Big index tuples
are basically bad for this.

But I sill think that evading arbitrary payload for indexes is delaying of
inevitable, if only we want pluggable storages and want them to reuse
existing index AMs. So, for example, arbitrary payload together with
ability to update this payload allows us to make indexes separately
versioned (have separate garbage collection process more or less unrelated
to heap). Despite overhead caused by MVCC attributes, I think such indexes
could give significant advantages in various workloads.

Yeah. Technically you could have some indirection to keep under 6
bytes when that isn't assured by the PK index tuple width, but it
probably wouldn't be worth it. TID is almost like just another
attribute. The more I look, the less I think that TID is this thing
that a bunch of code makes many assumptions about that we will never
find all of. *Plenty* of TIDs today do not point to the heap at all.
For example, internal pages in nbtree uses TIDs that point to the
level below.

You would break some code within indextuple.c, but that doesn't seem
so bad. IndexInfoFindDataOffset() already has to deal with
variable-width NULL bitmaps. Why not a variabke-length pointer, too?

[1]: https://pdfs.semanticscholar.org/39e3/d058a5987cb643e000bce555676d71be1c80.pdf

--
Peter Geoghegan

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#55Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Robert Haas (#48)
Re: Pluggable storage

On Sat, Jul 15, 2017 at 12:30 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Jul 14, 2017 at 8:35 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

To replace tuple with slot, I took trigger and SPI calls as the first

step

in modifying
from tuple to slot, Here I attached a WIP patch. The notable changes are,

1. Replace most of the HeapTuple with Slot in SPI interface functions.
2. In SPITupleTable, Instead of HeapTuple, it is changed to

TupleTableSlot.

But this change may not be a proper approach, because a duplicate copy of
TupleTableSlot is generated and stored.
3. Changed all trigger interfaces to accept TupleTableSlot Instead of
HeapTuple.
4. ItemPointerData is added as a member to the TupleTableSlot structure.
5. Modified the ExecInsert and others to work directly on TupleTableSlot
instead
of tuple(not completely).

What problem are you trying to solve with these changes? I'm not
saying that it's a bad idea, but I think you should spell out the
motivation a little more explicitly.

Sorry for not providing complete details. I am trying these experiments
to find out the best way to return the tuple from Storage methods by
designing a proper API.

The changes that I am doing are to reduce the dependency on the
HeapTuple format with value/nulls array. So if there is no dependency
on the HeapTuple format in the upper layers of the PostgreSQL storage,
then directly we can define the StorageAPI to return the value/nulls array
instead of one big chunck of tuple data like HeapTuple or StorageTuple(void
*).

I am finding out that eliminating the HeapTuple usage in the upper layers
needs some major changes, How about not changing anything in the upper
layers of storage currently and just support the pluggable tuple with one of
the following approach for first version?

1. Design an API that returns values/nulls array and convert that into a
HeapTuple whenever it is required in the upper layers. All the existing
heap form/deform tuples are used for every tuple with some adjustments.

2. Design an API that returns StorageTuple(void *) with first member
represents the TYPE of the storage, so that corresponding registered
function calls can be called to deform/form the tuple whenever there is
a need of tuple.

3. Design an API that returns StorageTuple(void *) but the necessary
format information of that tuple can be get from the tupledesc. wherever
the tuple is present, there exists a tupledesc in most of the cases. How
about adding some kind of information in tupledesc to find out the tuple
format and call the necessary functions

4. Design an API to return always the StorageTuple and it converts to
HeapTuple with a function hook if it gets registered (for heap storages
this is not required to register the hook, because it is already a HeapTuple
format). This function hook should be placed in the heap form/deform
functions.

Any other better ideas for a first version.

Regards,
Hari Babu
Fujitsu Australia

#56Robert Haas
robertmhaas@gmail.com
In reply to: Alexander Korotkov (#49)
Re: Pluggable storage

On Sat, Jul 15, 2017 at 6:36 PM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

I think in general there are two ways dealing with out index AM API
limitation. One of them is to extend index AM API.

That's pretty much what I have in mind. I think it's likely that if
we end up with, say, 3 kinds of heap and 12 kinds of index, there will
be some compatibility matrix. Some index AMs will be compatible with
some heap AMs, and others won't be. For example, if somebody makes an
IOT-type heap using the API proposed here or some other one, BRIN
probably won't work at all. btree, on the other hand, could probably
be made to work, perhaps with some greater or lesser degree of
modification.

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#57Robert Haas
robertmhaas@gmail.com
In reply to: Peter Geoghegan (#50)
Re: Pluggable storage

On Sat, Jul 15, 2017 at 8:58 PM, Peter Geoghegan <pg@bowt.ie> wrote:

To repeat myself, for emphasis: *Not all bloat is equal*.

+1.

I strongly agree. I simply don't understand how you can adopt UNDO for
MVCC, and yet expect to get a benefit commensurate with the effort
without also implementing "retail index tuple deletion" first.

I agree that we need retail index tuple deletion. I liked Claudio's
idea at /messages/by-id/CAGTBQpZ-kTRQiAa13xG1GNe461YOwrA-s-ycCQPtyFrpKTaDBQ@mail.gmail.com
-- that seems indispensible to making retail index tuple deletion
reasonably efficient. Is anybody going to work on getting that
committed?

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

In reply to: Robert Haas (#57)
Re: Pluggable storage

On Wed, Jul 19, 2017 at 10:56 AM, Robert Haas <robertmhaas@gmail.com> wrote:

I strongly agree. I simply don't understand how you can adopt UNDO for
MVCC, and yet expect to get a benefit commensurate with the effort
without also implementing "retail index tuple deletion" first.

I agree that we need retail index tuple deletion. I liked Claudio's
idea at /messages/by-id/CAGTBQpZ-kTRQiAa13xG1GNe461YOwrA-s-ycCQPtyFrpKTaDBQ@mail.gmail.com
-- that seems indispensible to making retail index tuple deletion
reasonably efficient. Is anybody going to work on getting that
committed?

I will do review work on it.

IMV the main problems are:

* The way a "header" is added at the PageAddItemExtended() level,
rather than making heap TID something much closer to a conventional
attribute that perhaps only nbtree and indextuple.c have special
knowledge of, strikes me as the wrong way to go.

* It's simply not acceptable to add overhead to *all* internal items.
That kills fan-in. We're going to need suffix truncation for the
common case where the user-visible attributes for a split point/new
high key at the leaf level sufficiently distinguish what belongs on
either side. IOW, you should only see internal items with a heap TID
in the uncommon case where you have so many duplicates at the leaf
level that you have no choice put to use a split point that's right in
the middle of many duplicates.

Fortunately, if we confine ourselves to making heap TID part of the
keyspace, the code can be far simpler than what would be needed to get
my preferred, all-encompassing design for suffix truncation [1]https://wiki.postgresql.org/wiki/Key_normalization#Suffix_truncation_of_normalized_keys -- Peter Geoghegan to
work. I think we could just stash the number of attributes
participating in a comparison within internal pages' unused item
pointer offset. I've talked about this before, in the context of
Anastasia's INCLUDED columns patch. If we can have a variable number
of attributes for heap tuples, we can do so for index tuples, too.

* We might also have problems with changing the performance
characteristics for the worse in some cases by some measures. This
will probably technically increase the amount of bloat for some
indexes with sparse deletion patterns. I think that that will be well
worth it, but I don't expect a slam dunk.

A nice benefit of this work is that it lets us kill the hack that adds
randomness to the search for free space among duplicates, and may let
us follow the Lehman & Yao algorithm more closely.

[1]: https://wiki.postgresql.org/wiki/Key_normalization#Suffix_truncation_of_normalized_keys -- Peter Geoghegan
--
Peter Geoghegan

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#59Amit Kapila
amit.kapila16@gmail.com
In reply to: Haribabu Kommi (#55)
Re: Pluggable storage

On Wed, Jul 19, 2017 at 11:33 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

On Sat, Jul 15, 2017 at 12:30 PM, Robert Haas <robertmhaas@gmail.com> wrote:

I am finding out that eliminating the HeapTuple usage in the upper layers
needs some major changes, How about not changing anything in the upper
layers of storage currently and just support the pluggable tuple with one of
the following approach for first version?

It is not very clear to me how any of the below alternatives are
better or worse as compare to the approach you are already working.
Do you mean to say that we will get away without changing all the
places which take HeapTuple as input or return it as output with some
of the below approaches?

1. Design an API that returns values/nulls array and convert that into a
HeapTuple whenever it is required in the upper layers. All the existing
heap form/deform tuples are used for every tuple with some adjustments.

So, this would have the additional cost of form/deform. Also, how
would it have lesser changes as compare to what you have described
earlier?

2. Design an API that returns StorageTuple(void *) with first member
represents the TYPE of the storage, so that corresponding registered
function calls can be called to deform/form the tuple whenever there is
a need of tuple.

Do you intend to say that we store such information in disk tuple or
only in the in-memory version of same? Also, what makes you think
that we would need hooks only for form and deform? Right now, in many
cases tuple will directly point to disk page and we deal with it by
retaining the pin on the corresponding buffer, what if some kinds of
tuple don't follow that rule? For ex. to support in-place updates, we
might always need a separate copy of tuple rather than the one
pointing to disk page.

3. Design an API that returns StorageTuple(void *) but the necessary
format information of that tuple can be get from the tupledesc. wherever
the tuple is present, there exists a tupledesc in most of the cases. How
about adding some kind of information in tupledesc to find out the tuple
format and call the necessary functions

4. Design an API to return always the StorageTuple and it converts to
HeapTuple with a function hook if it gets registered (for heap storages
this is not required to register the hook, because it is already a HeapTuple
format). This function hook should be placed in the heap form/deform
functions.

I think some more information is required to comment on any of the
approaches or suggest a new one. You might want to try by quoting
some specific examples from code so that it is easy to understand what
your proposal will change in that case. One idea could be that we
start with some interfaces like structures TupleTableSlot, EState,
HeapScanDescData, IndexScanDescData, etc. and interfaces like
heap_insert, heap_update, heap_lock_tuple,
SnapshotSatisfiesFunc, EvalPlanQualFetch, etc. Now, it is quite
possible that we don't want to change some of these interfaces, but it
can help to see how such a usage can be replaced with new kind of
Tuple structure.

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#60Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Amit Kapila (#59)
Re: Pluggable storage

On Sun, Jul 23, 2017 at 4:10 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:

On Wed, Jul 19, 2017 at 11:33 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

I am finding out that eliminating the HeapTuple usage in the upper layers
needs some major changes, How about not changing anything in the upper
layers of storage currently and just support the pluggable tuple with

one of

the following approach for first version?

It is not very clear to me how any of the below alternatives are
better or worse as compare to the approach you are already working.
Do you mean to say that we will get away without changing all the
places which take HeapTuple as input or return it as output with some
of the below approaches?

Yes, With the following approaches, my intention is to reduce the changing
of HeapTuple everywhere to support the Pluggable Storage API with minimal
changes and later improvise further. But until unless we change the
HeapTuple
everywhere properly, may be we can't see the benefits of pluggable storages.

1. Design an API that returns values/nulls array and convert that into a
HeapTuple whenever it is required in the upper layers. All the existing
heap form/deform tuples are used for every tuple with some adjustments.

So, this would have the additional cost of form/deform. Also, how
would it have lesser changes as compare to what you have described
earlier?

Yes, It have the additional cost of form/deform. It is the same approach
that
is described earlier. But I have an intention of modifying everywhere the
HeapTuple is accessed. But with the other prototype changes of removing
HeapTuple usage from triggers, I realized that it needs some clear design
to proceed further, instead of combining those changes with pluggable
Storage API.

- heap_getnext function is kept as it as and it is used only for system
table
access.
- heap_getnext_slot function is introduced to return the slot whenever the
data is found, otherwise NULL, This function is used in all the places
from
Executor and etc.

- The TupleTableSlot structure is modified to contain a void* tuple instead
of
HeapTuple. And also it contains the storagehanlder functions.
- heap_insert and etc function can take Slot as an argument and perform the
insert operation.

The cases where the TupleTableSlot is not possible to sent, form a HeapTuple
from the data and sent it and also note down that it is a HeapTuple data,
not
the tuple from the storage.

2. Design an API that returns StorageTuple(void *) with first member

represents the TYPE of the storage, so that corresponding registered
function calls can be called to deform/form the tuple whenever there is
a need of tuple.

Do you intend to say that we store such information in disk tuple or
only in the in-memory version of same?

Only in-memory version.

Also, what makes you think
that we would need hooks only for form and deform? Right now, in many
cases tuple will directly point to disk page and we deal with it by
retaining the pin on the corresponding buffer, what if some kinds of
tuple don't follow that rule? For ex. to support in-place updates, we
might always need a separate copy of tuple rather than the one
pointing to disk page.

In any of the approaches, except for system tables, we are going to remove
the direct disk access of the tuple. Either with replace tuple with slot or
something,
no direct disk access will be removed. Otherwise it will be difficult to
support,
and also it doesn't provide much performance benefit also.

All the storagehandler functions needs to be maintained seperately
and accessed by them using the hanlder ID.

heap_getnext function returns StorageTuple and not HeapTuple.
The StorageTuple can be mapped to HeapTuple or another Tuple
based on the first member type.

heap_insert etc function takes input as StorageTuple and internally
decides based on the type of the tuple to perform the insert operation.

Instead of storing the handler functions inside a relation/slot, the
function pointers can be accessed directly based on the storage
type data.

This works every case where the tuple is accessed, but the problem
is, it may need changes wherever the tuple is accessed.

3. Design an API that returns StorageTuple(void *) but the necessary
format information of that tuple can be get from the tupledesc. wherever
the tuple is present, there exists a tupledesc in most of the cases. How
about adding some kind of information in tupledesc to find out the tuple
format and call the necessary functions

heap_getnext function returns StorageTuple instead of HeapTuple. The tuple
type information is available in the TupleDesc structure.

All heap_insert and etc function accepts TupleTableSlot as input and perform
the insert operation. This approach is almost same as first approach except
the
storage handler functions are stored in TupleDesc.

In case if the tuple is formed internally based on the value/nulls array,
the formed
tuple is always the HeapTuple format and the same is updated in TupleDesc.

I am having doubt that passing the StorageTuple everywhere in case that
location
doesn't contains a TupleDesc and direct access of the tuple may give
problems.

4. Design an API to return always the StorageTuple and it converts to
HeapTuple with a function hook if it gets registered (for heap storages
this is not required to register the hook, because it is already a

HeapTuple

format). This function hook should be placed in the heap form/deform
functions.

heap_getnext function always returns HeapTuple irrespective of the storage.
That means all other storage modules have to frame the HeapTuple from the
data and send it back to the server.

heap_insert and etc functions can work with same interfaces and convert
the HeapTuple internally and perform the insert operation according to their
storage requirement.

There are not many changes in the structures, just adding the storage
handler
functions.

This approach is simple, but it doesn't provide much benefit in having a
different
storage in my opinion.

I am preferring to go with the Original (first) approach and generate the
HeapTuple
wherever it is necessary in upper layers, but the API will return a
TupleTableSlot
with either void pointer to tuple or value/nulls array.

Please provide your views.

Regards,
Hari Babu
Fujitsu Australia

#61Amit Kapila
amit.kapila16@gmail.com
In reply to: Haribabu Kommi (#60)
Re: Pluggable storage

On Tue, Aug 1, 2017 at 1:56 PM, Haribabu Kommi <kommi.haribabu@gmail.com> wrote:

On Sun, Jul 23, 2017 at 4:10 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:

1. Design an API that returns values/nulls array and convert that into a
HeapTuple whenever it is required in the upper layers. All the existing
heap form/deform tuples are used for every tuple with some adjustments.

So, this would have the additional cost of form/deform. Also, how
would it have lesser changes as compare to what you have described
earlier?

Yes, It have the additional cost of form/deform. It is the same approach
that
is described earlier. But I have an intention of modifying everywhere the
HeapTuple is accessed. But with the other prototype changes of removing
HeapTuple usage from triggers, I realized that it needs some clear design
to proceed further, instead of combining those changes with pluggable
Storage API.

- heap_getnext function is kept as it as and it is used only for system
table
access.
- heap_getnext_slot function is introduced to return the slot whenever the
data is found, otherwise NULL, This function is used in all the places
from
Executor and etc.

- The TupleTableSlot structure is modified to contain a void* tuple instead
of
HeapTuple. And also it contains the storagehanlder functions.
- heap_insert and etc function can take Slot as an argument and perform the
insert operation.

The cases where the TupleTableSlot is not possible to sent, form a HeapTuple
from the data and sent it and also note down that it is a HeapTuple data,
not
the tuple from the storage.

..

3. Design an API that returns StorageTuple(void *) but the necessary
format information of that tuple can be get from the tupledesc. wherever
the tuple is present, there exists a tupledesc in most of the cases. How
about adding some kind of information in tupledesc to find out the tuple
format and call the necessary functions

heap_getnext function returns StorageTuple instead of HeapTuple. The tuple
type information is available in the TupleDesc structure.

All heap_insert and etc function accepts TupleTableSlot as input and perform
the insert operation. This approach is almost same as first approach except
the
storage handler functions are stored in TupleDesc.

Why do we need to store handler function in TupleDesc? As of now, the
above patch series has it available in RelationData and
TupleTableSlot, I am not sure if instead of that keeping it in
TupleDesc is a good idea. Which all kind of places require TupleDesc
to contain handler? If those are few places, can we think of passing
it as a parameter?

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#62Amit Kapila
amit.kapila16@gmail.com
In reply to: Haribabu Kommi (#16)
Re: Pluggable storage

On Tue, Jun 13, 2017 at 7:20 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

On Fri, Oct 14, 2016 at 7:26 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

I have sent the partial patch I have to Hari Babu Kommi. We expect that
he will be able to further this goal some more.

Thanks Alvaro for sharing your development patch.

Most of the patch design is same as described by Alvaro in the first mail
[1].
I will detail the modifications, pending items and open items (needs
discussion)
to implement proper pluggable storage.

Here I attached WIP patches to support pluggable storage. The patch series
are may not work individually. Still so many things are under development.
These patches are just to share the approach of the current development.

+typedef struct StorageAmRoutine
+{

In this structure, you have already covered most of the API's that a
new storage module needs to provide, but I think there could be more.
One such API could be heap_hot_search. This seems specific to current
heap where we have the provision of HOT. I think we can provide a new
API tuple_search or something like that.

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#63Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Amit Kapila (#61)
Re: Pluggable storage

On Mon, Aug 7, 2017 at 11:12 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:

On Tue, Aug 1, 2017 at 1:56 PM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:

On Sun, Jul 23, 2017 at 4:10 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:

1. Design an API that returns values/nulls array and convert that

into a

HeapTuple whenever it is required in the upper layers. All the

existing

heap form/deform tuples are used for every tuple with some

adjustments.

So, this would have the additional cost of form/deform. Also, how
would it have lesser changes as compare to what you have described
earlier?

Yes, It have the additional cost of form/deform. It is the same approach
that
is described earlier. But I have an intention of modifying everywhere the
HeapTuple is accessed. But with the other prototype changes of removing
HeapTuple usage from triggers, I realized that it needs some clear design
to proceed further, instead of combining those changes with pluggable
Storage API.

- heap_getnext function is kept as it as and it is used only for system
table
access.
- heap_getnext_slot function is introduced to return the slot whenever

the

data is found, otherwise NULL, This function is used in all the places
from
Executor and etc.

- The TupleTableSlot structure is modified to contain a void* tuple

instead

of
HeapTuple. And also it contains the storagehanlder functions.
- heap_insert and etc function can take Slot as an argument and perform

the

insert operation.

The cases where the TupleTableSlot is not possible to sent, form a

HeapTuple

from the data and sent it and also note down that it is a HeapTuple data,
not
the tuple from the storage.

..

3. Design an API that returns StorageTuple(void *) but the necessary
format information of that tuple can be get from the tupledesc.

wherever

the tuple is present, there exists a tupledesc in most of the cases.

How

about adding some kind of information in tupledesc to find out the

tuple

format and call the necessary functions

heap_getnext function returns StorageTuple instead of HeapTuple. The

tuple

type information is available in the TupleDesc structure.

All heap_insert and etc function accepts TupleTableSlot as input and

perform

the insert operation. This approach is almost same as first approach

except

the
storage handler functions are stored in TupleDesc.

Why do we need to store handler function in TupleDesc? As of now, the
above patch series has it available in RelationData and
TupleTableSlot, I am not sure if instead of that keeping it in
TupleDesc is a good idea. Which all kind of places require TupleDesc
to contain handler? If those are few places, can we think of passing
it as a parameter?

Till now I am to able to proceed without adding any storage handler
functions to
TupleDesc structure. Sure, I will try the way of passing as a parameter
when
there is a need of it.

During the progress of the patch, I am facing problems in designing the
storage API
regarding the Buffer. For example To replace all the HeapTupleSatisfiesMVCC
and
related functions with function pointers, In HeapTuple format, the tuple
may belongs
to one buffer, so the buffer is passed to the HeapTupleSatifisifes***
functions along
with buffer, But in case of other storage formats, the single buffer may
not contains
the actual data. This buffer is used to set the Hint bits and mark the
buffer as dirty.
In case if the buffer is not available, the performance may affect for the
following
queries if the hint bits are not set.

And also the Buffer is used to get from heap_fetch, heap_lock_tuple and
related
functions to check the Tuple visibility, but currently returning a buffer
from the above
heap_** function is not possible for other formats. And also for the
HeapTuple data,
the tuple data is copied into palloced buffer instead of pointing directly
to the page.
So, returning a Buffer is a valid or not here?

Currently I am proceeding to remove the Buffer as parameter in the API and
proceed
further, In case if it affects the performance, we need to find out a
different appraoch
in handling the hint bits.

comments?

Regards,
Hari Babu
Fujitsu Australia

#64Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Amit Kapila (#62)
Re: Pluggable storage

On Tue, Aug 8, 2017 at 2:21 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:

On Tue, Jun 13, 2017 at 7:20 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

On Fri, Oct 14, 2016 at 7:26 AM, Alvaro Herrera <

alvherre@2ndquadrant.com>

wrote:

I have sent the partial patch I have to Hari Babu Kommi. We expect that
he will be able to further this goal some more.

Thanks Alvaro for sharing your development patch.

Most of the patch design is same as described by Alvaro in the first mail
[1].
I will detail the modifications, pending items and open items (needs
discussion)
to implement proper pluggable storage.

Here I attached WIP patches to support pluggable storage. The patch

series

are may not work individually. Still so many things are under

development.

These patches are just to share the approach of the current development.

+typedef struct StorageAmRoutine
+{

In this structure, you have already covered most of the API's that a
new storage module needs to provide, but I think there could be more.
One such API could be heap_hot_search. This seems specific to current
heap where we have the provision of HOT. I think we can provide a new
API tuple_search or something like that.

Thanks for the review.

Yes, the storageAmRoutine needs more function pointers. Currently I am
adding all the functions that are present in the heapam.h and some slot
related function from tuptable.h. Once I stabilize the code and API's that
are
currently added, then I will further enhance it with remaining functions
that
are necessary to support pluggable storage API.

Regards,
Hari Babu
Fujitsu Australia

#65Amit Kapila
amit.kapila16@gmail.com
In reply to: Haribabu Kommi (#63)
Re: Pluggable storage

On Sat, Aug 12, 2017 at 10:31 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

Why do we need to store handler function in TupleDesc? As of now, the
above patch series has it available in RelationData and
TupleTableSlot, I am not sure if instead of that keeping it in
TupleDesc is a good idea. Which all kind of places require TupleDesc
to contain handler? If those are few places, can we think of passing
it as a parameter?

Till now I am to able to proceed without adding any storage handler
functions to
TupleDesc structure. Sure, I will try the way of passing as a parameter when
there is a need of it.

Okay, I think it is better if you discuss such locations before
directly modifying those.

During the progress of the patch, I am facing problems in designing the
storage API
regarding the Buffer. For example To replace all the HeapTupleSatisfiesMVCC
and
related functions with function pointers, In HeapTuple format, the tuple may
belongs
to one buffer, so the buffer is passed to the HeapTupleSatifisifes***
functions along
with buffer, But in case of other storage formats, the single buffer may not
contains
the actual data.

Also, it is quite possible that some of the storage Am's don't even
want to return bool as a parameter from HeapTupleSatisfies* API's. I
guess what we need here is to provide a way so that different storage
am's can register their function pointer for an equivalent to
satisfies function. So, we need to change
SnapshotData.SnapshotSatisfiesFunc in some way so that different
handlers can register their function instead of using that directly.
I think that should address the problem you are planning to solve by
omitting buffer parameter.

This buffer is used to set the Hint bits and mark the
buffer as dirty.
In case if the buffer is not available, the performance may affect for the
following
queries if the hint bits are not set.

I don't think it is advisable to change that for the current heap.

And also the Buffer is used to get from heap_fetch, heap_lock_tuple and
related
functions to check the Tuple visibility, but currently returning a buffer
from the above
heap_** function is not possible for other formats.

Why not? I mean if we consider that all the formats we are worried at
this stage have TID (block number, tuple location), then we can get
the buffer. We might want to consider passing TID as a parameter to
these API's if required to make that possible. You also agreed above
[1]: /messages/by-id/CAJrrPGd8+i8sqZCdhfvBhs2d1akEb_kEuBvgRHSPJ9z2Z7VBJw@mail.gmail.com
having TID.

And also for the
HeapTuple data,
the tuple data is copied into palloced buffer instead of pointing directly
to the page.
So, returning a Buffer is a valid or not here?

Yeah, but I think for the sake of compatibility and not changing too
much in the current API's signature, we should try to avoid it.

Currently I am proceeding to remove the Buffer as parameter in the API and
proceed
further, In case if it affects the performance, we need to find out a
different appraoch
in handling the hint bits.

Leaving aside the performance concern, I am not convinced that it is a
good idea to remove Buffer as a parameter from the API's you have
mentioned above. Would you mind thinking once again keeping the
suggestions provided above in this email to see if we can avoid
removing Buffer as a parameter?

[1]: /messages/by-id/CAJrrPGd8+i8sqZCdhfvBhs2d1akEb_kEuBvgRHSPJ9z2Z7VBJw@mail.gmail.com

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#66Amit Kapila
amit.kapila16@gmail.com
In reply to: Haribabu Kommi (#64)
Re: Pluggable storage

On Sat, Aug 12, 2017 at 10:34 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

On Tue, Aug 8, 2017 at 2:21 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:

On Tue, Jun 13, 2017 at 7:20 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

On Fri, Oct 14, 2016 at 7:26 AM, Alvaro Herrera
<alvherre@2ndquadrant.com>
wrote:

I have sent the partial patch I have to Hari Babu Kommi. We expect
that
he will be able to further this goal some more.

Thanks Alvaro for sharing your development patch.

Most of the patch design is same as described by Alvaro in the first
mail
[1].
I will detail the modifications, pending items and open items (needs
discussion)
to implement proper pluggable storage.

Here I attached WIP patches to support pluggable storage. The patch
series
are may not work individually. Still so many things are under
development.
These patches are just to share the approach of the current development.

+typedef struct StorageAmRoutine
+{

In this structure, you have already covered most of the API's that a
new storage module needs to provide, but I think there could be more.
One such API could be heap_hot_search. This seems specific to current
heap where we have the provision of HOT. I think we can provide a new
API tuple_search or something like that.

Thanks for the review.

Yes, the storageAmRoutine needs more function pointers. Currently I am
adding all the functions that are present in the heapam.h and some slot
related function from tuptable.h.

Hmm, this API is exposed via heapam.h. Am I missing something?

Once I stabilize the code and API's that
are
currently added, then I will further enhance it with remaining functions
that
are necessary to support pluggable storage API.

Sure, but I think if we found any thing during development/review,
then we should either add it immediately or at the very least add fix
me in a patch to avoid forgetting the finding.

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#67Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Amit Kapila (#66)
Re: Pluggable storage

On Sun, Aug 13, 2017 at 5:21 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:

On Sat, Aug 12, 2017 at 10:34 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

On Tue, Aug 8, 2017 at 2:21 PM, Amit Kapila <amit.kapila16@gmail.com>

wrote:

+typedef struct StorageAmRoutine
+{

In this structure, you have already covered most of the API's that a
new storage module needs to provide, but I think there could be more.
One such API could be heap_hot_search. This seems specific to current
heap where we have the provision of HOT. I think we can provide a new
API tuple_search or something like that.

Thanks for the review.

Yes, the storageAmRoutine needs more function pointers. Currently I am
adding all the functions that are present in the heapam.h and some slot
related function from tuptable.h.

Hmm, this API is exposed via heapam.h. Am I missing something?

Sorry I was not clearly explaining in my earlier mail. Yes your are right
that the heap_hot_search function exists in heapam.h, but I am yet to
add all the exposed functions that are present in the heapam.h file to
the storageAmRoutine structure.

Once I stabilize the code and API's that

are
currently added, then I will further enhance it with remaining functions
that
are necessary to support pluggable storage API.

Sure, but I think if we found any thing during development/review,
then we should either add it immediately or at the very least add fix
me in a patch to avoid forgetting the finding.

OK. I will add all the functions that are identified till now.

Regards,
Hari Babu
Fujitsu Australia

#68Andres Freund
andres@anarazel.de
In reply to: Haribabu Kommi (#16)
Re: Pluggable storage

Hi,

On 2017-06-13 11:50:27 +1000, Haribabu Kommi wrote:

Here I attached WIP patches to support pluggable storage. The patch series
are may not work individually. Still so many things are under development.
These patches are just to share the approach of the current development.

Making a pass through the patchset to get a feel where this at, and
where this is headed. I previously skimmed the thread to get a rough
sense on what's discused, but not in a very detailed manner.

General:

- I think one important discussion we need to have is what kind of
performance impact we're going to accept introducing this. It seems
very likely that this'll cause some slowdown. We can kind of
alleviate that by doing some optimizations at the same time, but
nevertheless, this abstraction is going to cost.

- I don't think we should introduce this without a user besides
heapam. The likelihood that API will be usable by anything else
without a testcase seems fairly remote. I think some src/test/modules
type implementation of a per-session, in-memory storage - relatively
easy to implement - or such is necessary.

- I think, and detailed some of that, we're going to need some cleanups
that go in before this, to decrease the size / increase the quality of
the new APIs. It's going to get more painful to change APIs
subsequently.

- We'll have to document clearly that these APIs are going to change for
a while, even after the release introducing them.

StorageAm - Scan stuff:

- I think API isn't quite right. There's a lot of granular callback
functionality like scan_begin_function / scan_begin_catalog /
scan_begin_bm - these largely are convenience wrappers around the same
function, and I don't think that would, or rather should, change in
any abstracted storage layer. So I think there needs to be some
unification first (pretty close w/ beginscan_internal already, but
perhaps we should get rid of a few of these wrappers).

- Some of the exposed functionality, e.g. scan_getpage,
scan_update_snapshot, scan_rescan_set_params looks like it should just
be excised, i.e. there's no reason for it to exist.

- Minor: don't think the _function suffix for Storis necessary, just
makes things long, and every member has it. Besides that, it's also
easy to misunderstand - for a second I understood
scan_getnext_function to be about getting the next function...

- Scans are still represented as HeapScanDesc - I don't think that's
going to fly. Either this needs to be an opaque type (i.e. a struct
that's not defined, just forward declared), or it needs to be a base
struct that individual AMs embed in their own structs. Individual AMs
definitely are going to need different pieces of data.

Storage AM - tuple stuff:

- tuple_get_{xmin, updated_xid, cmin, itempointer, ctid, heaponly} are
each individual functions, that seems pretty painful to maintain, and
v/ likely to just grow and grow. Not sure what the solution is, but
this seems like a hard sell.

- The three *speculative* functions don't quite seem right to me, nor do
  I understand:
+	 *
+	 * 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:
+	 */
  I don't see anything related to slots, here?

Storage AM - slot stuff:

- I think there's a number of wrapper functions (slot_getattr,
slot_getallattrs, getsomeattrs, attisnull) around the same
functionality - that bloats the API and causes slowdowns. Instead we
need something like slot_virtualize_tuple(int16 upto), and the rest
should just be wrappers.

- I think it's wrong to have the slot functionality defined on the
StorageAm level. That'll cause even more indirect function calls (=>
slowness), and besides that the TupleTableSlot struct will need
members for each and every Am.

I think the solution is to instead have an Am level "create slot"
function, and the returned slot is allocated by the Am, with a base
member of TupleTableSlot with basically just tts_nvalid, tts_values,
tts_isnull as members. Those are the only members that can be
accessed without functions.

Access to the individual functions (say store_tuple) would then be
direct members of the TupleTableSlot interface. While that costs a bit
of memory, it removes one indirection from an already performance
critical path.

- MinimalTuples should be one type of slot for the above, except it's
not created by an StorageAm but by a function returning a
TupleTableSlot.

This should remove the need for the slot_copy_min_tuple,
slot_is_physical_tuple functions.

- Right now TupleTableSlots are an executor datastructure, but
these patches (rightly!) make it much more widely used. So I think it
needs to be moved outside of executor/, and probably renamed to
something like TupleHolder or something.

- The oid integration seems wrong - without an accessor oids won't be
queryable with this unless you break through the API. But from a
higher level view I do wonder if it's not time to remove "special" oid
columns and replace them with a normal column. We should be hesitant
enshrining crusty old concepts in new APIs.

Executor integration:

- I'm quite fearful that this'll cause slowdowns in a few tight paths.
The most likely cases here seem to be a) bitmap indexscans b)
indexscans c) highly selective sequential scans. I do wonder if
that can be partially addressed by switching out the individual
executor routines in the relevant scan nodes by something using or
similar to the infrastructure in cc9f08b6b8

Regards,

Andres

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#69Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Amit Kapila (#65)
Re: Pluggable storage

On Sun, Aug 13, 2017 at 5:17 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:

On Sat, Aug 12, 2017 at 10:31 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

Why do we need to store handler function in TupleDesc? As of now, the
above patch series has it available in RelationData and
TupleTableSlot, I am not sure if instead of that keeping it in
TupleDesc is a good idea. Which all kind of places require TupleDesc
to contain handler? If those are few places, can we think of passing
it as a parameter?

Till now I am to able to proceed without adding any storage handler
functions to
TupleDesc structure. Sure, I will try the way of passing as a parameter

when

there is a need of it.

Okay, I think it is better if you discuss such locations before
directly modifying those.

Sure. I will check with community before making any such changes.

During the progress of the patch, I am facing problems in designing the
storage API
regarding the Buffer. For example To replace all the

HeapTupleSatisfiesMVCC

and
related functions with function pointers, In HeapTuple format, the tuple

may

belongs
to one buffer, so the buffer is passed to the HeapTupleSatifisifes***
functions along
with buffer, But in case of other storage formats, the single buffer may

not

contains
the actual data.

Also, it is quite possible that some of the storage Am's don't even
want to return bool as a parameter from HeapTupleSatisfies* API's. I
guess what we need here is to provide a way so that different storage
am's can register their function pointer for an equivalent to
satisfies function. So, we need to change
SnapshotData.SnapshotSatisfiesFunc in some way so that different
handlers can register their function instead of using that directly.
I think that should address the problem you are planning to solve by
omitting buffer parameter.

Thanks for your suggestion. Yes, it is better to go in the direction of
SnapshotSatisfiesFunc.

I verified the above idea of implementing the Tuple visibility functions
and assign them into the snapshotData structure based on the snapshot.

The Tuple visibility functions that are specific to the relation are
available
with the RelationData structure and this structure may not be available,
so I changed the SnapShotData structure to hold an enum to represent
what type of snapshot it is, instead of storing the pointer to the tuple
visibility function. Whenever there is a need to check for the tuple
visibilty
the storageam handler pointer corresponding to the snapshot type is
called and result is obtained as earlier.

This buffer is used to set the Hint bits and mark the

buffer as dirty.
In case if the buffer is not available, the performance may affect for

the

following
queries if the hint bits are not set.

I don't think it is advisable to change that for the current heap.

I didn't change the prototype of existing functions. Currently tuple
visibility
functions assumes that Buffer is always proper, but that may not be correct
based on the storage.

And also the Buffer is used to get from heap_fetch, heap_lock_tuple and
related
functions to check the Tuple visibility, but currently returning a buffer
from the above
heap_** function is not possible for other formats.

Why not? I mean if we consider that all the formats we are worried at
this stage have TID (block number, tuple location), then we can get
the buffer. We might want to consider passing TID as a parameter to
these API's if required to make that possible. You also agreed above
[1] that we can first design the API considering storage formats
having TID.

The current approach is to support the storages that support TID bits.
But what I mean here, in some storage methods (for example column
storage), the tuple is not present in one buffer, the tuple data may be
calculated from many buffers and return the slot/storageTuple (until
unless we change everywhere to slot).

If any of the following code after the storage methods is expecting
a Buffer that should be valid may need some changes to check it first
whether it is a valid or not and perform the operations based on that.

And also for the
HeapTuple data,
the tuple data is copied into palloced buffer instead of pointing

directly

to the page.
So, returning a Buffer is a valid or not here?

Yeah, but I think for the sake of compatibility and not changing too
much in the current API's signature, we should try to avoid it.

Currently I am trying to avoid changing the current API's signatures.
Most of the signature changes are something like HeapTuple -> StorageTuple
and etc.

Currently I am proceeding to remove the Buffer as parameter in the API

and

proceed
further, In case if it affects the performance, we need to find out a
different appraoch
in handling the hint bits.

Leaving aside the performance concern, I am not convinced that it is a
good idea to remove Buffer as a parameter from the API's you have
mentioned above. Would you mind thinking once again keeping the
suggestions provided above in this email to see if we can avoid
removing Buffer as a parameter?

Thanks for your suggestions.
Yes. I am able to proceed without removing Buffer parameter.

Regards,
Hari Babu
Fujitsu Australia

#70Amit Kapila
amit.kapila16@gmail.com
In reply to: Haribabu Kommi (#69)
Re: Pluggable storage

On Mon, Aug 21, 2017 at 12:58 PM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

On Sun, Aug 13, 2017 at 5:17 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:

Also, it is quite possible that some of the storage Am's don't even
want to return bool as a parameter from HeapTupleSatisfies* API's. I
guess what we need here is to provide a way so that different storage
am's can register their function pointer for an equivalent to
satisfies function. So, we need to change
SnapshotData.SnapshotSatisfiesFunc in some way so that different
handlers can register their function instead of using that directly.
I think that should address the problem you are planning to solve by
omitting buffer parameter.

Thanks for your suggestion. Yes, it is better to go in the direction of
SnapshotSatisfiesFunc.

I verified the above idea of implementing the Tuple visibility functions
and assign them into the snapshotData structure based on the snapshot.

The Tuple visibility functions that are specific to the relation are
available
with the RelationData structure and this structure may not be available,

Which functions are you referring here? I don't see anything in
tqual.h that uses RelationData.

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#71Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Andres Freund (#68)
Re: Pluggable storage

On Tue, Aug 15, 2017 at 4:53 PM, Andres Freund <andres@anarazel.de> wrote:

Hi,

On 2017-06-13 11:50:27 +1000, Haribabu Kommi wrote:

Here I attached WIP patches to support pluggable storage. The patch

series

are may not work individually. Still so many things are under

development.

These patches are just to share the approach of the current development.

Making a pass through the patchset to get a feel where this at, and
where this is headed. I previously skimmed the thread to get a rough
sense on what's discused, but not in a very detailed manner.

Thanks for the review.

General:

- I think one important discussion we need to have is what kind of
performance impact we're going to accept introducing this. It seems
very likely that this'll cause some slowdown. We can kind of
alleviate that by doing some optimizations at the same time, but
nevertheless, this abstraction is going to cost.

OK. May be to take some decision, we may need some performance
figures, I will measure the performance once the API's stabilized.

- I don't think we should introduce this without a user besides

heapam. The likelihood that API will be usable by anything else
without a testcase seems fairly remote. I think some src/test/modules
type implementation of a per-session, in-memory storage - relatively
easy to implement - or such is necessary.

Sure, I will add a test module once the API's are stabilized.

- I think, and detailed some of that, we're going to need some cleanups
that go in before this, to decrease the size / increase the quality of
the new APIs. It's going to get more painful to change APIs
subsequently.

- We'll have to document clearly that these APIs are going to change for
a while, even after the release introducing them.

Yes, that's correct, because this is the first time we are developing the
storage API's to support pluggable storage, so it may needs some refinements
based on the usage to support different storage methods.

StorageAm - Scan stuff:

- I think API isn't quite right. There's a lot of granular callback
functionality like scan_begin_function / scan_begin_catalog /
scan_begin_bm - these largely are convenience wrappers around the same
function, and I don't think that would, or rather should, change in
any abstracted storage layer. So I think there needs to be some
unification first (pretty close w/ beginscan_internal already, but
perhaps we should get rid of a few of these wrappers).

OK. I will change the API to add a function to beginscan_internal and
replace
the rest of the functions usage with beginscan_internal. And also there are
many bool flags that are passed to the beginscan_internal, I will try to
optimize
them also.

- Some of the exposed functionality, e.g. scan_getpage,
scan_update_snapshot, scan_rescan_set_params looks like it should just
be excised, i.e. there's no reason for it to exist.

Currently these API's are used only in Bitmap and Sample scan's.
These scan methods are fully depends on the heap format. I will
check how to remove these API's.

- Minor: don't think the _function suffix for Storis necessary, just

makes things long, and every member has it. Besides that, it's also
easy to misunderstand - for a second I understood
scan_getnext_function to be about getting the next function...

OK. How about adding _hook?

- Scans are still represented as HeapScanDesc - I don't think that's
going to fly. Either this needs to be an opaque type (i.e. a struct
that's not defined, just forward declared), or it needs to be a base
struct that individual AMs embed in their own structs. Individual AMs
definitely are going to need different pieces of data.

Currently the internal members of the HeapScanDesc are directly used
in many places especially in Bitmap and Sample scan's. I am yet to write
the code in a best way to handle these scan methods and then removing
its usage will be easy.

Storage AM - tuple stuff:

- tuple_get_{xmin, updated_xid, cmin, itempointer, ctid, heaponly} are
each individual functions, that seems pretty painful to maintain, and
v/ likely to just grow and grow. Not sure what the solution is, but
this seems like a hard sell.

OK. How about adding a one API and takes some flags to represent
what type of data that is needed from the tuple and returned the
corresponding
data as void *. The caller must typecast the data to their corresponding
type before use it.

- The three *speculative* functions don't quite seem right to me, nor do

I understand:
+        *
+        * 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:
+        */
I don't see anything related to slots, here?

The tuple_set_speculative_token API is not required. Just update the slot
member directly with speculative token is fine and this value is used in
the tuple_insert API to form the tuple with speculative token. Later with
the other two API's the tuple is either finished or aborted.

Storage AM - slot stuff:

- I think there's a number of wrapper functions (slot_getattr,
slot_getallattrs, getsomeattrs, attisnull) around the same
functionality - that bloats the API and causes slowdowns. Instead we
need something like slot_virtualize_tuple(int16 upto), and the rest
should just be wrappers.

OK. I will change accordingly.

- I think it's wrong to have the slot functionality defined on the
StorageAm level. That'll cause even more indirect function calls (=>
slowness), and besides that the TupleTableSlot struct will need
members for each and every Am.

I think the solution is to instead have an Am level "create slot"
function, and the returned slot is allocated by the Am, with a base
member of TupleTableSlot with basically just tts_nvalid, tts_values,
tts_isnull as members. Those are the only members that can be
accessed without functions.

Access to the individual functions (say store_tuple) would then be
direct members of the TupleTableSlot interface. While that costs a bit
of memory, it removes one indirection from an already performance
critical path.

OK. This will change the structure to hold the minimal members that are
accessed irrespective of storage AM, and rest of data will be a void pointer
that can be accessed by only the storage AM.

- MinimalTuples should be one type of slot for the above, except it's

not created by an StorageAm but by a function returning a
TupleTableSlot.

This should remove the need for the slot_copy_min_tuple,
slot_is_physical_tuple functions.

OK.

- Right now TupleTableSlots are an executor datastructure, but
these patches (rightly!) make it much more widely used. So I think it
needs to be moved outside of executor/, and probably renamed to
something like TupleHolder or something.

OK.

- The oid integration seems wrong - without an accessor oids won't be
queryable with this unless you break through the API. But from a
higher level view I do wonder if it's not time to remove "special" oid
columns and replace them with a normal column. We should be hesitant
enshrining crusty old concepts in new APIs.

OK.

Executor integration:

- I'm quite fearful that this'll cause slowdowns in a few tight paths.
The most likely cases here seem to be a) bitmap indexscans b)
indexscans c) highly selective sequential scans. I do wonder if
that can be partially addressed by switching out the individual
executor routines in the relevant scan nodes by something using or
similar to the infrastructure in cc9f08b6b8

Sorry, I didn't understand this point clearly. Can you provide some more
details.

Regards,
Hari Babu
Fujitsu Australia

#72Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Amit Kapila (#70)
Re: Pluggable storage

On Mon, Aug 21, 2017 at 7:25 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:

On Mon, Aug 21, 2017 at 12:58 PM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

On Sun, Aug 13, 2017 at 5:17 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:

Also, it is quite possible that some of the storage Am's don't even
want to return bool as a parameter from HeapTupleSatisfies* API's. I
guess what we need here is to provide a way so that different storage
am's can register their function pointer for an equivalent to
satisfies function. So, we need to change
SnapshotData.SnapshotSatisfiesFunc in some way so that different
handlers can register their function instead of using that directly.
I think that should address the problem you are planning to solve by
omitting buffer parameter.

Thanks for your suggestion. Yes, it is better to go in the direction of
SnapshotSatisfiesFunc.

I verified the above idea of implementing the Tuple visibility functions
and assign them into the snapshotData structure based on the snapshot.

The Tuple visibility functions that are specific to the relation are
available
with the RelationData structure and this structure may not be available,

Which functions are you referring here? I don't see anything in
tqual.h that uses RelationData.

With storage API's, the tuple visibility functions are available with
RelationData
and those are needs used to update the SnapshotData structure
SnapshotSatisfiesFunc member.

But the RelationData is not available everywhere, where the snapshot is
created,
but it is available every place where the tuple visibility is checked. So I
just changed
the way of checking the tuple visibility with the information of snapshot
by calling
the corresponding tuple visibility function from RelationData.

If SnapshotData provides MVCC, then the MVCC specific tuple visibility
function from
RelationData is called. The SnapshotSatisfiesFunc member is changed to a
enum
that holds the tuple visibility type such as MVCC, DIRTY, SELF and etc.
Whenever
the visibility check is needed, the corresponding function is called.

Regards,
Hari Babu
Fujitsu Australia

#73Amit Kapila
amit.kapila16@gmail.com
In reply to: Haribabu Kommi (#72)
Re: Pluggable storage

On Wed, Aug 23, 2017 at 11:05 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

On Mon, Aug 21, 2017 at 7:25 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:

On Mon, Aug 21, 2017 at 12:58 PM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

On Sun, Aug 13, 2017 at 5:17 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:

Also, it is quite possible that some of the storage Am's don't even
want to return bool as a parameter from HeapTupleSatisfies* API's. I
guess what we need here is to provide a way so that different storage
am's can register their function pointer for an equivalent to
satisfies function. So, we need to change
SnapshotData.SnapshotSatisfiesFunc in some way so that different
handlers can register their function instead of using that directly.
I think that should address the problem you are planning to solve by
omitting buffer parameter.

Thanks for your suggestion. Yes, it is better to go in the direction of
SnapshotSatisfiesFunc.

I verified the above idea of implementing the Tuple visibility functions
and assign them into the snapshotData structure based on the snapshot.

The Tuple visibility functions that are specific to the relation are
available
with the RelationData structure and this structure may not be available,

Which functions are you referring here? I don't see anything in
tqual.h that uses RelationData.

With storage API's, the tuple visibility functions are available with
RelationData
and those are needs used to update the SnapshotData structure
SnapshotSatisfiesFunc member.

But the RelationData is not available everywhere, where the snapshot is
created,
but it is available every place where the tuple visibility is checked. So I
just changed
the way of checking the tuple visibility with the information of snapshot by
calling
the corresponding tuple visibility function from RelationData.

If SnapshotData provides MVCC, then the MVCC specific tuple visibility
function from
RelationData is called. The SnapshotSatisfiesFunc member is changed to a
enum
that holds the tuple visibility type such as MVCC, DIRTY, SELF and etc.
Whenever
the visibility check is needed, the corresponding function is called.

It will be easy to understand and see if there is some better
alternative once you have something in the form of a patch.

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#74Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Amit Kapila (#73)
Re: Pluggable storage

On Wed, Aug 23, 2017 at 11:59 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:

On Wed, Aug 23, 2017 at 11:05 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

On Mon, Aug 21, 2017 at 7:25 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:

On Mon, Aug 21, 2017 at 12:58 PM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

On Sun, Aug 13, 2017 at 5:17 PM, Amit Kapila <amit.kapila16@gmail.com

wrote:

Also, it is quite possible that some of the storage Am's don't even
want to return bool as a parameter from HeapTupleSatisfies* API's. I
guess what we need here is to provide a way so that different storage
am's can register their function pointer for an equivalent to
satisfies function. So, we need to change
SnapshotData.SnapshotSatisfiesFunc in some way so that different
handlers can register their function instead of using that directly.
I think that should address the problem you are planning to solve by
omitting buffer parameter.

Thanks for your suggestion. Yes, it is better to go in the direction

of

SnapshotSatisfiesFunc.

I verified the above idea of implementing the Tuple visibility

functions

and assign them into the snapshotData structure based on the snapshot.

The Tuple visibility functions that are specific to the relation are
available
with the RelationData structure and this structure may not be

available,

Which functions are you referring here? I don't see anything in
tqual.h that uses RelationData.

With storage API's, the tuple visibility functions are available with
RelationData
and those are needs used to update the SnapshotData structure
SnapshotSatisfiesFunc member.

But the RelationData is not available everywhere, where the snapshot is
created,
but it is available every place where the tuple visibility is checked.

So I

just changed
the way of checking the tuple visibility with the information of

snapshot by

calling
the corresponding tuple visibility function from RelationData.

If SnapshotData provides MVCC, then the MVCC specific tuple visibility
function from
RelationData is called. The SnapshotSatisfiesFunc member is changed to a
enum
that holds the tuple visibility type such as MVCC, DIRTY, SELF and etc.
Whenever
the visibility check is needed, the corresponding function is called.

It will be easy to understand and see if there is some better
alternative once you have something in the form of a patch.

Sorry for the delay.

I will submit the new patch series with all comments given
in the upthread to the upcoming commitfest.

Regards,
Hari Babu
Fujitsu Australia

#75Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Haribabu Kommi (#74)
7 attachment(s)
Re: Pluggable storage

On Sat, Aug 26, 2017 at 1:34 PM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:

I will submit the new patch series with all comments given
in the upthread to the upcoming commitfest.

Here I attached new set of patches that are rebased to the latest master.

0001-Change-Create-Access-method-to-include-storage-handl:

Add the support of storage method to create as part of create
access method syntax.

0002-Storage-AM-API-hooks-and-related-functions:

The necessary storage AM API hooks that are required
to support pluggable storage API and supporting functions
to frame the storage routine.

0003-Adding-storageam-hanlder-to-relation-structure:

Add the storageAm routine pointer to relation structure
and necessary functions to initialize the storage AM routine
whenever the relation is built.

0004-Adding-tuple-visibility-function-to-storage-AM:

The tuple visibility functions are moved into heap storage AM
and the visibility function in snapshot structure is changed from
function pointer to an enum to indicate what type of snapshot
it is. Based on that enum, the corresponding visibility function
is executed from the relation storage AM routine.

0005-slot-hooks-are-added-to-storage-AM:

The slot specific storage AM routine pointer is added to slot
structure and removed some of the members and created
a new HeapamSlot structure to hold the necessary tuple
information. Currently the slot supports the minimal tuple
also.

Currently this patch may further needs some changes as it
assumes the tuple is in HeapTuple format in some API's.
This slot storage AM routine may be common to all the
pluggable storage modules.

0006-Tuple-Insert-API-is-added-to-Storage-AM:

The write support functionality is added to storage AM.
And also all the storage AM functions are extracted into
another file to make it easier to understand while writing
and changing the code to support new API's.

0007-Scan-functions-are-added-to-storage-AM:

All the scan supported functions are added to storage AM.
And these functions are also extracted into storageam.c file.

Pending comments from Andres:
1. Remove the usage of HeapScanDesc.
2. Remove the usage of scan_getpage, scan_update_snapshot
and scan_rescan_set_params hooks.
3. Many places the relation is not available while creating the slot,
and also slot shouldn't depend on relation, because the slot values
are may be some times from two different storage relations also.
So I modified the slot code to use the same storage mechanism.
4.Tuples functionality moving into a separate folder.

Other pending activities are:
1. Handle Bitmap and sample scans as they dependent on heap format.
2. Add some configuration flags in the storage AM, based on these flags
whether vacuum can run on these relations or not will be decided. This
may be further enhanced to provide the cost parameters also that can be
used for planning.

I attached individual patches in the mail, in case if it increases the mail
size,
or creates problems to some one, i will attach the zip version from next
time
onward.

Regards,
Hari Babu
Fujitsu Australia

Attachments:

0007-Scan-functions-are-added-to-storage-AM.patchapplication/octet-stream; name=0007-Scan-functions-are-added-to-storage-AM.patchDownload
From 2c2b95107ccc45ab44eaf5e155ba1f44fe17c5c4 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Wed, 30 Aug 2017 19:00:19 +1000
Subject: [PATCH 7/7] 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           | 1509 ++--------------------------
 src/backend/access/heap/heapam_storage.c   | 1303 ++++++++++++++++++++++++
 src/backend/access/heap/rewriteheap.c      |    2 +-
 src/backend/access/heap/storageam.c        |  235 +++++
 src/backend/access/index/genam.c           |    7 +-
 src/backend/access/index/indexam.c         |    3 +-
 src/backend/access/nbtree/nbtinsert.c      |    5 +-
 src/backend/bootstrap/bootstrap.c          |   25 +-
 src/backend/catalog/aclchk.c               |   13 +-
 src/backend/catalog/index.c                |   27 +-
 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/trigger.c             |    3 +-
 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  |   11 +-
 src/backend/executor/nodeForeignscan.c     |    7 +-
 src/backend/executor/nodeGather.c          |    8 +-
 src/backend/executor/nodeGatherMerge.c     |   12 +-
 src/backend/executor/nodeIndexonlyscan.c   |    4 +-
 src/backend/executor/nodeIndexscan.c       |   16 +-
 src/backend/executor/nodeSamplescan.c      |   21 +-
 src/backend/executor/nodeSeqscan.c         |   39 +-
 src/backend/executor/nodeWindowAgg.c       |    4 +-
 src/backend/executor/spi.c                 |   20 +-
 src/backend/executor/tqueue.c              |   16 +-
 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                |   29 -
 src/include/access/heapam_common.h         |    8 +
 src/include/access/storageam.h             |   42 +-
 src/include/executor/functions.h           |    2 +-
 src/include/executor/spi.h                 |   10 +-
 src/include/executor/tqueue.h              |    2 +-
 src/include/funcapi.h                      |    2 +-
 55 files changed, 1920 insertions(+), 1740 deletions(-)

diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index 5f076ef..063e079 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 f7b68a8..eb33b26 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 1eb2c32..4146015 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -81,19 +81,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 XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 				Buffer newbuf, HeapTuple oldtup,
 				HeapTuple newtup, HeapTuple old_key_tup,
@@ -112,139 +99,6 @@ static bool Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
 static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_modified,
 					   bool *copy);
 
-/* ----------------------------------------------------------------
- *						 heap support routines
- * ----------------------------------------------------------------
- */
-
-/* ----------------
- *		initscan - scan code common to heap_beginscan and heap_rescan
- * ----------------
- */
-static void
-initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
-{
-	bool		allow_strat;
-	bool		allow_sync;
-
-	/*
-	 * Determine the number of blocks we have to scan.
-	 *
-	 * It is sufficient to do this once at scan start, since any tuples added
-	 * while the scan is in progress will be invisible to my snapshot anyway.
-	 * (That is not true when using a non-MVCC snapshot.  However, we couldn't
-	 * guarantee to return tuples added after scan start anyway, since they
-	 * might go into pages we already scanned.  To guarantee consistent
-	 * results for a non-MVCC snapshot, the caller must hold some higher-level
-	 * lock that ensures the interesting tuple(s) won't change.)
-	 */
-	if (scan->rs_parallel != NULL)
-		scan->rs_nblocks = scan->rs_parallel->phs_nblocks;
-	else
-		scan->rs_nblocks = RelationGetNumberOfBlocks(scan->rs_rd);
-
-	/*
-	 * If the table is large relative to NBuffers, use a bulk-read access
-	 * strategy and enable synchronized scanning (see syncscan.c).  Although
-	 * the thresholds for these features could be different, we make them the
-	 * same so that there are only two behaviors to tune rather than four.
-	 * (However, some callers need to be able to disable one or both of these
-	 * behaviors, independently of the size of the table; also there is a GUC
-	 * variable that can disable synchronized scanning.)
-	 *
-	 * 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)
-	{
-		allow_strat = scan->rs_allow_strat;
-		allow_sync = scan->rs_allow_sync;
-	}
-	else
-		allow_strat = allow_sync = false;
-
-	if (allow_strat)
-	{
-		/* During a rescan, keep the previous strategy object. */
-		if (scan->rs_strategy == NULL)
-			scan->rs_strategy = GetAccessStrategy(BAS_BULKREAD);
-	}
-	else
-	{
-		if (scan->rs_strategy != NULL)
-			FreeAccessStrategy(scan->rs_strategy);
-		scan->rs_strategy = NULL;
-	}
-
-	if (scan->rs_parallel != NULL)
-	{
-		/* For parallel scan, believe whatever ParallelHeapScanDesc says. */
-		scan->rs_syncscan = scan->rs_parallel->phs_syncscan;
-	}
-	else if (keep_startblock)
-	{
-		/*
-		 * When rescanning, we want to keep the previous startblock setting,
-		 * so that rewinding a cursor doesn't generate surprising results.
-		 * Reset the active syncscan setting, though.
-		 */
-		scan->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);
-	}
-	else
-	{
-		scan->rs_syncscan = false;
-		scan->rs_startblock = 0;
-	}
-
-	scan->rs_numblocks = InvalidBlockNumber;
-	scan->rs_inited = false;
-	scan->rs_ctup.t_data = NULL;
-	ItemPointerSetInvalid(&scan->rs_ctup.t_self);
-	scan->rs_cbuf = InvalidBuffer;
-	scan->rs_cblock = InvalidBlockNumber;
-
-	/* page-at-a-time fields are always invalid when not rs_inited */
-
-	/*
-	 * copy the scan key, if appropriate
-	 */
-	if (key != NULL)
-		memcpy(scan->rs_key, key, scan->rs_nkeys * sizeof(ScanKeyData));
-
-	/*
-	 * Currently, we don't have a stats counter for bitmap heap scans (but the
-	 * underlying bitmap index scans will be counted) or sample scans (we only
-	 * update stats for tuple fetches there)
-	 */
-	if (!scan->rs_bitmapscan && !scan->rs_samplescan)
-		pgstat_count_heap_scan(scan->rs_rd);
-}
-
-/*
- * 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
-heap_setscanlimits(HeapScanDesc scan, BlockNumber startBlk, BlockNumber numBlks)
-{
-	Assert(!scan->rs_inited);	/* else too late to change */
-	Assert(!scan->rs_syncscan); /* else rs_startblock is significant */
-
-	/* Check startBlk is valid (but allow case of zero blocks...) */
-	Assert(startBlk == 0 || startBlk < scan->rs_nblocks);
-
-	scan->rs_startblock = startBlk;
-	scan->rs_numblocks = numBlks;
-}
-
 /*
  * heapgetpage - subroutine for heapgettup()
  *
@@ -363,603 +217,6 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 	scan->rs_ntuples = ntup;
 }
 
-/* ----------------
- *		heapgettup - fetch next heap tuple
- *
- *		Initialize the scan if not already done; then advance to the next
- *		tuple as indicated by "dir"; return the next tuple in scan->rs_ctup,
- *		or set scan->rs_ctup.t_data = NULL if no more tuples.
- *
- * dir == NoMovementScanDirection means "re-fetch the tuple indicated
- * by scan->rs_ctup".
- *
- * Note: the reason nkeys/key are passed separately, even though they are
- * kept in the scan descriptor, is that the caller may not want us to check
- * the scankeys.
- *
- * Note: when we fall off the end of the scan in either direction, we
- * reset rs_inited.  This means that a further request with the same
- * scan direction will restart the scan, which is a bit odd, but a
- * request with the opposite scan direction will start a fresh scan
- * in the proper direction.  The latter is required behavior for cursors,
- * while the former case is generally undefined behavior in Postgres
- * so we don't care too much.
- * ----------------
- */
-static void
-heapgettup(HeapScanDesc scan,
-		   ScanDirection dir,
-		   int nkeys,
-		   ScanKey key)
-{
-	HeapTuple	tuple = &(scan->rs_ctup);
-	Snapshot	snapshot = scan->rs_snapshot;
-	bool		backward = ScanDirectionIsBackward(dir);
-	BlockNumber page;
-	bool		finished;
-	Page		dp;
-	int			lines;
-	OffsetNumber lineoff;
-	int			linesleft;
-	ItemId		lpp;
-
-	/*
-	 * calculate next starting lineoff, given scan direction
-	 */
-	if (ScanDirectionIsForward(dir))
-	{
-		if (!scan->rs_inited)
-		{
-			/*
-			 * return null immediately if relation is empty
-			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
-			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
-				tuple->t_data = NULL;
-				return;
-			}
-			if (scan->rs_parallel != NULL)
-			{
-				heap_parallelscan_startblock_init(scan);
-
-				page = heap_parallelscan_nextpage(scan);
-
-				/* Other processes might have already finished the scan. */
-				if (page == InvalidBlockNumber)
-				{
-					Assert(!BufferIsValid(scan->rs_cbuf));
-					tuple->t_data = NULL;
-					return;
-				}
-			}
-			else
-				page = scan->rs_startblock; /* first page */
-			heapgetpage(scan, page);
-			lineoff = FirstOffsetNumber;	/* first offnum */
-			scan->rs_inited = true;
-		}
-		else
-		{
-			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-			lineoff =			/* next offnum */
-				OffsetNumberNext(ItemPointerGetOffsetNumber(&(tuple->t_self)));
-		}
-
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
-		lines = PageGetMaxOffsetNumber(dp);
-		/* page and lineoff now reference the physically next tid */
-
-		linesleft = lines - lineoff + 1;
-	}
-	else if (backward)
-	{
-		/* backward parallel scan not supported */
-		Assert(scan->rs_parallel == NULL);
-
-		if (!scan->rs_inited)
-		{
-			/*
-			 * return null immediately if relation is empty
-			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
-			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
-				tuple->t_data = NULL;
-				return;
-			}
-
-			/*
-			 * Disable reporting to syncscan logic in a backwards scan; it's
-			 * not very likely anyone else is doing the same thing at the same
-			 * time, and much more likely that we'll just bollix things for
-			 * forward scanners.
-			 */
-			scan->rs_syncscan = false;
-			/* start from last page of the scan */
-			if (scan->rs_startblock > 0)
-				page = scan->rs_startblock - 1;
-			else
-				page = scan->rs_nblocks - 1;
-			heapgetpage(scan, page);
-		}
-		else
-		{
-			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-		}
-
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
-		lines = PageGetMaxOffsetNumber(dp);
-
-		if (!scan->rs_inited)
-		{
-			lineoff = lines;	/* final offnum */
-			scan->rs_inited = true;
-		}
-		else
-		{
-			lineoff =			/* previous offnum */
-				OffsetNumberPrev(ItemPointerGetOffsetNumber(&(tuple->t_self)));
-		}
-		/* page and lineoff now reference the physically previous tid */
-
-		linesleft = lineoff;
-	}
-	else
-	{
-		/*
-		 * ``no movement'' scan direction: refetch prior tuple
-		 */
-		if (!scan->rs_inited)
-		{
-			Assert(!BufferIsValid(scan->rs_cbuf));
-			tuple->t_data = NULL;
-			return;
-		}
-
-		page = ItemPointerGetBlockNumber(&(tuple->t_self));
-		if (page != scan->rs_cblock)
-			heapgetpage(scan, page);
-
-		/* Since the tuple was previously fetched, needn't lock page here */
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
-		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
-		lpp = PageGetItemId(dp, lineoff);
-		Assert(ItemIdIsNormal(lpp));
-
-		tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
-		tuple->t_len = ItemIdGetLength(lpp);
-
-		return;
-	}
-
-	/*
-	 * advance the scan until we find a qualifying tuple or run out of stuff
-	 * to scan
-	 */
-	lpp = PageGetItemId(dp, lineoff);
-	for (;;)
-	{
-		while (linesleft > 0)
-		{
-			if (ItemIdIsNormal(lpp))
-			{
-				bool		valid;
-
-				tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
-				tuple->t_len = ItemIdGetLength(lpp);
-				ItemPointerSet(&(tuple->t_self), page, lineoff);
-
-				/*
-				 * if current tuple qualifies, return it.
-				 */
-				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine,
-													 tuple,
-													 snapshot,
-													 scan->rs_cbuf);
-
-				CheckForSerializableConflictOut(valid, scan->rs_rd, tuple,
-												scan->rs_cbuf, snapshot);
-
-				if (valid && key != NULL)
-					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
-								nkeys, key, valid);
-
-				if (valid)
-				{
-					LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
-					return;
-				}
-			}
-
-			/*
-			 * otherwise move to the next item on the page
-			 */
-			--linesleft;
-			if (backward)
-			{
-				--lpp;			/* move back in this page's ItemId array */
-				--lineoff;
-			}
-			else
-			{
-				++lpp;			/* move forward in this page's ItemId array */
-				++lineoff;
-			}
-		}
-
-		/*
-		 * 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);
-
-		/*
-		 * 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);
-			if (page == 0)
-				page = scan->rs_nblocks;
-			page--;
-		}
-		else if (scan->rs_parallel != NULL)
-		{
-			page = heap_parallelscan_nextpage(scan);
-			finished = (page == InvalidBlockNumber);
-		}
-		else
-		{
-			page++;
-			if (page >= scan->rs_nblocks)
-				page = 0;
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
-
-			/*
-			 * Report our new scan position for synchronization purposes. We
-			 * don't do that when moving backwards, however. That would just
-			 * mess up any other forward-moving scanners.
-			 *
-			 * Note: we do this before checking for end of scan so that the
-			 * final state of the position hint is back at the start of the
-			 * rel.  That's not strictly necessary, but otherwise when you run
-			 * the same query multiple times the starting position would shift
-			 * 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);
-		}
-
-		/*
-		 * return NULL if we've exhausted all the pages
-		 */
-		if (finished)
-		{
-			if (BufferIsValid(scan->rs_cbuf))
-				ReleaseBuffer(scan->rs_cbuf);
-			scan->rs_cbuf = InvalidBuffer;
-			scan->rs_cblock = InvalidBlockNumber;
-			tuple->t_data = NULL;
-			scan->rs_inited = false;
-			return;
-		}
-
-		heapgetpage(scan, page);
-
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
-		lines = PageGetMaxOffsetNumber((Page) dp);
-		linesleft = lines;
-		if (backward)
-		{
-			lineoff = lines;
-			lpp = PageGetItemId(dp, lines);
-		}
-		else
-		{
-			lineoff = FirstOffsetNumber;
-			lpp = PageGetItemId(dp, FirstOffsetNumber);
-		}
-	}
-}
-
-/* ----------------
- *		heapgettup_pagemode - fetch next heap tuple in page-at-a-time mode
- *
- *		Same API as heapgettup, but used in page-at-a-time mode
- *
- * The internal logic is much the same as heapgettup's too, but there are some
- * differences: we do not take the buffer content lock (that only needs to
- * happen inside heapgetpage), and we iterate through just the tuples listed
- * in rs_vistuples[] rather than all tuples on the page.  Notice that
- * lineindex is 0-based, where the corresponding loop variable lineoff in
- * heapgettup is 1-based.
- * ----------------
- */
-static void
-heapgettup_pagemode(HeapScanDesc scan,
-					ScanDirection dir,
-					int nkeys,
-					ScanKey key)
-{
-	HeapTuple	tuple = &(scan->rs_ctup);
-	bool		backward = ScanDirectionIsBackward(dir);
-	BlockNumber page;
-	bool		finished;
-	Page		dp;
-	int			lines;
-	int			lineindex;
-	OffsetNumber lineoff;
-	int			linesleft;
-	ItemId		lpp;
-
-	/*
-	 * calculate next starting lineindex, given scan direction
-	 */
-	if (ScanDirectionIsForward(dir))
-	{
-		if (!scan->rs_inited)
-		{
-			/*
-			 * return null immediately if relation is empty
-			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
-			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
-				tuple->t_data = NULL;
-				return;
-			}
-			if (scan->rs_parallel != NULL)
-			{
-				heap_parallelscan_startblock_init(scan);
-
-				page = heap_parallelscan_nextpage(scan);
-
-				/* Other processes might have already finished the scan. */
-				if (page == InvalidBlockNumber)
-				{
-					Assert(!BufferIsValid(scan->rs_cbuf));
-					tuple->t_data = NULL;
-					return;
-				}
-			}
-			else
-				page = scan->rs_startblock; /* first page */
-			heapgetpage(scan, page);
-			lineindex = 0;
-			scan->rs_inited = true;
-		}
-		else
-		{
-			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-			lineindex = scan->rs_cindex + 1;
-		}
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
-		/* page and lineindex now reference the next visible tid */
-
-		linesleft = lines - lineindex;
-	}
-	else if (backward)
-	{
-		/* backward parallel scan not supported */
-		Assert(scan->rs_parallel == NULL);
-
-		if (!scan->rs_inited)
-		{
-			/*
-			 * return null immediately if relation is empty
-			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
-			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
-				tuple->t_data = NULL;
-				return;
-			}
-
-			/*
-			 * Disable reporting to syncscan logic in a backwards scan; it's
-			 * not very likely anyone else is doing the same thing at the same
-			 * time, and much more likely that we'll just bollix things for
-			 * forward scanners.
-			 */
-			scan->rs_syncscan = false;
-			/* start from last page of the scan */
-			if (scan->rs_startblock > 0)
-				page = scan->rs_startblock - 1;
-			else
-				page = scan->rs_nblocks - 1;
-			heapgetpage(scan, page);
-		}
-		else
-		{
-			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-		}
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
-
-		if (!scan->rs_inited)
-		{
-			lineindex = lines - 1;
-			scan->rs_inited = true;
-		}
-		else
-		{
-			lineindex = scan->rs_cindex - 1;
-		}
-		/* page and lineindex now reference the previous visible tid */
-
-		linesleft = lineindex + 1;
-	}
-	else
-	{
-		/*
-		 * ``no movement'' scan direction: refetch prior tuple
-		 */
-		if (!scan->rs_inited)
-		{
-			Assert(!BufferIsValid(scan->rs_cbuf));
-			tuple->t_data = NULL;
-			return;
-		}
-
-		page = ItemPointerGetBlockNumber(&(tuple->t_self));
-		if (page != scan->rs_cblock)
-			heapgetpage(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);
-		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
-		lpp = PageGetItemId(dp, lineoff);
-		Assert(ItemIdIsNormal(lpp));
-
-		tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
-		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]);
-
-		return;
-	}
-
-	/*
-	 * advance the scan until we find a qualifying tuple or run out of stuff
-	 * to scan
-	 */
-	for (;;)
-	{
-		while (linesleft > 0)
-		{
-			lineoff = scan->rs_vistuples[lineindex];
-			lpp = PageGetItemId(dp, lineoff);
-			Assert(ItemIdIsNormal(lpp));
-
-			tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
-			tuple->t_len = ItemIdGetLength(lpp);
-			ItemPointerSet(&(tuple->t_self), page, lineoff);
-
-			/*
-			 * if current tuple qualifies, return it.
-			 */
-			if (key != NULL)
-			{
-				bool		valid;
-
-				HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
-							nkeys, key, valid);
-				if (valid)
-				{
-					scan->rs_cindex = lineindex;
-					return;
-				}
-			}
-			else
-			{
-				scan->rs_cindex = lineindex;
-				return;
-			}
-
-			/*
-			 * otherwise move to the next item on the page
-			 */
-			--linesleft;
-			if (backward)
-				--lineindex;
-			else
-				++lineindex;
-		}
-
-		/*
-		 * if we get here, it means we've exhausted the items on this page and
-		 * it's time to move to the next.
-		 */
-		if (backward)
-		{
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
-			if (page == 0)
-				page = scan->rs_nblocks;
-			page--;
-		}
-		else if (scan->rs_parallel != NULL)
-		{
-			page = heap_parallelscan_nextpage(scan);
-			finished = (page == InvalidBlockNumber);
-		}
-		else
-		{
-			page++;
-			if (page >= scan->rs_nblocks)
-				page = 0;
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
-
-			/*
-			 * Report our new scan position for synchronization purposes. We
-			 * don't do that when moving backwards, however. That would just
-			 * mess up any other forward-moving scanners.
-			 *
-			 * Note: we do this before checking for end of scan so that the
-			 * final state of the position hint is back at the start of the
-			 * rel.  That's not strictly necessary, but otherwise when you run
-			 * the same query multiple times the starting position would shift
-			 * 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);
-		}
-
-		/*
-		 * return NULL if we've exhausted all the pages
-		 */
-		if (finished)
-		{
-			if (BufferIsValid(scan->rs_cbuf))
-				ReleaseBuffer(scan->rs_cbuf);
-			scan->rs_cbuf = InvalidBuffer;
-			scan->rs_cblock = InvalidBlockNumber;
-			tuple->t_data = NULL;
-			scan->rs_inited = false;
-			return;
-		}
-
-		heapgetpage(scan, page);
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
-		linesleft = lines;
-		if (backward)
-			lineindex = lines - 1;
-		else
-			lineindex = 0;
-	}
-}
-
 
 #if defined(DISABLE_COMPLEX_MACRO)
 /*
@@ -1188,332 +445,97 @@ relation_close(Relation relation, LOCKMODE lockmode)
 
 
 /* ----------------
- *		heap_open - open a heap relation by relation OID
- *
- *		This is essentially relation_open plus check that the relation
- *		is not an index nor a composite type.  (The caller should also
- *		check that it's not a view or foreign table before assuming it has
- *		storage.)
- * ----------------
- */
-Relation
-heap_open(Oid relationId, LOCKMODE lockmode)
-{
-	Relation	r;
-
-	r = relation_open(relationId, lockmode);
-
-	if (r->rd_rel->relkind == RELKIND_INDEX)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is an index",
-						RelationGetRelationName(r))));
-	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is a composite type",
-						RelationGetRelationName(r))));
-
-	return r;
-}
-
-/* ----------------
- *		heap_openrv - open a heap relation specified
- *		by a RangeVar node
- *
- *		As above, but relation is specified by a RangeVar.
- * ----------------
- */
-Relation
-heap_openrv(const RangeVar *relation, LOCKMODE lockmode)
-{
-	Relation	r;
-
-	r = relation_openrv(relation, lockmode);
-
-	if (r->rd_rel->relkind == RELKIND_INDEX)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is an index",
-						RelationGetRelationName(r))));
-	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is a composite type",
-						RelationGetRelationName(r))));
-
-	return r;
-}
-
-/* ----------------
- *		heap_openrv_extended - open a heap relation specified
- *		by a RangeVar node
- *
- *		As above, but optionally return NULL instead of failing for
- *		relation-not-found.
- * ----------------
- */
-Relation
-heap_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
-					 bool missing_ok)
-{
-	Relation	r;
-
-	r = relation_openrv_extended(relation, lockmode, missing_ok);
-
-	if (r)
-	{
-		if (r->rd_rel->relkind == RELKIND_INDEX)
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("\"%s\" is an index",
-							RelationGetRelationName(r))));
-		else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("\"%s\" is a composite type",
-							RelationGetRelationName(r))));
-	}
-
-	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)
-{
-	HeapScanDesc scan;
-
-	/*
-	 * increment relation ref count while scanning relation
-	 *
-	 * This is just to make really sure the relcache entry won't go away while
-	 * the scan has a pointer to it.  Caller should be holding the rel open
-	 * anyway, so this is redundant in all normal scenarios...
-	 */
-	RelationIncrementReferenceCount(relation);
-
-	/*
-	 * allocate and initialize scan descriptor
-	 */
-	scan = (HeapScanDesc) palloc(sizeof(HeapScanDescData));
-
-	scan->rs_rd = relation;
-	scan->rs_snapshot = snapshot;
-	scan->rs_nkeys = nkeys;
-	scan->rs_bitmapscan = is_bitmapscan;
-	scan->rs_samplescan = is_samplescan;
-	scan->rs_strategy = NULL;	/* set in initscan */
-	scan->rs_allow_strat = allow_strat;
-	scan->rs_allow_sync = allow_sync;
-	scan->rs_temp_snap = temp_snap;
-	scan->rs_parallel = parallel_scan;
-
-	/*
-	 * we can use page-at-a-time mode if it's an MVCC-safe snapshot
-	 */
-	scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(snapshot);
-
-	/*
-	 * For a seqscan in a serializable transaction, acquire a predicate lock
-	 * on the entire relation. This is required not only to lock all the
-	 * matching tuples, but also to conflict with new insertions into the
-	 * table. In an indexscan, we take page locks on the index pages covering
-	 * the range specified in the scan qual, but in a heap scan there is
-	 * nothing more fine-grained to lock. A bitmap scan is a different story,
-	 * there we have already scanned the index and locked the index pages
-	 * covering the predicate. But in that case we still have to lock any
-	 * matching heap tuples.
-	 */
-	if (!is_bitmapscan)
-		PredicateLockRelation(relation, snapshot);
-
-	/* we only need to set this up once */
-	scan->rs_ctup.t_tableOid = RelationGetRelid(relation);
-
-	/*
-	 * we do this here instead of in initscan() because heap_rescan also calls
-	 * initscan() and we don't want to allocate memory again
-	 */
-	if (nkeys > 0)
-		scan->rs_key = (ScanKey) palloc(sizeof(ScanKeyData) * nkeys);
-	else
-		scan->rs_key = NULL;
-
-	initscan(scan, key, false);
-
-	return scan;
-}
-
-/* ----------------
- *		heap_rescan		- restart a relation scan
+ *		heap_open - open a heap relation by relation OID
+ *
+ *		This is essentially relation_open plus check that the relation
+ *		is not an index nor a composite type.  (The caller should also
+ *		check that it's not a view or foreign table before assuming it has
+ *		storage.)
  * ----------------
  */
-void
-heap_rescan(HeapScanDesc scan,
-			ScanKey key)
+Relation
+heap_open(Oid relationId, LOCKMODE lockmode)
 {
-	/*
-	 * unpin scan buffers
-	 */
-	if (BufferIsValid(scan->rs_cbuf))
-		ReleaseBuffer(scan->rs_cbuf);
+	Relation	r;
 
-	/*
-	 * reinitialize scan descriptor
-	 */
-	initscan(scan, key, true);
+	r = relation_open(relationId, lockmode);
 
-	/*
-	 * reset parallel scan, if present
-	 */
-	if (scan->rs_parallel != NULL)
-	{
-		ParallelHeapScanDesc parallel_scan;
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is an index",
+						RelationGetRelationName(r))));
+	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is a composite type",
+						RelationGetRelationName(r))));
 
-		/*
-		 * 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);
-	}
+	return r;
 }
 
 /* ----------------
- *		heap_rescan_set_params	- restart a relation scan after changing params
+ *		heap_openrv - open a heap relation specified
+ *		by a RangeVar node
  *
- * 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.
+ *		As above, but relation is specified by a RangeVar.
  * ----------------
  */
-void
-heap_rescan_set_params(HeapScanDesc scan, ScanKey key,
-					   bool allow_strat, bool allow_sync, bool allow_pagemode)
+Relation
+heap_openrv(const RangeVar *relation, LOCKMODE lockmode)
 {
-	/* 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);
+	Relation	r;
+
+	r = relation_openrv(relation, lockmode);
+
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is an index",
+						RelationGetRelationName(r))));
+	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is a composite type",
+						RelationGetRelationName(r))));
+
+	return r;
 }
 
 /* ----------------
- *		heap_endscan	- end relation scan
+ *		heap_openrv_extended - open a heap relation specified
+ *		by a RangeVar node
  *
- *		See how to integrate with index scans.
- *		Check handling if reldesc caching.
+ *		As above, but optionally return NULL instead of failing for
+ *		relation-not-found.
  * ----------------
  */
-void
-heap_endscan(HeapScanDesc scan)
+Relation
+heap_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
+					 bool missing_ok)
 {
-	/* Note: no locking manipulations needed */
-
-	/*
-	 * unpin scan buffers
-	 */
-	if (BufferIsValid(scan->rs_cbuf))
-		ReleaseBuffer(scan->rs_cbuf);
-
-	/*
-	 * decrement relation reference count and free scan descriptor storage
-	 */
-	RelationDecrementReferenceCount(scan->rs_rd);
-
-	if (scan->rs_key)
-		pfree(scan->rs_key);
+	Relation	r;
 
-	if (scan->rs_strategy != NULL)
-		FreeAccessStrategy(scan->rs_strategy);
+	r = relation_openrv_extended(relation, lockmode, missing_ok);
 
-	if (scan->rs_temp_snap)
-		UnregisterSnapshot(scan->rs_snapshot);
+	if (r)
+	{
+		if (r->rd_rel->relkind == RELKIND_INDEX)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("\"%s\" is an index",
+							RelationGetRelationName(r))));
+		else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("\"%s\" is a composite type",
+							RelationGetRelationName(r))));
+	}
 
-	pfree(scan);
+	return r;
 }
 
+
 /* ----------------
  *		heap_parallelscan_estimate - estimate storage for ParallelHeapScanDesc
  *
@@ -1552,379 +574,6 @@ heap_parallelscan_initialize(ParallelHeapScanDesc target, Relation relation,
 	SerializeSnapshot(snapshot, target->phs_snapshot_data);
 }
 
-/* ----------------
- *		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
- *
- *		Determine where the parallel seq scan should start.  This function may
- *		be called many times, once by each parallel worker.  We must be careful
- *		only to set the startblock once.
- * ----------------
- */
-static void
-heap_parallelscan_startblock_init(HeapScanDesc scan)
-{
-	BlockNumber sync_startpage = InvalidBlockNumber;
-	ParallelHeapScanDesc parallel_scan;
-
-	Assert(scan->rs_parallel);
-	parallel_scan = scan->rs_parallel;
-
-retry:
-	/* Grab the spinlock. */
-	SpinLockAcquire(&parallel_scan->phs_mutex);
-
-	/*
-	 * If the scan's startblock has not yet been initialized, we must do so
-	 * now.  If this is not a synchronized scan, we just start at block 0, but
-	 * if it is a synchronized scan, we must get the starting position from
-	 * the synchronized scan machinery.  We can't hold the spinlock while
-	 * doing that, though, so release the spinlock, get the information we
-	 * need, and retry.  If nobody else has initialized the scan in the
-	 * meantime, we'll fill in the value we fetched on the second time
-	 * through.
-	 */
-	if (parallel_scan->phs_startblock == InvalidBlockNumber)
-	{
-		if (!parallel_scan->phs_syncscan)
-			parallel_scan->phs_startblock = 0;
-		else if (sync_startpage != InvalidBlockNumber)
-			parallel_scan->phs_startblock = sync_startpage;
-		else
-		{
-			SpinLockRelease(&parallel_scan->phs_mutex);
-			sync_startpage = ss_get_location(scan->rs_rd, scan->rs_nblocks);
-			goto retry;
-		}
-	}
-	SpinLockRelease(&parallel_scan->phs_mutex);
-}
-
-/* ----------------
- *		heap_parallelscan_nextpage - get the next page to scan
- *
- *		Get the next page to scan.  Even if there are no pages left to scan,
- *		another backend could have grabbed a page to scan and not yet finished
- *		looking at it, so it doesn't follow that the scan is done when the
- *		first backend gets an InvalidBlockNumber return.
- * ----------------
- */
-static BlockNumber
-heap_parallelscan_nextpage(HeapScanDesc scan)
-{
-	BlockNumber page;
-	ParallelHeapScanDesc parallel_scan;
-	uint64		nallocated;
-
-	Assert(scan->rs_parallel);
-	parallel_scan = scan->rs_parallel;
-
-	/*
-	 * phs_nallocated tracks how many pages have been allocated to workers
-	 * already.  When phs_nallocated >= rs_nblocks, all blocks have been
-	 * allocated.
-	 *
-	 * Because we use an atomic fetch-and-add to fetch the current value, the
-	 * phs_nallocated counter will exceed rs_nblocks, because workers will
-	 * still increment the value, when they try to allocate the next block but
-	 * all blocks have been allocated already. The counter must be 64 bits
-	 * wide because of that, to avoid wrapping around when rs_nblocks is close
-	 * to 2^32.
-	 *
-	 * The actual page to return is calculated by adding the counter to the
-	 * starting block number, modulo nblocks.
-	 */
-	nallocated = pg_atomic_fetch_add_u64(&parallel_scan->phs_nallocated, 1);
-	if (nallocated >= scan->rs_nblocks)
-		page = InvalidBlockNumber;	/* all blocks have been allocated */
-	else
-		page = (nallocated + parallel_scan->phs_startblock) % scan->rs_nblocks;
-
-	/*
-	 * Report scan location.  Normally, we report the current page number.
-	 * When we reach the end of the scan, though, we report the starting page,
-	 * not the ending page, just so the starting positions for later scans
-	 * 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 (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);
-	}
-
-	return page;
-}
-
-/* ----------------
- *		heap_update_snapshot
- *
- *		Update snapshot info in heap scan descriptor.
- * ----------------
- */
-void
-heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
-{
-	Assert(IsMVCCSnapshot(snapshot));
-
-	RegisterSnapshot(snapshot);
-	scan->rs_snapshot = snapshot;
-	scan->rs_temp_snap = true;
-}
-
-/* ----------------
- *		heap_getnext	- retrieve next tuple in scan
- *
- *		Fix to work with index relations.
- *		We don't return the buffer anymore, but you can get it from the
- *		returned HeapTuple.
- * ----------------
- */
-
-#ifdef HEAPDEBUGALL
-#define HEAPDEBUG_1 \
-	elog(DEBUG2, "heap_getnext([%s,nkeys=%d],dir=%d) called", \
-		 RelationGetRelationName(scan->rs_rd), scan->rs_nkeys, (int) direction)
-#define HEAPDEBUG_2 \
-	elog(DEBUG2, "heap_getnext returning EOS")
-#define HEAPDEBUG_3 \
-	elog(DEBUG2, "heap_getnext returning tuple")
-#else
-#define HEAPDEBUG_1
-#define HEAPDEBUG_2
-#define HEAPDEBUG_3
-#endif							/* !defined(HEAPDEBUGALL) */
-
-
-HeapTuple
-heap_getnext(HeapScanDesc scan, ScanDirection direction)
-{
-	/* Note: no locking manipulations needed */
-
-	HEAPDEBUG_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)
-	{
-		HEAPDEBUG_2;			/* heap_getnext returning EOS */
-		return NULL;
-	}
-
-	/*
-	 * if we get here it means we have a new current scan tuple, so point to
-	 * the proper return buffer and return the tuple.
-	 */
-	HEAPDEBUG_3;				/* heap_getnext returning tuple */
-
-	pgstat_count_heap_getnext(scan->rs_rd);
-
-	return &(scan->rs_ctup);
-}
-
-/*
- *	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
-heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
-					   Snapshot snapshot, HeapTuple heapTuple,
-					   bool *all_dead, bool first_call)
-{
-	Page		dp = (Page) BufferGetPage(buffer);
-	TransactionId prev_xmax = InvalidTransactionId;
-	OffsetNumber offnum;
-	bool		at_chain_start;
-	bool		valid;
-	bool		skip;
-
-	/* If this is not the first call, previous call returned a (live!) tuple */
-	if (all_dead)
-		*all_dead = first_call;
-
-	Assert(TransactionIdIsValid(RecentGlobalXmin));
-
-	Assert(ItemPointerGetBlockNumber(tid) == BufferGetBlockNumber(buffer));
-	offnum = ItemPointerGetOffsetNumber(tid);
-	at_chain_start = first_call;
-	skip = !first_call;
-
-	heapTuple->t_self = *tid;
-
-	/* Scan through possible multiple members of HOT-chain */
-	for (;;)
-	{
-		ItemId		lp;
-
-		/* check for bogus TID */
-		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(dp))
-			break;
-
-		lp = PageGetItemId(dp, offnum);
-
-		/* check for unused, dead, or redirected items */
-		if (!ItemIdIsNormal(lp))
-		{
-			/* We should only see a redirect at start of chain */
-			if (ItemIdIsRedirected(lp) && at_chain_start)
-			{
-				/* Follow the redirect */
-				offnum = ItemIdGetRedirect(lp);
-				at_chain_start = false;
-				continue;
-			}
-			/* else must be end of chain */
-			break;
-		}
-
-		heapTuple->t_data = (HeapTupleHeader) PageGetItem(dp, lp);
-		heapTuple->t_len = ItemIdGetLength(lp);
-		heapTuple->t_tableOid = RelationGetRelid(relation);
-		ItemPointerSetOffsetNumber(&heapTuple->t_self, offnum);
-
-		/*
-		 * Shouldn't see a HEAP_ONLY tuple at chain start.
-		 */
-		if (at_chain_start && HeapTupleIsHeapOnly(heapTuple))
-			break;
-
-		/*
-		 * The xmin should match the previous xmax value, else chain is
-		 * broken.
-		 */
-		if (TransactionIdIsValid(prev_xmax) &&
-			!TransactionIdEquals(prev_xmax,
-								 HeapTupleHeaderGetXmin(heapTuple->t_data)))
-			break;
-
-		/*
-		 * When first_call is true (and thus, skip is initially false) we'll
-		 * return the first tuple we find.  But on later passes, heapTuple
-		 * will initially be pointing to the tuple we returned last time.
-		 * Returning it again would be incorrect (and would loop forever), so
-		 * we skip it and return the next match we find.
-		 */
-		if (!skip)
-		{
-			/*
-			 * For the benefit of logical decoding, have t_self point at the
-			 * element of the HOT chain we're currently investigating instead
-			 * of the root tuple of the HOT chain. This is important because
-			 * the *Satisfies routine for historical mvcc snapshots needs the
-			 * correct tid to decide about the visibility in some cases.
-			 */
-			ItemPointerSet(&(heapTuple->t_self), BufferGetBlockNumber(buffer), offnum);
-
-			/* If it's visible per the snapshot, we must return it */
-			valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, heapTuple, snapshot, buffer);
-			CheckForSerializableConflictOut(valid, relation, heapTuple,
-											buffer, snapshot);
-			/* reset to original, non-redirected, tid */
-			heapTuple->t_self = *tid;
-
-			if (valid)
-			{
-				ItemPointerSetOffsetNumber(tid, offnum);
-				PredicateLockTuple(relation, heapTuple, snapshot);
-				if (all_dead)
-					*all_dead = false;
-				return true;
-			}
-		}
-		skip = false;
-
-		/*
-		 * If we can't see it, maybe no one else can either.  At caller
-		 * request, check whether all chain members are dead to all
-		 * transactions.
-		 */
-		if (all_dead && *all_dead &&
-			!HeapTupleIsSurelyDead(heapTuple, RecentGlobalXmin))
-			*all_dead = false;
-
-		/*
-		 * Check to see if HOT chain continues past this tuple; if so fetch
-		 * the next offnum and loop around.
-		 */
-		if (HeapTupleIsHotUpdated(heapTuple))
-		{
-			Assert(ItemPointerGetBlockNumber(&heapTuple->t_data->t_ctid) ==
-				   ItemPointerGetBlockNumber(tid));
-			offnum = ItemPointerGetOffsetNumber(&heapTuple->t_data->t_ctid);
-			at_chain_start = false;
-			prev_xmax = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
-		}
-		else
-			break;				/* end of chain */
-	}
-
-	return false;
-}
-
-/*
- *	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
-heap_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 = heap_hot_search_buffer(tid, relation, buffer, snapshot,
-									&heapTuple, all_dead, true);
-	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-	ReleaseBuffer(buffer);
-	return result;
-}
 
 
 /*
@@ -4761,32 +3410,6 @@ heap_execute_freeze_tuple(HeapTupleHeader tuple, xl_heap_freeze_tuple *frz)
 	tuple->t_infomask2 = frz->t_infomask2;
 }
 
-/*
- * heap_freeze_tuple
- *		Freeze tuple in place, without WAL logging.
- *
- * Useful for callers like CLUSTER that perform their own WAL logging.
- */
-bool
-heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
-				  TransactionId cutoff_multi)
-{
-	xl_heap_freeze_tuple frz;
-	bool		do_freeze;
-	bool		tuple_totally_frozen;
-
-	do_freeze = heap_prepare_freeze_tuple(tuple, cutoff_xid, cutoff_multi,
-										  &frz, &tuple_totally_frozen);
-
-	/*
-	 * Note that because this is not a WAL-logged operation, we don't need to
-	 * fill in the offset in the freeze record.
-	 */
-
-	if (do_freeze)
-		heap_execute_freeze_tuple(tuple, &frz);
-	return do_freeze;
-}
 
 /*
  * For a given MultiXactId, return the hint bits that should be set in the
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index d08a99e..6d031d1 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -1640,6 +1640,1095 @@ HeapTupleSatisfiesHistoricMVCC(StorageTuple stup, Snapshot snapshot,
 		return true;
 }
 
+/* ----------------------------------------------------------------
+ *						 heap support routines
+ * ----------------------------------------------------------------
+ */
+
+/* ----------------
+ *		heap_parallelscan_startblock_init - find and set the scan's startblock
+ *
+ *		Determine where the parallel seq scan should start.  This function may
+ *		be called many times, once by each parallel worker.  We must be careful
+ *		only to set the startblock once.
+ * ----------------
+ */
+static void
+heap_parallelscan_startblock_init(HeapScanDesc scan)
+{
+	BlockNumber sync_startpage = InvalidBlockNumber;
+	ParallelHeapScanDesc parallel_scan;
+
+	Assert(scan->rs_parallel);
+	parallel_scan = scan->rs_parallel;
+
+retry:
+	/* Grab the spinlock. */
+	SpinLockAcquire(&parallel_scan->phs_mutex);
+
+	/*
+	 * If the scan's startblock has not yet been initialized, we must do so
+	 * now.  If this is not a synchronized scan, we just start at block 0, but
+	 * if it is a synchronized scan, we must get the starting position from
+	 * the synchronized scan machinery.  We can't hold the spinlock while
+	 * doing that, though, so release the spinlock, get the information we
+	 * need, and retry.  If nobody else has initialized the scan in the
+	 * meantime, we'll fill in the value we fetched on the second time
+	 * through.
+	 */
+	if (parallel_scan->phs_startblock == InvalidBlockNumber)
+	{
+		if (!parallel_scan->phs_syncscan)
+			parallel_scan->phs_startblock = 0;
+		else if (sync_startpage != InvalidBlockNumber)
+			parallel_scan->phs_startblock = sync_startpage;
+		else
+		{
+			SpinLockRelease(&parallel_scan->phs_mutex);
+			sync_startpage = ss_get_location(scan->rs_rd, scan->rs_nblocks);
+			goto retry;
+		}
+	}
+	SpinLockRelease(&parallel_scan->phs_mutex);
+}
+
+/* ----------------
+ *		heap_parallelscan_nextpage - get the next page to scan
+ *
+ *		Get the next page to scan.  Even if there are no pages left to scan,
+ *		another backend could have grabbed a page to scan and not yet finished
+ *		looking at it, so it doesn't follow that the scan is done when the
+ *		first backend gets an InvalidBlockNumber return.
+ * ----------------
+ */
+static BlockNumber
+heap_parallelscan_nextpage(HeapScanDesc scan)
+{
+	BlockNumber page;
+	ParallelHeapScanDesc parallel_scan;
+	uint64		nallocated;
+
+	Assert(scan->rs_parallel);
+	parallel_scan = scan->rs_parallel;
+
+	/*
+	 * phs_nallocated tracks how many pages have been allocated to workers
+	 * already.  When phs_nallocated >= rs_nblocks, all blocks have been
+	 * allocated.
+	 *
+	 * Because we use an atomic fetch-and-add to fetch the current value, the
+	 * phs_nallocated counter will exceed rs_nblocks, because workers will
+	 * still increment the value, when they try to allocate the next block but
+	 * all blocks have been allocated already. The counter must be 64 bits
+	 * wide because of that, to avoid wrapping around when rs_nblocks is close
+	 * to 2^32.
+	 *
+	 * The actual page to return is calculated by adding the counter to the
+	 * starting block number, modulo nblocks.
+	 */
+	nallocated = pg_atomic_fetch_add_u64(&parallel_scan->phs_nallocated, 1);
+	if (nallocated >= scan->rs_nblocks)
+		page = InvalidBlockNumber;	/* all blocks have been allocated */
+	else
+		page = (nallocated + parallel_scan->phs_startblock) % scan->rs_nblocks;
+
+	/*
+	 * Report scan location.  Normally, we report the current page number.
+	 * When we reach the end of the scan, though, we report the starting page,
+	 * not the ending page, just so the starting positions for later scans
+	 * 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 (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);
+	}
+
+	return page;
+}
+
+
+/* ----------------
+ *		initscan - scan code common to heap_beginscan and heap_rescan
+ * ----------------
+ */
+static void
+initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
+{
+	bool		allow_strat;
+	bool		allow_sync;
+
+	/*
+	 * Determine the number of blocks we have to scan.
+	 *
+	 * It is sufficient to do this once at scan start, since any tuples added
+	 * while the scan is in progress will be invisible to my snapshot anyway.
+	 * (That is not true when using a non-MVCC snapshot.  However, we couldn't
+	 * guarantee to return tuples added after scan start anyway, since they
+	 * might go into pages we already scanned.  To guarantee consistent
+	 * results for a non-MVCC snapshot, the caller must hold some higher-level
+	 * lock that ensures the interesting tuple(s) won't change.)
+	 */
+	if (scan->rs_parallel != NULL)
+		scan->rs_nblocks = scan->rs_parallel->phs_nblocks;
+	else
+		scan->rs_nblocks = RelationGetNumberOfBlocks(scan->rs_rd);
+
+	/*
+	 * If the table is large relative to NBuffers, use a bulk-read access
+	 * strategy and enable synchronized scanning (see syncscan.c).  Although
+	 * the thresholds for these features could be different, we make them the
+	 * same so that there are only two behaviors to tune rather than four.
+	 * (However, some callers need to be able to disable one or both of these
+	 * behaviors, independently of the size of the table; also there is a GUC
+	 * variable that can disable synchronized scanning.)
+	 *
+	 * 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)
+	{
+		allow_strat = scan->rs_allow_strat;
+		allow_sync = scan->rs_allow_sync;
+	}
+	else
+		allow_strat = allow_sync = false;
+
+	if (allow_strat)
+	{
+		/* During a rescan, keep the previous strategy object. */
+		if (scan->rs_strategy == NULL)
+			scan->rs_strategy = GetAccessStrategy(BAS_BULKREAD);
+	}
+	else
+	{
+		if (scan->rs_strategy != NULL)
+			FreeAccessStrategy(scan->rs_strategy);
+		scan->rs_strategy = NULL;
+	}
+
+	if (scan->rs_parallel != NULL)
+	{
+		/* For parallel scan, believe whatever ParallelHeapScanDesc says. */
+		scan->rs_syncscan = scan->rs_parallel->phs_syncscan;
+	}
+	else if (keep_startblock)
+	{
+		/*
+		 * When rescanning, we want to keep the previous startblock setting,
+		 * so that rewinding a cursor doesn't generate surprising results.
+		 * Reset the active syncscan setting, though.
+		 */
+		scan->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);
+	}
+	else
+	{
+		scan->rs_syncscan = false;
+		scan->rs_startblock = 0;
+	}
+
+	scan->rs_numblocks = InvalidBlockNumber;
+	scan->rs_inited = false;
+	scan->rs_ctup.t_data = NULL;
+	ItemPointerSetInvalid(&scan->rs_ctup.t_self);
+	scan->rs_cbuf = InvalidBuffer;
+	scan->rs_cblock = InvalidBlockNumber;
+
+	/* page-at-a-time fields are always invalid when not rs_inited */
+
+	/*
+	 * copy the scan key, if appropriate
+	 */
+	if (key != NULL)
+		memcpy(scan->rs_key, key, scan->rs_nkeys * sizeof(ScanKeyData));
+
+	/*
+	 * Currently, we don't have a stats counter for bitmap heap scans (but the
+	 * underlying bitmap index scans will be counted) or sample scans (we only
+	 * update stats for tuple fetches there)
+	 */
+	if (!scan->rs_bitmapscan && !scan->rs_samplescan)
+		pgstat_count_heap_scan(scan->rs_rd);
+}
+
+
+/* ----------------
+ *		heapgettup - fetch next heap tuple
+ *
+ *		Initialize the scan if not already done; then advance to the next
+ *		tuple as indicated by "dir"; return the next tuple in scan->rs_ctup,
+ *		or set scan->rs_ctup.t_data = NULL if no more tuples.
+ *
+ * dir == NoMovementScanDirection means "re-fetch the tuple indicated
+ * by scan->rs_ctup".
+ *
+ * Note: the reason nkeys/key are passed separately, even though they are
+ * kept in the scan descriptor, is that the caller may not want us to check
+ * the scankeys.
+ *
+ * Note: when we fall off the end of the scan in either direction, we
+ * reset rs_inited.  This means that a further request with the same
+ * scan direction will restart the scan, which is a bit odd, but a
+ * request with the opposite scan direction will start a fresh scan
+ * in the proper direction.  The latter is required behavior for cursors,
+ * while the former case is generally undefined behavior in Postgres
+ * so we don't care too much.
+ * ----------------
+ */
+static void
+heapgettup(HeapScanDesc scan,
+		   ScanDirection dir,
+		   int nkeys,
+		   ScanKey key)
+{
+	HeapTuple	tuple = &(scan->rs_ctup);
+	Snapshot	snapshot = scan->rs_snapshot;
+	bool		backward = ScanDirectionIsBackward(dir);
+	BlockNumber page;
+	bool		finished;
+	Page		dp;
+	int			lines;
+	OffsetNumber lineoff;
+	int			linesleft;
+	ItemId		lpp;
+
+	/*
+	 * calculate next starting lineoff, given scan direction
+	 */
+	if (ScanDirectionIsForward(dir))
+	{
+		if (!scan->rs_inited)
+		{
+			/*
+			 * return null immediately if relation is empty
+			 */
+			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			{
+				Assert(!BufferIsValid(scan->rs_cbuf));
+				tuple->t_data = NULL;
+				return;
+			}
+			if (scan->rs_parallel != NULL)
+			{
+				heap_parallelscan_startblock_init(scan);
+
+				page = heap_parallelscan_nextpage(scan);
+
+				/* Other processes might have already finished the scan. */
+				if (page == InvalidBlockNumber)
+				{
+					Assert(!BufferIsValid(scan->rs_cbuf));
+					tuple->t_data = NULL;
+					return;
+				}
+			}
+			else
+				page = scan->rs_startblock; /* first page */
+			heapgetpage(scan, page);
+			lineoff = FirstOffsetNumber;	/* first offnum */
+			scan->rs_inited = true;
+		}
+		else
+		{
+			/* continue from previously returned page/tuple */
+			page = scan->rs_cblock; /* current page */
+			lineoff =			/* next offnum */
+				OffsetNumberNext(ItemPointerGetOffsetNumber(&(tuple->t_self)));
+		}
+
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		lines = PageGetMaxOffsetNumber(dp);
+		/* page and lineoff now reference the physically next tid */
+
+		linesleft = lines - lineoff + 1;
+	}
+	else if (backward)
+	{
+		/* backward parallel scan not supported */
+		Assert(scan->rs_parallel == NULL);
+
+		if (!scan->rs_inited)
+		{
+			/*
+			 * return null immediately if relation is empty
+			 */
+			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			{
+				Assert(!BufferIsValid(scan->rs_cbuf));
+				tuple->t_data = NULL;
+				return;
+			}
+
+			/*
+			 * Disable reporting to syncscan logic in a backwards scan; it's
+			 * not very likely anyone else is doing the same thing at the same
+			 * time, and much more likely that we'll just bollix things for
+			 * forward scanners.
+			 */
+			scan->rs_syncscan = false;
+			/* start from last page of the scan */
+			if (scan->rs_startblock > 0)
+				page = scan->rs_startblock - 1;
+			else
+				page = scan->rs_nblocks - 1;
+			heapgetpage(scan, page);
+		}
+		else
+		{
+			/* continue from previously returned page/tuple */
+			page = scan->rs_cblock; /* current page */
+		}
+
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		lines = PageGetMaxOffsetNumber(dp);
+
+		if (!scan->rs_inited)
+		{
+			lineoff = lines;	/* final offnum */
+			scan->rs_inited = true;
+		}
+		else
+		{
+			lineoff =			/* previous offnum */
+				OffsetNumberPrev(ItemPointerGetOffsetNumber(&(tuple->t_self)));
+		}
+		/* page and lineoff now reference the physically previous tid */
+
+		linesleft = lineoff;
+	}
+	else
+	{
+		/*
+		 * ``no movement'' scan direction: refetch prior tuple
+		 */
+		if (!scan->rs_inited)
+		{
+			Assert(!BufferIsValid(scan->rs_cbuf));
+			tuple->t_data = NULL;
+			return;
+		}
+
+		page = ItemPointerGetBlockNumber(&(tuple->t_self));
+		if (page != scan->rs_cblock)
+			heapgetpage(scan, page);
+
+		/* Since the tuple was previously fetched, needn't lock page here */
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
+		lpp = PageGetItemId(dp, lineoff);
+		Assert(ItemIdIsNormal(lpp));
+
+		tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+		tuple->t_len = ItemIdGetLength(lpp);
+
+		return;
+	}
+
+	/*
+	 * advance the scan until we find a qualifying tuple or run out of stuff
+	 * to scan
+	 */
+	lpp = PageGetItemId(dp, lineoff);
+	for (;;)
+	{
+		while (linesleft > 0)
+		{
+			if (ItemIdIsNormal(lpp))
+			{
+				bool		valid;
+
+				tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+				tuple->t_len = ItemIdGetLength(lpp);
+				ItemPointerSet(&(tuple->t_self), page, lineoff);
+
+				/*
+				 * if current tuple qualifies, return it.
+				 */
+				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine,
+													 tuple,
+													 snapshot,
+													 scan->rs_cbuf);
+
+				CheckForSerializableConflictOut(valid, scan->rs_rd, tuple,
+												scan->rs_cbuf, snapshot);
+
+				if (valid && key != NULL)
+					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
+								nkeys, key, valid);
+
+				if (valid)
+				{
+					LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+					return;
+				}
+			}
+
+			/*
+			 * otherwise move to the next item on the page
+			 */
+			--linesleft;
+			if (backward)
+			{
+				--lpp;			/* move back in this page's ItemId array */
+				--lineoff;
+			}
+			else
+			{
+				++lpp;			/* move forward in this page's ItemId array */
+				++lineoff;
+			}
+		}
+
+		/*
+		 * 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);
+
+		/*
+		 * 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);
+			if (page == 0)
+				page = scan->rs_nblocks;
+			page--;
+		}
+		else if (scan->rs_parallel != NULL)
+		{
+			page = heap_parallelscan_nextpage(scan);
+			finished = (page == InvalidBlockNumber);
+		}
+		else
+		{
+			page++;
+			if (page >= scan->rs_nblocks)
+				page = 0;
+			finished = (page == scan->rs_startblock) ||
+				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+
+			/*
+			 * Report our new scan position for synchronization purposes. We
+			 * don't do that when moving backwards, however. That would just
+			 * mess up any other forward-moving scanners.
+			 *
+			 * Note: we do this before checking for end of scan so that the
+			 * final state of the position hint is back at the start of the
+			 * rel.  That's not strictly necessary, but otherwise when you run
+			 * the same query multiple times the starting position would shift
+			 * 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);
+		}
+
+		/*
+		 * return NULL if we've exhausted all the pages
+		 */
+		if (finished)
+		{
+			if (BufferIsValid(scan->rs_cbuf))
+				ReleaseBuffer(scan->rs_cbuf);
+			scan->rs_cbuf = InvalidBuffer;
+			scan->rs_cblock = InvalidBlockNumber;
+			tuple->t_data = NULL;
+			scan->rs_inited = false;
+			return;
+		}
+
+		heapgetpage(scan, page);
+
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		lines = PageGetMaxOffsetNumber((Page) dp);
+		linesleft = lines;
+		if (backward)
+		{
+			lineoff = lines;
+			lpp = PageGetItemId(dp, lines);
+		}
+		else
+		{
+			lineoff = FirstOffsetNumber;
+			lpp = PageGetItemId(dp, FirstOffsetNumber);
+		}
+	}
+}
+
+/* ----------------
+ *		heapgettup_pagemode - fetch next heap tuple in page-at-a-time mode
+ *
+ *		Same API as heapgettup, but used in page-at-a-time mode
+ *
+ * The internal logic is much the same as heapgettup's too, but there are some
+ * differences: we do not take the buffer content lock (that only needs to
+ * happen inside heapgetpage), and we iterate through just the tuples listed
+ * in rs_vistuples[] rather than all tuples on the page.  Notice that
+ * lineindex is 0-based, where the corresponding loop variable lineoff in
+ * heapgettup is 1-based.
+ * ----------------
+ */
+static void
+heapgettup_pagemode(HeapScanDesc scan,
+					ScanDirection dir,
+					int nkeys,
+					ScanKey key)
+{
+	HeapTuple	tuple = &(scan->rs_ctup);
+	bool		backward = ScanDirectionIsBackward(dir);
+	BlockNumber page;
+	bool		finished;
+	Page		dp;
+	int			lines;
+	int			lineindex;
+	OffsetNumber lineoff;
+	int			linesleft;
+	ItemId		lpp;
+
+	/*
+	 * calculate next starting lineindex, given scan direction
+	 */
+	if (ScanDirectionIsForward(dir))
+	{
+		if (!scan->rs_inited)
+		{
+			/*
+			 * return null immediately if relation is empty
+			 */
+			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			{
+				Assert(!BufferIsValid(scan->rs_cbuf));
+				tuple->t_data = NULL;
+				return;
+			}
+			if (scan->rs_parallel != NULL)
+			{
+				heap_parallelscan_startblock_init(scan);
+
+				page = heap_parallelscan_nextpage(scan);
+
+				/* Other processes might have already finished the scan. */
+				if (page == InvalidBlockNumber)
+				{
+					Assert(!BufferIsValid(scan->rs_cbuf));
+					tuple->t_data = NULL;
+					return;
+				}
+			}
+			else
+				page = scan->rs_startblock; /* first page */
+			heapgetpage(scan, page);
+			lineindex = 0;
+			scan->rs_inited = true;
+		}
+		else
+		{
+			/* continue from previously returned page/tuple */
+			page = scan->rs_cblock; /* current page */
+			lineindex = scan->rs_cindex + 1;
+		}
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
+		lines = scan->rs_ntuples;
+		/* page and lineindex now reference the next visible tid */
+
+		linesleft = lines - lineindex;
+	}
+	else if (backward)
+	{
+		/* backward parallel scan not supported */
+		Assert(scan->rs_parallel == NULL);
+
+		if (!scan->rs_inited)
+		{
+			/*
+			 * return null immediately if relation is empty
+			 */
+			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			{
+				Assert(!BufferIsValid(scan->rs_cbuf));
+				tuple->t_data = NULL;
+				return;
+			}
+
+			/*
+			 * Disable reporting to syncscan logic in a backwards scan; it's
+			 * not very likely anyone else is doing the same thing at the same
+			 * time, and much more likely that we'll just bollix things for
+			 * forward scanners.
+			 */
+			scan->rs_syncscan = false;
+			/* start from last page of the scan */
+			if (scan->rs_startblock > 0)
+				page = scan->rs_startblock - 1;
+			else
+				page = scan->rs_nblocks - 1;
+			heapgetpage(scan, page);
+		}
+		else
+		{
+			/* continue from previously returned page/tuple */
+			page = scan->rs_cblock; /* current page */
+		}
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
+		lines = scan->rs_ntuples;
+
+		if (!scan->rs_inited)
+		{
+			lineindex = lines - 1;
+			scan->rs_inited = true;
+		}
+		else
+		{
+			lineindex = scan->rs_cindex - 1;
+		}
+		/* page and lineindex now reference the previous visible tid */
+
+		linesleft = lineindex + 1;
+	}
+	else
+	{
+		/*
+		 * ``no movement'' scan direction: refetch prior tuple
+		 */
+		if (!scan->rs_inited)
+		{
+			Assert(!BufferIsValid(scan->rs_cbuf));
+			tuple->t_data = NULL;
+			return;
+		}
+
+		page = ItemPointerGetBlockNumber(&(tuple->t_self));
+		if (page != scan->rs_cblock)
+			heapgetpage(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);
+		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
+		lpp = PageGetItemId(dp, lineoff);
+		Assert(ItemIdIsNormal(lpp));
+
+		tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+		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]);
+
+		return;
+	}
+
+	/*
+	 * advance the scan until we find a qualifying tuple or run out of stuff
+	 * to scan
+	 */
+	for (;;)
+	{
+		while (linesleft > 0)
+		{
+			lineoff = scan->rs_vistuples[lineindex];
+			lpp = PageGetItemId(dp, lineoff);
+			Assert(ItemIdIsNormal(lpp));
+
+			tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+			tuple->t_len = ItemIdGetLength(lpp);
+			ItemPointerSet(&(tuple->t_self), page, lineoff);
+
+			/*
+			 * if current tuple qualifies, return it.
+			 */
+			if (key != NULL)
+			{
+				bool		valid;
+
+				HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
+							nkeys, key, valid);
+				if (valid)
+				{
+					scan->rs_cindex = lineindex;
+					return;
+				}
+			}
+			else
+			{
+				scan->rs_cindex = lineindex;
+				return;
+			}
+
+			/*
+			 * otherwise move to the next item on the page
+			 */
+			--linesleft;
+			if (backward)
+				--lineindex;
+			else
+				++lineindex;
+		}
+
+		/*
+		 * if we get here, it means we've exhausted the items on this page and
+		 * it's time to move to the next.
+		 */
+		if (backward)
+		{
+			finished = (page == scan->rs_startblock) ||
+				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+			if (page == 0)
+				page = scan->rs_nblocks;
+			page--;
+		}
+		else if (scan->rs_parallel != NULL)
+		{
+			page = heap_parallelscan_nextpage(scan);
+			finished = (page == InvalidBlockNumber);
+		}
+		else
+		{
+			page++;
+			if (page >= scan->rs_nblocks)
+				page = 0;
+			finished = (page == scan->rs_startblock) ||
+				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+
+			/*
+			 * Report our new scan position for synchronization purposes. We
+			 * don't do that when moving backwards, however. That would just
+			 * mess up any other forward-moving scanners.
+			 *
+			 * Note: we do this before checking for end of scan so that the
+			 * final state of the position hint is back at the start of the
+			 * rel.  That's not strictly necessary, but otherwise when you run
+			 * the same query multiple times the starting position would shift
+			 * 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);
+		}
+
+		/*
+		 * return NULL if we've exhausted all the pages
+		 */
+		if (finished)
+		{
+			if (BufferIsValid(scan->rs_cbuf))
+				ReleaseBuffer(scan->rs_cbuf);
+			scan->rs_cbuf = InvalidBuffer;
+			scan->rs_cblock = InvalidBlockNumber;
+			tuple->t_data = NULL;
+			scan->rs_inited = false;
+			return;
+		}
+
+		heapgetpage(scan, page);
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
+		lines = scan->rs_ntuples;
+		linesleft = lines;
+		if (backward)
+			lineindex = lines - 1;
+		else
+			lineindex = 0;
+	}
+}
+
+
+static HeapScanDesc
+heapam_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)
+{
+	HeapScanDesc scan;
+
+	/*
+	 * increment relation ref count while scanning relation
+	 *
+	 * This is just to make really sure the relcache entry won't go away while
+	 * the scan has a pointer to it.  Caller should be holding the rel open
+	 * anyway, so this is redundant in all normal scenarios...
+	 */
+	RelationIncrementReferenceCount(relation);
+
+	/*
+	 * allocate and initialize scan descriptor
+	 */
+	scan = (HeapScanDesc) palloc(sizeof(HeapScanDescData));
+
+	scan->rs_rd = relation;
+	scan->rs_snapshot = snapshot;
+	scan->rs_nkeys = nkeys;
+	scan->rs_bitmapscan = is_bitmapscan;
+	scan->rs_samplescan = is_samplescan;
+	scan->rs_strategy = NULL;	/* set in initscan */
+	scan->rs_allow_strat = allow_strat;
+	scan->rs_allow_sync = allow_sync;
+	scan->rs_temp_snap = temp_snap;
+	scan->rs_parallel = parallel_scan;
+
+	/*
+	 * we can use page-at-a-time mode if it's an MVCC-safe snapshot
+	 */
+	scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(snapshot);
+
+	/*
+	 * For a seqscan in a serializable transaction, acquire a predicate lock
+	 * on the entire relation. This is required not only to lock all the
+	 * matching tuples, but also to conflict with new insertions into the
+	 * table. In an indexscan, we take page locks on the index pages covering
+	 * the range specified in the scan qual, but in a heap scan there is
+	 * nothing more fine-grained to lock. A bitmap scan is a different story,
+	 * there we have already scanned the index and locked the index pages
+	 * covering the predicate. But in that case we still have to lock any
+	 * matching heap tuples.
+	 */
+	if (!is_bitmapscan)
+		PredicateLockRelation(relation, snapshot);
+
+	/* we only need to set this up once */
+	scan->rs_ctup.t_tableOid = RelationGetRelid(relation);
+
+	/*
+	 * we do this here instead of in initscan() because heap_rescan also calls
+	 * initscan() and we don't want to allocate memory again
+	 */
+	if (nkeys > 0)
+		scan->rs_key = (ScanKey) palloc(sizeof(ScanKeyData) * nkeys);
+	else
+		scan->rs_key = NULL;
+
+	initscan(scan, key, false);
+
+	return scan;
+}
+
+/* ----------------
+ *		heapam_rescan		- restart a relation scan
+ * ----------------
+ */
+static void
+heapam_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
+	 */
+	if (BufferIsValid(scan->rs_cbuf))
+		ReleaseBuffer(scan->rs_cbuf);
+
+	/*
+	 * reinitialize scan descriptor
+	 */
+	initscan(scan, key, true);
+
+	/*
+	 * 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);
+	}
+}
+
+/* ----------------
+ *		heapam_endscan	- end relation scan
+ *
+ *		See how to integrate with index scans.
+ *		Check handling if reldesc caching.
+ * ----------------
+ */
+static void
+heapam_endscan(HeapScanDesc scan)
+{
+	/* Note: no locking manipulations needed */
+
+	/*
+	 * unpin scan buffers
+	 */
+	if (BufferIsValid(scan->rs_cbuf))
+		ReleaseBuffer(scan->rs_cbuf);
+
+	/*
+	 * decrement relation reference count and free scan descriptor storage
+	 */
+	RelationDecrementReferenceCount(scan->rs_rd);
+
+	if (scan->rs_key)
+		pfree(scan->rs_key);
+
+	if (scan->rs_strategy != NULL)
+		FreeAccessStrategy(scan->rs_strategy);
+
+	if (scan->rs_temp_snap)
+		UnregisterSnapshot(scan->rs_snapshot);
+
+	pfree(scan);
+}
+
+/* ----------------
+ *		heapam_scan_update_snapshot
+ *
+ *		Update snapshot info in heap scan descriptor.
+ * ----------------
+ */
+static void
+heapam_scan_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
+{
+	Assert(IsMVCCSnapshot(snapshot));
+
+	RegisterSnapshot(snapshot);
+	scan->rs_snapshot = snapshot;
+	scan->rs_temp_snap = true;
+}
+
+/* ----------------
+ *		heapam_getnext	- retrieve next tuple in scan
+ *
+ *		Fix to work with index relations.
+ *		We don't return the buffer anymore, but you can get it from the
+ *		returned HeapTuple.
+ * ----------------
+ */
+
+#ifdef HEAPAMDEBUGALL
+#define HEAPAMDEBUG_1 \
+	elog(DEBUG2, "heapam_getnext([%s,nkeys=%d],dir=%d) called", \
+		 RelationGetRelationName(scan->rs_rd), scan->rs_nkeys, (int) direction)
+#define HEAPAMDEBUG_2 \
+	elog(DEBUG2, "heapam_getnext returning EOS")
+#define HEAPAMDEBUG_3 \
+	elog(DEBUG2, "heapam_getnext returning tuple")
+#else
+#define HEAPAMDEBUG_1
+#define HEAPAMDEBUG_2
+#define HEAPAMDEBUG_3
+#endif							/* !defined(HEAPDEBUGALL) */
+
+
+static StorageTuple
+heapam_getnext(HeapScanDesc sscan, ScanDirection direction)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	/* Note: no locking manipulations needed */
+
+	HEAPAMDEBUG_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)
+	{
+		HEAPAMDEBUG_2;			/* heap_getnext returning EOS */
+		return NULL;
+	}
+
+	/*
+	 * if we get here it means we have a new current scan tuple, so point to
+	 * the proper return buffer and return the tuple.
+	 */
+	HEAPAMDEBUG_3;				/* heap_getnext returning tuple */
+
+	pgstat_count_heap_getnext(scan->rs_rd);
+
+	return &(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
+
+static TupleTableSlot *
+heapam_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);
+}
+
 
 /*
  *	heapam_fetch		- retrieve tuple with given tid
@@ -1796,7 +2885,210 @@ heapam_fetch(Relation relation,
 	return false;
 }
 
+/*
+ *	heapam_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.
+ */
+static bool
+heapam_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
+					   Snapshot snapshot, HeapTuple heapTuple,
+					   bool *all_dead, bool first_call)
+{
+	Page		dp = (Page) BufferGetPage(buffer);
+	TransactionId prev_xmax = InvalidTransactionId;
+	OffsetNumber offnum;
+	bool		at_chain_start;
+	bool		valid;
+	bool		skip;
+
+	/* If this is not the first call, previous call returned a (live!) tuple */
+	if (all_dead)
+		*all_dead = first_call;
+
+	Assert(TransactionIdIsValid(RecentGlobalXmin));
+
+	Assert(ItemPointerGetBlockNumber(tid) == BufferGetBlockNumber(buffer));
+	offnum = ItemPointerGetOffsetNumber(tid);
+	at_chain_start = first_call;
+	skip = !first_call;
+
+	heapTuple->t_self = *tid;
+
+	/* Scan through possible multiple members of HOT-chain */
+	for (;;)
+	{
+		ItemId		lp;
+
+		/* check for bogus TID */
+		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(dp))
+			break;
+
+		lp = PageGetItemId(dp, offnum);
+
+		/* check for unused, dead, or redirected items */
+		if (!ItemIdIsNormal(lp))
+		{
+			/* We should only see a redirect at start of chain */
+			if (ItemIdIsRedirected(lp) && at_chain_start)
+			{
+				/* Follow the redirect */
+				offnum = ItemIdGetRedirect(lp);
+				at_chain_start = false;
+				continue;
+			}
+			/* else must be end of chain */
+			break;
+		}
+
+		heapTuple->t_data = (HeapTupleHeader) PageGetItem(dp, lp);
+		heapTuple->t_len = ItemIdGetLength(lp);
+		heapTuple->t_tableOid = RelationGetRelid(relation);
+		ItemPointerSetOffsetNumber(&heapTuple->t_self, offnum);
+
+		/*
+		 * Shouldn't see a HEAP_ONLY tuple at chain start.
+		 */
+		if (at_chain_start && HeapTupleIsHeapOnly(heapTuple))
+			break;
+
+		/*
+		 * The xmin should match the previous xmax value, else chain is
+		 * broken.
+		 */
+		if (TransactionIdIsValid(prev_xmax) &&
+			!TransactionIdEquals(prev_xmax,
+								 HeapTupleHeaderGetXmin(heapTuple->t_data)))
+			break;
+
+		/*
+		 * When first_call is true (and thus, skip is initially false) we'll
+		 * return the first tuple we find.  But on later passes, heapTuple
+		 * will initially be pointing to the tuple we returned last time.
+		 * Returning it again would be incorrect (and would loop forever), so
+		 * we skip it and return the next match we find.
+		 */
+		if (!skip)
+		{
+			/*
+			 * For the benefit of logical decoding, have t_self point at the
+			 * element of the HOT chain we're currently investigating instead
+			 * of the root tuple of the HOT chain. This is important because
+			 * the *Satisfies routine for historical mvcc snapshots needs the
+			 * correct tid to decide about the visibility in some cases.
+			 */
+			ItemPointerSet(&(heapTuple->t_self), BufferGetBlockNumber(buffer), offnum);
+
+			/* If it's visible per the snapshot, we must return it */
+			valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, heapTuple, snapshot, buffer);
+			CheckForSerializableConflictOut(valid, relation, heapTuple,
+											buffer, snapshot);
+			/* reset to original, non-redirected, tid */
+			heapTuple->t_self = *tid;
+
+			if (valid)
+			{
+				ItemPointerSetOffsetNumber(tid, offnum);
+				PredicateLockTuple(relation, heapTuple, snapshot);
+				if (all_dead)
+					*all_dead = false;
+				return true;
+			}
+		}
+		skip = false;
+
+		/*
+		 * If we can't see it, maybe no one else can either.  At caller
+		 * request, check whether all chain members are dead to all
+		 * transactions.
+		 */
+		if (all_dead && *all_dead &&
+			!HeapTupleIsSurelyDead(heapTuple, RecentGlobalXmin))
+			*all_dead = false;
+
+		/*
+		 * Check to see if HOT chain continues past this tuple; if so fetch
+		 * the next offnum and loop around.
+		 */
+		if (HeapTupleIsHotUpdated(heapTuple))
+		{
+			Assert(ItemPointerGetBlockNumber(&heapTuple->t_data->t_ctid) ==
+				   ItemPointerGetBlockNumber(tid));
+			offnum = ItemPointerGetOffsetNumber(&heapTuple->t_data->t_ctid);
+			at_chain_start = false;
+			prev_xmax = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
+		}
+		else
+			break;				/* end of chain */
+	}
+
+	return false;
+}
+
+/*
+ * heapam_setscanlimits - restrict range of a heapscan
+ *
+ * startBlk is the page to start at
+ * numBlks is number of pages to scan (InvalidBlockNumber means "all")
+ */
+static void
+heapam_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	Assert(!scan->rs_inited);	/* else too late to change */
+	Assert(!scan->rs_syncscan); /* else rs_startblock is significant */
+
+	/* Check startBlk is valid (but allow case of zero blocks...) */
+	Assert(startBlk == 0 || startBlk < scan->rs_nblocks);
+
+	scan->rs_startblock = startBlk;
+	scan->rs_numblocks = numBlks;
+}
+
+
+/*
+ * heapam_freeze_tuple
+ *		Freeze tuple in place, without WAL logging.
+ *
+ * Useful for callers like CLUSTER that perform their own WAL logging.
+ */
+static bool
+heapam_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
+				  TransactionId cutoff_multi)
+{
+	xl_heap_freeze_tuple frz;
+	bool		do_freeze;
+	bool		tuple_totally_frozen;
+
+	do_freeze = heap_prepare_freeze_tuple(tuple, cutoff_xid, cutoff_multi,
+										  &frz, &tuple_totally_frozen);
+
+	/*
+	 * Note that because this is not a WAL-logged operation, we don't need to
+	 * fill in the offset in the freeze record.
+	 */
 
+	if (do_freeze)
+		heap_execute_freeze_tuple(tuple, &frz);
+	return do_freeze;
+}
 
 
 
@@ -3762,6 +5054,17 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 {
 	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
 
+	amroutine->scan_begin = heapam_beginscan;
+	amroutine->scansetlimits = heapam_setscanlimits;
+	amroutine->scan_getnext = heapam_getnext;
+	amroutine->scan_getnextslot = heapam_getnextslot;
+	amroutine->scan_end = heapam_endscan;
+	amroutine->scan_rescan = heapam_rescan;
+	amroutine->scan_update_snapshot = heapam_scan_update_snapshot;
+	amroutine->tuple_freeze = heapam_freeze_tuple;
+	amroutine->hot_search_buffer = heapam_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/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 8fba61c..a475a85 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -409,7 +409,7 @@ rewrite_heap_tuple(RewriteState state,
 	 * While we have our hands on the tuple, we may as well freeze any
 	 * eligible xmin or xmax, so that future VACUUM effort can be saved.
 	 */
-	heap_freeze_tuple(new_tuple->t_data, state->rs_freeze_xid,
+	storage_freeze_tuple(state->rs_new_rel, new_tuple->t_data, state->rs_freeze_xid,
 					  state->rs_cutoff_multi);
 
 	/*
diff --git a/src/backend/access/heap/storageam.c b/src/backend/access/heap/storageam.c
index f8e7a4f..53b3b3c 100644
--- a/src/backend/access/heap/storageam.c
+++ b/src/backend/access/heap/storageam.c
@@ -48,6 +48,174 @@
 #include "utils/tqual.h"
 
 
+/* ----------------
+ *		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);
+}
+
 /*
  *	storage_fetch		- retrieve tuple with given tid
  *
@@ -99,6 +267,73 @@ storage_fetch(Relation relation,
 							userbuf, keep_buf, stats_relation);
 }
 
+/*
+ *	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;
+}
+
+/*
+ * heap_freeze_tuple
+ *		Freeze tuple in place, without WAL logging.
+ *
+ * Useful for callers like CLUSTER that perform their own WAL logging.
+ */
+bool
+storage_freeze_tuple(Relation rel, HeapTupleHeader tuple, TransactionId cutoff_xid,
+				  TransactionId cutoff_multi)
+{
+	return rel->rd_stamroutine->tuple_freeze(tuple, cutoff_xid, cutoff_multi);
+}
+
 
 /*
  *	storage_lock_tuple - lock a tuple in shared or exclusive mode
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 01321a2..db5c93b 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,7 +395,7 @@ 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,
+		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 bef4255..349a127 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,7 +606,7 @@ 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,
+	got_heap_tuple = storage_hot_search_buffer(tid, scan->heapRelation,
 											scan->xs_cbuf,
 											scan->xs_snapshot,
 											&scan->xs_ctup,
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index bf963fc..0e25e9a 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,7 +326,7 @@ _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,
+				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/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 0453fd4..975cd5b 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 "bootstrap/bootstrap.h"
 #include "catalog/index.h"
@@ -573,18 +574,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,
@@ -592,7 +593,7 @@ boot_openrel(char *relname)
 				   sizeof((*app)->am_typ));
 			app++;
 		}
-		heap_endscan(scan);
+		storage_endscan(scan);
 		heap_close(rel, NoLock);
 	}
 
@@ -903,25 +904,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 ccde66a..d2a8a06 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"
@@ -797,14 +798,14 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 								ObjectIdGetDatum(namespaceId));
 
 					rel = heap_open(ProcedureRelationId, AccessShareLock);
-					scan = heap_beginscan_catalog(rel, 1, key);
+					scan = storage_beginscan_catalog(rel, 1, 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;
@@ -842,14 +843,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 0240df7..68c46f7 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"
@@ -1904,10 +1905,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
 	{
@@ -2279,7 +2280,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	}
 
     method = heapRelation->rd_stamroutine;
-	scan = heap_beginscan_strat(heapRelation,	/* relation */
+	scan = storage_beginscan_strat(heapRelation,	/* relation */
 								snapshot,	/* snapshot */
 								0,	/* number of keys */
 								NULL,	/* scan key */
@@ -2288,7 +2289,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 
 	/* 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 */
@@ -2301,7 +2302,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;
 
@@ -2613,7 +2614,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		}
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	/* we can now forget our snapshot, if set */
 	if (IsBootstrapProcessingMode() || indexInfo->ii_Concurrent)
@@ -2684,14 +2685,14 @@ IndexCheckExclusion(Relation heapRelation,
 	 * Scan all live tuples in the base relation.
 	 */
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = heap_beginscan_strat(heapRelation,	/* relation */
+	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 = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((heapTuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		CHECK_FOR_INTERRUPTS();
 
@@ -2727,7 +2728,7 @@ IndexCheckExclusion(Relation heapRelation,
 								   estate, true);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 
 	ExecDropSingleTupleTableSlot(slot);
@@ -3004,7 +3005,7 @@ 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 */
+	scan = storage_beginscan_strat(heapRelation,	/* relation */
 								snapshot,	/* snapshot */
 								0,	/* number of keys */
 								NULL,	/* scan key */
@@ -3014,7 +3015,7 @@ validate_index_heapscan(Relation heapRelation,
 	/*
 	 * 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;
@@ -3171,7 +3172,7 @@ validate_index_heapscan(Relation heapRelation,
 		}
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	ExecDropSingleTupleTableSlot(slot);
 
diff --git a/src/backend/catalog/pg_conversion.c b/src/backend/catalog/pg_conversion.c
index 5746dc3..1d048e6 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 323471b..517e310 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 3ef7ba8..145e3c1 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 fb53d71..a51f2e4 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 dbcc5bc..e0f6973 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 e2544e5..6727d15 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 c81ddf5..00e71e3 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2028,10 +2028,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();
 
@@ -2043,7 +2043,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 e138539..39850b1 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 b61aaac..46bc3da 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"
@@ -1948,8 +1949,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);
@@ -1989,7 +1990,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 d46e2e6..c3e90bd 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4496,7 +4496,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
@@ -4504,7 +4504,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)
 			{
@@ -4610,7 +4610,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 
 		MemoryContextSwitchTo(oldCxt);
-		heap_endscan(scan);
+		storage_endscan(scan);
 		UnregisterSnapshot(snapshot);
 
 		ExecDropSingleTupleTableSlot(oldslot);
@@ -5013,9 +5013,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,
@@ -5027,7 +5027,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;
@@ -8161,7 +8161,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
@@ -8169,7 +8169,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);
 
@@ -8184,7 +8184,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	}
 
 	MemoryContextSwitchTo(oldcxt);
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 	ExecDropSingleTupleTableSlot(slot);
 	FreeExecutorState(estate);
@@ -8239,9 +8239,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;
@@ -8270,7 +8270,7 @@ validateForeignKeyConstraint(char *conname,
 		RI_FKey_check_ins(&fcinfo);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 }
 
@@ -10720,8 +10720,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;
@@ -10780,7 +10780,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 8559c3b..cdfa8ff 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/trigger.c b/src/backend/commands/trigger.c
index d2438ce..fbbdbc2 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -15,8 +15,9 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
-#include "access/sysattr.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
+#include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 7ed16ae..c07f508 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"
@@ -2315,8 +2316,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;
 
@@ -2345,7 +2346,7 @@ AlterDomainNotNull(List *names, bool notNull)
 					}
 				}
 			}
-			heap_endscan(scan);
+			storage_endscan(scan);
 			UnregisterSnapshot(snapshot);
 
 			/* Close each rel after processing, but keep lock */
@@ -2711,8 +2712,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;
 
@@ -2755,7 +2756,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 faa1812..e24ac9f 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"
@@ -447,9 +448,9 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
 
 		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);
 
@@ -469,7 +470,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
 			MemoryContextSwitchTo(oldcontext);
 		}
 
-		heap_endscan(scan);
+		storage_endscan(scan);
 		heap_close(pgclass, AccessShareLock);
 	}
 
@@ -1121,9 +1122,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;
@@ -1160,7 +1161,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 f1636a5..6ade9df 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 89e189f..5e9daea 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 a1c2319..dd425d4 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 an index 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)) != NULL)
 	{
-		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 b683ac6..d3d0f1b 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -681,7 +681,7 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 Datum
 ExecFetchSlotTupleDatum(TupleTableSlot *slot)
 {
-	HeapTuple	tup;
+	StorageTuple	tup;
 	TupleDesc	tupdesc;
 
 	/* Fetch slot's contents in regular-physical-tuple form */
@@ -765,7 +765,7 @@ ExecHeapifySlot(TupleTableSlot *slot)
 TupleTableSlot *
 ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
 {
-	HeapTuple	newTuple;
+	StorageTuple	newTuple;
 	MemoryContext oldContext;
 
 	/*
@@ -1085,7 +1085,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;
@@ -1093,7 +1093,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 b7ac5f7..db6b5ad 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)
 {
@@ -597,7 +597,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 0ae5873..d94169c 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -3097,7 +3097,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,
@@ -3212,7 +3212,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 02e8f3d..d4f46cb 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 "executor/execdebug.h"
 #include "executor/nodeBitmapHeapscan.h"
@@ -400,7 +401,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 			HeapTupleData heapTuple;
 
 			ItemPointerSet(&tid, page, offnum);
-			if (heap_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
+			if (storage_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
 									   &heapTuple, NULL, true))
 				scan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
 		}
@@ -685,7 +686,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);
 
 	if (node->tbmiterator)
 		tbm_end_iterate(node->tbmiterator);
@@ -781,7 +782,7 @@ ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 	/*
 	 * close heap scan
 	 */
-	heap_endscan(scanDesc);
+	storage_endscan(scanDesc);
 
 	/*
 	 * close the heap relation.
@@ -882,7 +883,7 @@ 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,
+	scanstate->ss.ss_currentScanDesc = storage_beginscan_bm(currentRelation,
 														 estate->es_snapshot,
 														 0,
 														 NULL);
@@ -1015,5 +1016,5 @@ ExecBitmapHeapInitializeWorker(BitmapHeapScanState *node, shm_toc *toc)
 	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 84ff343..f25133c 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -61,11 +61,8 @@ ForeignNext(ForeignScanState *node)
 	 * tableoid, which is the only actually-useful system column.
 	 */
 	if (plan->fsSystemCol && !TupIsNull(slot))
-	{
-		HeapTuple	tup = ExecHeapifySlot(slot);
-
-		tup->t_tableOid = RelationGetRelid(node->ss.ss_currentRelation);
-	}
+		ExecSlotUpdateTupleTableoid(slot,
+							RelationGetRelid(node->ss.ss_currentRelation));
 
 	return slot;
 }
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index a0f5a60..abd7580 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -45,7 +45,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);
 
 
@@ -250,7 +250,7 @@ gather_getnext(GatherState *gatherstate)
 	TupleTableSlot *outerTupleSlot;
 	TupleTableSlot *fslot = gatherstate->funnel_slot;
 	MemoryContext tupleContext = gatherstate->ps.ps_ExprContext->ecxt_per_tuple_memory;
-	HeapTuple	tup;
+	StorageTuple	tup;
 
 	while (gatherstate->reader != NULL || gatherstate->need_to_scan_locally)
 	{
@@ -293,7 +293,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;
@@ -301,7 +301,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 2526c58..673fa44 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -31,7 +31,7 @@
  */
 typedef struct GMReaderTupleBuffer
 {
-	HeapTuple  *tuple;
+	StorageTuple  *tuple;
 	int			readCounter;
 	int			nTuples;
 	bool		done;
@@ -47,7 +47,7 @@ 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,
+static StorageTuple gm_readnext_tuple(GatherMergeState *gm_state, int nreader,
 				  bool nowait, bool *done);
 static void gather_merge_init(GatherMergeState *gm_state);
 static void ExecShutdownGatherMergeWorkers(GatherMergeState *node);
@@ -377,7 +377,7 @@ gather_merge_init(GatherMergeState *gm_state)
 	{
 		/* Allocate the tuple array with MAX_TUPLE_STORE size */
 		gm_state->gm_tuple_buffers[i].tuple =
-			(HeapTuple *) palloc0(sizeof(HeapTuple) * MAX_TUPLE_STORE);
+			(StorageTuple *) palloc0(sizeof(StorageTuple) * MAX_TUPLE_STORE);
 
 		/* Initialize slot for worker */
 		gm_state->gm_slots[i] = ExecInitExtraTupleSlot(gm_state->ps.state);
@@ -541,7 +541,7 @@ static bool
 gather_merge_readnext(GatherMergeState *gm_state, int reader, bool nowait)
 {
 	GMReaderTupleBuffer *tuple_buffer;
-	HeapTuple	tup = NULL;
+	StorageTuple	tup = NULL;
 
 	/*
 	 * If we're being asked to generate a tuple from the leader, then we just
@@ -616,12 +616,12 @@ gather_merge_readnext(GatherMergeState *gm_state, int reader, bool nowait)
 /*
  * Attempt to read a tuple from given reader.
  */
-static HeapTuple
+static StorageTuple
 gm_readnext_tuple(GatherMergeState *gm_state, int nreader, bool nowait,
 				  bool *done)
 {
 	TupleQueueReader *reader;
-	HeapTuple	tup = NULL;
+	StorageTuple	tup = NULL;
 	MemoryContext oldContext;
 	MemoryContext tupleContext;
 
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index fe7ba3f..8668aa5 100644
--- a/src/backend/executor/nodeIndexonlyscan.c
+++ b/src/backend/executor/nodeIndexonlyscan.c
@@ -116,7 +116,7 @@ IndexOnlyNext(IndexOnlyScanState *node)
 	 */
 	while ((tid = index_getnext_tid(scandesc, direction)) != NULL)
 	{
-		HeapTuple	tuple = NULL;
+		StorageTuple	tuple = NULL;
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -185,7 +185,7 @@ 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
+		 * 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.
 		 */
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 404076d..38be6b1 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -50,7 +50,7 @@
 typedef struct
 {
 	pairingheap_node ph_node;
-	HeapTuple	htup;
+	StorageTuple	htup;
 	Datum	   *orderbyvals;
 	bool	   *orderbynulls;
 } ReorderTuple;
@@ -64,9 +64,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);
 
 
 /* ----------------------------------------------------------------
@@ -83,7 +83,7 @@ IndexNext(IndexScanState *node)
 	ExprContext *econtext;
 	ScanDirection direction;
 	IndexScanDesc scandesc;
-	HeapTuple	tuple;
+	StorageTuple	tuple;
 	TupleTableSlot *slot;
 
 	/*
@@ -184,7 +184,7 @@ IndexNextWithReorder(IndexScanState *node)
 	EState	   *estate;
 	ExprContext *econtext;
 	IndexScanDesc scandesc;
-	HeapTuple	tuple;
+	StorageTuple	tuple;
 	TupleTableSlot *slot;
 	ReorderTuple *topmost = NULL;
 	bool		was_exact;
@@ -482,7 +482,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;
@@ -515,10 +515,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 6a118d1..04f85e5 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -29,9 +29,9 @@
 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,
-				   HeapScanDesc scan);
+static StorageTuple tablesample_getnext(SampleScanState *scanstate);
+static bool SampleTupleVisible(StorageTuple tuple, OffsetNumber tupoffset,
+		HeapScanDesc scan); //hari
 
 /* ----------------------------------------------------------------
  *						Scan Support
@@ -47,7 +47,7 @@ static bool SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset,
 static TupleTableSlot *
 SampleNext(SampleScanState *node)
 {
-	HeapTuple	tuple;
+	StorageTuple	tuple;
 	TupleTableSlot *slot;
 
 	/*
@@ -66,7 +66,8 @@ SampleNext(SampleScanState *node)
 	if (tuple)
 		ExecStoreTuple(tuple,	/* tuple to store */
 					   slot,	/* slot to store in */
-					   node->ss.ss_currentScanDesc->rs_cbuf,	/* tuple's buffer */
+					   //harinode->ss.ss_currentScanDesc->rs_cbuf,	/* tuple's buffer */
+					   InvalidBuffer,
 					   false);	/* don't pfree this pointer */
 	else
 		ExecClearTuple(slot);
@@ -244,7 +245,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,7 +350,7 @@ tablesample_init(SampleScanState *scanstate)
 	if (scanstate->ss.ss_currentScanDesc == NULL)
 	{
 		scanstate->ss.ss_currentScanDesc =
-			heap_beginscan_sampling(scanstate->ss.ss_currentRelation,
+			storage_beginscan_sampling(scanstate->ss.ss_currentRelation,
 									scanstate->ss.ps.state->es_snapshot,
 									0, NULL,
 									scanstate->use_bulkread,
@@ -358,7 +359,7 @@ tablesample_init(SampleScanState *scanstate)
 	}
 	else
 	{
-		heap_rescan_set_params(scanstate->ss.ss_currentScanDesc, NULL,
+		storage_rescan_set_params(scanstate->ss.ss_currentScanDesc, NULL,
 							   scanstate->use_bulkread,
 							   allow_sync,
 							   scanstate->use_pagemode);
@@ -376,7 +377,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 +555,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 5c49d4c..77f171f 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -27,6 +27,7 @@
 #include "postgres.h"
 
 #include "access/relscan.h"
+#include "access/storageam.h"
 #include "executor/execdebug.h"
 #include "executor/nodeSeqscan.h"
 #include "utils/rel.h"
@@ -48,8 +49,7 @@ static TupleTableSlot *SeqNext(SeqScanState *node);
 static TupleTableSlot *
 SeqNext(SeqScanState *node)
 {
-	HeapTuple	tuple;
-	HeapScanDesc scandesc;
+	StorageScanDesc scandesc;
 	EState	   *estate;
 	ScanDirection direction;
 	TupleTableSlot *slot;
@@ -68,7 +68,7 @@ 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,
+		scandesc = storage_beginscan(node->ss.ss_currentRelation,
 								  estate->es_snapshot,
 								  0, NULL);
 		node->ss.ss_currentScanDesc = scandesc;
@@ -77,26 +77,7 @@ SeqNext(SeqScanState *node)
 	/*
 	 * 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);
 }
 
 /*
@@ -224,7 +205,7 @@ void
 ExecEndSeqScan(SeqScanState *node)
 {
 	Relation	relation;
-	HeapScanDesc scanDesc;
+	StorageScanDesc scanDesc;
 
 	/*
 	 * get information from node
@@ -247,7 +228,7 @@ ExecEndSeqScan(SeqScanState *node)
 	 * close heap scan
 	 */
 	if (scanDesc != NULL)
-		heap_endscan(scanDesc);
+		storage_endscan(scanDesc);
 
 	/*
 	 * close the heap relation.
@@ -269,12 +250,12 @@ ExecEndSeqScan(SeqScanState *node)
 void
 ExecReScanSeqScan(SeqScanState *node)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 
 	scan = node->ss.ss_currentScanDesc;
 
 	if (scan != NULL)
-		heap_rescan(scan,		/* scan desc */
+		storage_rescan(scan,		/* scan desc */
 					NULL);		/* new scan keys */
 
 	ExecScanReScan((ScanState *) node);
@@ -321,7 +302,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);
 }
 
 /* ----------------------------------------------------------------
@@ -337,5 +318,5 @@ ExecSeqScanInitializeWorker(SeqScanState *node, shm_toc *toc)
 
 	pscan = shm_toc_lookup(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 80be460..d55a752 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -2091,7 +2091,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;
@@ -2159,7 +2159,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 afe231f..418c2a6 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -627,11 +627,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)
 	{
@@ -655,7 +655,7 @@ SPI_copytuple(HeapTuple tuple)
 }
 
 HeapTupleHeader
-SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc)
+SPI_returntuple(StorageTuple tuple, TupleDesc tupdesc)
 {
 	MemoryContext oldcxt;
 	HeapTupleHeader dtup;
@@ -686,7 +686,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)
 {
@@ -854,7 +854,7 @@ char *
 SPI_gettype(TupleDesc tupdesc, int fnumber)
 {
 	Oid			typoid;
-	HeapTuple	typeTuple;
+	StorageTuple	typeTuple;
 	char	   *result;
 
 	SPI_result = 0;
@@ -962,7 +962,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);
@@ -1683,7 +1683,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);
@@ -1714,8 +1714,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 a8e5206..3440db2 100644
--- a/src/backend/executor/tqueue.c
+++ b/src/backend/executor/tqueue.c
@@ -192,12 +192,12 @@ static void TQSendRecordInfo(TQueueDestReceiver *tqueue, int32 typmod,
 				 TupleDesc tupledesc);
 static void TupleQueueHandleControlMessage(TupleQueueReader *reader,
 							   Size nbytes, char *data);
-static HeapTuple TupleQueueHandleDataMessage(TupleQueueReader *reader,
+static StorageTuple TupleQueueHandleDataMessage(TupleQueueReader *reader,
 							Size nbytes, HeapTupleHeader data);
-static HeapTuple TQRemapTuple(TupleQueueReader *reader,
+static StorageTuple TQRemapTuple(TupleQueueReader *reader,
 			 TupleDesc tupledesc,
 			 TupleRemapInfo **field_remapinfo,
-			 HeapTuple tuple);
+			 StorageTuple tuple);
 static Datum TQRemap(TupleQueueReader *reader, TupleRemapInfo *remapinfo,
 		Datum value, bool *changed);
 static Datum TQRemapArray(TupleQueueReader *reader, ArrayRemapInfo *remapinfo,
@@ -675,7 +675,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)
 {
 	shm_mq_result result;
@@ -730,7 +730,7 @@ TupleQueueReaderNext(TupleQueueReader *reader, bool nowait, bool *done)
 /*
  * Handle a data message - that is, a tuple - from the remote side.
  */
-static HeapTuple
+static StorageTuple
 TupleQueueHandleDataMessage(TupleQueueReader *reader,
 							Size nbytes,
 							HeapTupleHeader data)
@@ -759,11 +759,11 @@ TupleQueueHandleDataMessage(TupleQueueReader *reader,
 /*
  * Copy the given tuple, remapping any transient typmods contained in it.
  */
-static HeapTuple
+static StorageTuple
 TQRemapTuple(TupleQueueReader *reader,
 			 TupleDesc tupledesc,
 			 TupleRemapInfo **field_remapinfo,
-			 HeapTuple tuple)
+			 StorageTuple tuple)
 {
 	Datum	   *values;
 	bool	   *isnull;
@@ -1121,7 +1121,7 @@ TupleQueueHandleControlMessage(TupleQueueReader *reader, Size nbytes,
 static TupleRemapInfo *
 BuildTupleRemapInfo(Oid typid, MemoryContext mycontext)
 {
-	HeapTuple	tup;
+	StorageTuple	tup;
 	Form_pg_type typ;
 
 	/* This is recursive, so it could be driven to stack overflow. */
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 776b1c0..fec203d 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1882,9 +1882,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;
@@ -1911,7 +1911,7 @@ get_database_list(void)
 		MemoryContextSwitchTo(oldcxt);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	CommitTransactionCommand();
@@ -2042,13 +2042,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;
@@ -2134,7 +2134,7 @@ do_autovacuum(void)
 		}
 	}
 
-	heap_endscan(relScan);
+	storage_endscan(relScan);
 
 	/* second pass: check TOAST tables */
 	ScanKeyInit(&key,
@@ -2142,8 +2142,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;
@@ -2189,7 +2189,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 1f75e2e..6370541 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 6c89442..fca56d4 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 d03984a..d71d673 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(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 eb6960d..82c042a 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -21,6 +21,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"
@@ -1208,10 +1209,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 b40d2d5..7f8b908 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -106,39 +106,14 @@ 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);
-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_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 Size heap_parallelscan_estimate(Snapshot snapshot);
 extern void heap_parallelscan_initialize(ParallelHeapScanDesc target,
 							 Relation relation, Snapshot snapshot);
-extern HeapScanDesc heap_beginscan_parallel(Relation, ParallelHeapScanDesc);
 
 extern Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 			int options, BulkInsertState bistate);
-extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
-					   Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
-					   bool *all_dead, bool first_call);
-extern bool heap_hot_search(ItemPointer tid, Relation relation,
-				Snapshot snapshot, bool *all_dead);
 extern void setLastTid(const ItemPointer tid);
 
 extern BulkInsertState GetBulkInsertState(void);
@@ -146,8 +121,6 @@ extern void FreeBulkInsertState(BulkInsertState);
 extern void ReleaseBulkInsertStatePin(BulkInsertState bistate);
 
 extern void heap_inplace_update(Relation relation, HeapTuple tuple);
-extern bool heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
-				  TransactionId cutoff_multi);
 extern bool heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
 						MultiXactId cutoff_multi, Buffer buf);
 extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
@@ -157,8 +130,6 @@ extern void simple_heap_delete(Relation relation, ItemPointer tid);
 extern void simple_heap_update(Relation relation, ItemPointer otid,
 				   HeapTuple tup);
 
-extern void heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
-
 /* 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/heapam_common.h b/src/include/access/heapam_common.h
index 799b4ed..66a96d7 100644
--- a/src/include/access/heapam_common.h
+++ b/src/include/access/heapam_common.h
@@ -107,6 +107,9 @@ static const int MultiXactStatusLock[MaxMultiXactStatus + 1] =
 /* Get the LOCKMODE for a given MultiXactStatus */
 #define LOCKMODE_from_mxstatus(status) \
 			(tupleLockExtraInfo[TUPLOCK_from_mxstatus((status))].hwlock)
+
+extern bool	synchronize_seqscans;
+
 extern HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
 					TransactionId xid, CommandId cid, int options);
 
@@ -136,6 +139,11 @@ extern void MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 in
 extern MultiXactStatus get_mxact_status_for_lock(LockTupleMode mode, bool is_update);
 
 extern void heap_inplace_update(Relation relation, HeapTuple tuple);
+
+extern bool heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
+						MultiXactId cutoff_multi, Buffer buf);
+extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
+
 extern bool heap_acquire_tuplock(Relation relation, ItemPointer tid,
 					 LockTupleMode mode, LockWaitPolicy wait_policy,
 					 bool *have_tuple_lock);
diff --git a/src/include/access/storageam.h b/src/include/access/storageam.h
index 9502c92..507af71 100644
--- a/src/include/access/storageam.h
+++ b/src/include/access/storageam.h
@@ -19,6 +19,7 @@
 
 /* A physical tuple coming from a storage AM scan */
 typedef void *StorageTuple;
+typedef void *StorageScanDesc;
 
 typedef union tuple_data
 {
@@ -36,6 +37,34 @@ typedef enum tuple_data_flags
 	CTID
 } tuple_data_flags;
 
+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,
@@ -44,6 +73,15 @@ 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 bool storage_freeze_tuple(Relation rel, HeapTupleHeader tuple, TransactionId cutoff_xid,
+				  TransactionId cutoff_multi);
+
 extern HTSU_Result storage_lock_tuple(Relation relation, ItemPointer tid, StorageTuple *stuple,
 				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 				bool follow_updates,
@@ -72,10 +110,6 @@ extern bool storage_tuple_is_heaponly(Relation relation, StorageTuple tuple);
 
 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/executor/functions.h b/src/include/executor/functions.h
index 718d894..7f9bef1 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 acade7e..d466c99 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,9 +117,9 @@ 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,
+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);
@@ -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 a717ac6..4156767 100644
--- a/src/include/executor/tqueue.h
+++ b/src/include/executor/tqueue.h
@@ -27,7 +27,7 @@ extern DestReceiver *CreateTupleQueueDestReceiver(shm_mq_handle *handle);
 extern TupleQueueReader *CreateTupleQueueReader(shm_mq_handle *handle,
 					   TupleDesc tupledesc);
 extern void DestroyTupleQueueReader(TupleQueueReader *reader);
-extern HeapTuple TupleQueueReaderNext(TupleQueueReader *reader,
+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 951af2a..ab0e091 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -229,7 +229,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);
 
-- 
1.8.3.1

0001-Change-Create-Access-method-to-include-storage-handl.patchapplication/octet-stream; name=0001-Change-Create-Access-method-to-include-storage-handl.patchDownload
From c877ea9cdbd82efae869db9e73eb4d669be34e32 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Tue, 29 Aug 2017 19:45:30 +1000
Subject: [PATCH 1/7] 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              |  4 ++++
 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, 63 insertions(+), 11 deletions(-)

diff --git a/src/backend/commands/amcmds.c b/src/backend/commands/amcmds.c
index 7e0a9aa..33079c1 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 7d0de99..ee8760e 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
@@ -5143,16 +5145,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 be79353..0a7e0a3 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 e021f5b..2fbe359 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
@@ -83,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 = 772 (  heapam         heapam_storage_handler s ));
+DESCR("heapam storage access method");
+#define HEAPAM_STORAGE_AM_OID 772
 
 #endif							/* PG_AM_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 8b33b4e..a38a954 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3826,6 +3826,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 = 336  (  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 = 337  (  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 ffdb452..ea352fa 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 fcf8bd7..3bc9607 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1711,11 +1711,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 2945966..f1f58a3 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1153,15 +1153,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 ****************
 
-- 
1.8.3.1

0002-Storage-AM-API-hooks-and-related-functions.patchapplication/octet-stream; name=0002-Storage-AM-API-hooks-and-related-functions.patchDownload
From 0be15e2d9f0023068d44815e28708f0c42d6d430 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Wed, 30 Aug 2017 12:41:15 +1000
Subject: [PATCH 2/7] Storage AM API hooks and related functions

---
 src/backend/access/heap/Makefile         |   3 +-
 src/backend/access/heap/heapam_storage.c |  58 ++++++++
 src/backend/access/heap/storageamapi.c   | 103 ++++++++++++++
 src/include/access/htup.h                |  21 +++
 src/include/access/storageamapi.h        | 237 +++++++++++++++++++++++++++++++
 src/include/catalog/pg_proc.h            |   5 +
 src/include/nodes/nodes.h                |   1 +
 src/include/utils/tqual.h                |   9 --
 8 files changed, 427 insertions(+), 10 deletions(-)
 create mode 100644 src/backend/access/heap/heapam_storage.c
 create mode 100644 src/backend/access/heap/storageamapi.c
 create mode 100644 src/include/access/storageamapi.h

diff --git a/src/backend/access/heap/Makefile b/src/backend/access/heap/Makefile
index b83d496..02a3909 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 storageamapi.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 0000000..fbffb3a
--- /dev/null
+++ b/src/backend/access/heap/heapam_storage.c
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/heapam.h"
+#include "access/heapam_xlog.h"
+#include "access/hio.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/relscan.h"
+#include "access/storageamapi.h"
+#include "access/subtrans.h"
+#include "access/tuptoaster.h"
+#include "access/valid.h"
+#include "access/visibilitymap.h"
+#include "access/xloginsert.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/bufmgr.h"
+#include "storage/lmgr.h"
+#include "storage/predicate.h"
+#include "storage/procarray.h"
+#include "storage/smgr.h"
+#include "storage/spin.h"
+#include "utils/builtins.h"
+#include "utils/inval.h"
+#include "utils/rel.h"
+#include "utils/tqual.h"
+
+
+Datum
+heapam_storage_handler(PG_FUNCTION_ARGS)
+{
+	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
+
+
+	PG_RETURN_POINTER(amroutine);
+}
diff --git a/src/backend/access/heap/storageamapi.c b/src/backend/access/heap/storageamapi.c
new file mode 100644
index 0000000..def2029
--- /dev/null
+++ b/src/backend/access/heap/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/htup.h b/src/include/access/htup.h
index 61b3e68..6459435 100644
--- a/src/include/access/htup.h
+++ b/src/include/access/htup.h
@@ -26,6 +26,27 @@ typedef struct MinimalTupleData MinimalTupleData;
 
 typedef MinimalTupleData *MinimalTuple;
 
+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 */
+
+	END_OF_VISIBILITY
+} tuple_visibility_type;
+
+/* 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;
 
 /*
  * HeapTupleData is an in-memory data structure that points to a tuple.
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
new file mode 100644
index 0000000..95fe028
--- /dev/null
+++ b/src/include/access/storageamapi.h
@@ -0,0 +1,237 @@
+/*---------------------------------------------------------------------
+ *
+ * storageamapi.h
+ *		API for Postgres storage access methods
+ *
+ * Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * src/include/access/storageamapi.h
+ *---------------------------------------------------------------------
+ */
+#ifndef STORAGEAMAPI_H
+#define STORAGEAMAPI_H
+
+#include "access/htup.h"
+#include "access/heapam.h"
+#include "access/sdir.h"
+#include "access/skey.h"
+#include "executor/tuptable.h"
+#include "utils/relcache.h"
+#include "utils/snapshot.h"
+
+/* A physical tuple coming from a storage AM scan */
+typedef void *StorageTuple;
+
+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;
+
+
+typedef HeapScanDesc (*scan_begin_hook) (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 (*scan_setlimits_hook) (HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
+
+/* must return a TupleTableSlot? */
+typedef StorageTuple (*scan_getnext_hook) (HeapScanDesc scan,
+											ScanDirection direction);
+
+typedef TupleTableSlot* (*scan_getnext_slot_hook) (HeapScanDesc scan,
+										ScanDirection direction, TupleTableSlot *slot);
+
+typedef void (*scan_end_hook) (HeapScanDesc scan);
+
+
+typedef void (*scan_getpage_hook) (HeapScanDesc scan, BlockNumber page);
+typedef void (*scan_rescan_hook) (HeapScanDesc scan, ScanKey key, bool set_params,
+					bool allow_strat, bool allow_sync, bool allow_pagemode);
+typedef void (*scan_update_snapshot_hook) (HeapScanDesc scan, Snapshot snapshot);
+
+typedef bool (*hot_search_buffer_hook) (ItemPointer tid, Relation relation,
+					   Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
+					   bool *all_dead, bool first_call);
+
+typedef Oid (*tuple_insert_hook) (Relation relation,
+									  TupleTableSlot *tupslot,
+									  CommandId cid,
+									  int options,
+									  BulkInsertState bistate);
+
+typedef HTSU_Result (*tuple_delete_hook) (Relation relation,
+											  ItemPointer tid,
+											  CommandId cid,
+											  Snapshot crosscheck,
+											  bool wait,
+											  HeapUpdateFailureData *hufd);
+
+typedef HTSU_Result (*tuple_update_hook) (Relation relation,
+											  ItemPointer otid,
+											  TupleTableSlot *slot,
+											  CommandId cid,
+											  Snapshot crosscheck,
+											  bool wait,
+											  HeapUpdateFailureData *hufd,
+											  LockTupleMode *lockmode);
+
+typedef bool (*tuple_fetch_hook) (Relation relation,
+									ItemPointer tid,
+									Snapshot snapshot,
+									StorageTuple *tuple,
+									Buffer *userbuf,
+									bool keep_buf,
+									Relation stats_relation);
+
+typedef HTSU_Result (*tuple_lock_hook) (Relation relation,
+											ItemPointer tid,
+											StorageTuple *tuple,
+											CommandId cid,
+											LockTupleMode mode,
+											LockWaitPolicy wait_policy,
+											bool follow_update,
+											Buffer *buffer,
+											HeapUpdateFailureData *hufd);
+
+typedef void (*multi_insert_hook) (Relation relation, HeapTuple *tuples, int ntuples,
+				  CommandId cid, int options, BulkInsertState bistate);
+
+typedef bool (*tuple_freeze_hook) (HeapTupleHeader tuple, TransactionId cutoff_xid,
+				  TransactionId cutoff_multi);
+
+typedef void (*tuple_get_latest_tid_hook) (Relation relation,
+											Snapshot snapshot,
+											ItemPointer tid);
+
+typedef tuple_data (*get_tuple_data_hook) (StorageTuple tuple, tuple_data_flags flags);
+
+typedef StorageTuple (*tuple_from_datum_hook) (Datum data, Oid tableoid);
+
+typedef bool (*tuple_is_heaponly_hook) (StorageTuple tuple);
+
+typedef void (*slot_store_tuple_hook) (TupleTableSlot *slot,
+										   StorageTuple tuple,
+										   bool shouldFree,
+										   bool minumumtuple);
+typedef void (*slot_clear_tuple_hook) (TupleTableSlot *slot);
+typedef Datum (*slot_getattr_hook) (TupleTableSlot *slot,
+										int attnum, bool *isnull);
+typedef void (*slot_virtualize_tuple_hook) (TupleTableSlot *slot, int16 upto);
+
+typedef HeapTuple (*slot_tuple_hook) (TupleTableSlot *slot, bool palloc_copy);
+typedef MinimalTuple (*slot_min_tuple_hook) (TupleTableSlot *slot, bool palloc_copy);
+
+typedef void (*slot_update_tableoid_hook) (TupleTableSlot *slot, Oid tableoid);
+
+typedef void (*speculative_finish_hook) (Relation rel,
+											 TupleTableSlot *slot);
+typedef void (*speculative_abort_hook) (Relation rel,
+											TupleTableSlot *slot);
+
+typedef void (*relation_sync_hook) (Relation relation);
+
+typedef bool (*snapshot_satisfies_hook) (StorageTuple htup, Snapshot snapshot, Buffer buffer);
+typedef HTSU_Result (*snapshot_satisfies_update_hook) (StorageTuple htup, CommandId curcid, Buffer buffer);
+typedef HTSV_Result (*snapshot_satisfies_vacuum_hook) (StorageTuple htup, TransactionId OldestXmin, Buffer buffer);
+
+typedef struct StorageSlotAmRoutine
+{
+	/* Operations on TupleTableSlot */
+	slot_store_tuple_hook	slot_store_tuple;
+	slot_virtualize_tuple_hook	slot_virtualize_tuple;
+	slot_clear_tuple_hook	slot_clear_tuple;
+	slot_getattr_hook	slot_getattr;
+	slot_tuple_hook     slot_tuple;
+	slot_min_tuple_hook      slot_min_tuple;
+	slot_update_tableoid_hook slot_update_tableoid;
+} StorageSlotAmRoutine;
+
+typedef StorageSlotAmRoutine* (*slot_storageam_hook) (void);
+
+/*
+ * 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;
+
+	/* Operations on relation scans */
+	scan_begin_hook scan_begin;
+	scan_setlimits_hook scansetlimits;
+	scan_getnext_hook scan_getnext;
+	scan_getnext_slot_hook scan_getnextslot;
+	scan_end_hook scan_end;
+	scan_getpage_hook scan_getpage;
+	scan_rescan_hook scan_rescan;
+	scan_update_snapshot_hook scan_update_snapshot;
+	hot_search_buffer_hook hot_search_buffer; /* heap_hot_search_buffer */
+
+	// heap_sync_function		heap_sync;		/* heap_sync */
+	/* not implemented */
+	//	parallelscan_estimate_function	parallelscan_estimate;	/* heap_parallelscan_estimate */
+	//	parallelscan_initialize_function parallelscan_initialize;	/* heap_parallelscan_initialize */
+	//	parallelscan_begin_function	parallelscan_begin;	/* heap_beginscan_parallel */
+
+	/* Operations on physical tuples */
+	tuple_insert_hook		tuple_insert;	/* heap_insert */
+	tuple_update_hook		tuple_update;	/* heap_update */
+	tuple_delete_hook		tuple_delete;	/* heap_delete */
+	tuple_fetch_hook		tuple_fetch;	/* heap_fetch */
+	tuple_lock_hook			tuple_lock;		/* heap_lock_tuple */
+	multi_insert_hook		multi_insert;	/* heap_multi_insert */
+	tuple_freeze_hook       tuple_freeze;	/* heap_freeze_tuple */
+	tuple_get_latest_tid_hook tuple_get_latest_tid; /* heap_get_latest_tid */
+
+	get_tuple_data_hook		get_tuple_data;
+	tuple_is_heaponly_hook	tuple_is_heaponly;
+	tuple_from_datum_hook	tuple_from_datum;
+
+
+	slot_storageam_hook	slot_storageam;
+
+	/*
+	 * 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:
+	 */
+	speculative_finish_hook	speculative_finish;
+	speculative_abort_hook	speculative_abort;
+
+
+	relation_sync_hook	relation_sync;	/* heap_sync */
+
+	snapshot_satisfies_hook snapshot_satisfies[END_OF_VISIBILITY];
+	snapshot_satisfies_update_hook snapshot_satisfiesUpdate; /* HeapTupleSatisfiesUpdate */
+	snapshot_satisfies_vacuum_hook snapshot_satisfiesVacuum; /* HeapTupleSatisfiesVacuum */
+} 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_proc.h b/src/include/catalog/pg_proc.h
index a38a954..1065671 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -549,6 +549,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 = 425 (  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	425
+
 /* 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 27bd4f3..108df9b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -496,6 +496,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;
diff --git a/src/include/utils/tqual.h b/src/include/utils/tqual.h
index 036d989..0fb2e1c 100644
--- a/src/include/utils/tqual.h
+++ b/src/include/utils/tqual.h
@@ -45,15 +45,6 @@ extern PGDLLIMPORT SnapshotData CatalogSnapshotData;
 #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,
-- 
1.8.3.1

0003-Adding-storageam-hanlder-to-relation-structure.patchapplication/octet-stream; name=0003-Adding-storageam-hanlder-to-relation-structure.patchDownload
From be49e85d588d142749c4c2fb3636ac17909513a8 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Wed, 30 Aug 2017 12:49:46 +1000
Subject: [PATCH 3/7] Adding storageam hanlder to relation structure

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

diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index b8e3780..40cbc67 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -34,6 +34,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"
@@ -1368,10 +1369,26 @@ 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);
@@ -1868,6 +1885,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
@@ -2027,6 +2109,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
 	 */
 	if (IsBootstrapProcessingMode())
@@ -2354,6 +2441,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)
@@ -3368,6 +3457,13 @@ 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.
 	 *
@@ -3889,6 +3985,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);
 
@@ -5607,6 +5715,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 4bc61e5..82d8a53 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -161,6 +161,12 @@ typedef struct RelationData
 	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)
 	 *
 	 * Note: only default support procs for each opclass are cached, namely
@@ -428,6 +434,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 3c53cef..03d996f 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -71,6 +71,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
  */
-- 
1.8.3.1

0004-Adding-tuple-visibility-function-to-storage-AM.patchapplication/octet-stream; name=0004-Adding-tuple-visibility-function-to-storage-AM.patchDownload
From d3259b65df625091305a0223550020be649362af Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Wed, 30 Aug 2017 13:59:59 +1000
Subject: [PATCH 4/7] 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 heapam storage is moved into heapam_common.c
and heapam_common.h files.
---
 contrib/pg_visibility/pg_visibility.c       |   10 +-
 contrib/pgrowlocks/pgrowlocks.c             |    3 +-
 contrib/pgstattuple/pgstatapprox.c          |    7 +-
 contrib/pgstattuple/pgstattuple.c           |    3 +-
 src/backend/access/heap/Makefile            |    3 +-
 src/backend/access/heap/heapam.c            |   23 +-
 src/backend/access/heap/heapam_common.c     |  162 +++
 src/backend/access/heap/heapam_storage.c    | 1601 ++++++++++++++++++++++++
 src/backend/access/heap/pruneheap.c         |    4 +-
 src/backend/access/heap/rewriteheap.c       |    1 +
 src/backend/access/index/genam.c            |    4 +-
 src/backend/catalog/index.c                 |    4 +-
 src/backend/commands/analyze.c              |    2 +-
 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/backend/utils/time/tqual.c              | 1787 ---------------------------
 src/include/access/heapam_common.h          |   96 ++
 src/include/storage/bufmgr.h                |    6 +-
 src/include/utils/snapshot.h                |    2 +-
 src/include/utils/tqual.h                   |   62 +-
 28 files changed, 1940 insertions(+), 1881 deletions(-)
 create mode 100644 src/backend/access/heap/heapam_common.c
 delete mode 100644 src/backend/utils/time/tqual.c
 create mode 100644 src/include/access/heapam_common.h

diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c
index 2cc9575..10d47cd 100644
--- a/contrib/pg_visibility/pg_visibility.c
+++ b/contrib/pg_visibility/pg_visibility.c
@@ -51,7 +51,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 +656,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 +681,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 +739,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 eabca65..5f076ef 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,7 +150,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
 		/* must hold a buffer lock to call HeapTupleSatisfiesUpdate */
 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 
-		htsu = HeapTupleSatisfiesUpdate(tuple,
+		htsu = rel->rd_stamroutine->snapshot_satisfiesUpdate(tuple,
 										GetCurrentCommandId(false),
 										scan->rs_cbuf);
 		xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
diff --git a/contrib/pgstattuple/pgstatapprox.c b/contrib/pgstattuple/pgstatapprox.c
index 5bf0613..284eabc 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 7a91cc3..f7b68a8 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 02a3909..e6bc18e 100644
--- a/src/backend/access/heap/Makefile
+++ b/src/backend/access/heap/Makefile
@@ -12,7 +12,8 @@ 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 storageamapi.o \
+OBJS = heapam.o heapam_common.o heapam_storage.o hio.o \
+	pruneheap.o rewriteheap.o storageamapi.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 ff03c68..57aab19 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -40,11 +40,13 @@
 
 #include "access/bufmask.h"
 #include "access/heapam.h"
+#include "access/heapam_common.h"
 #include "access/heapam_xlog.h"
 #include "access/hio.h"
 #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 +440,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 +655,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);
 
@@ -1952,7 +1955,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);
@@ -2099,7 +2102,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 */
@@ -2270,7 +2273,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;
@@ -3094,7 +3097,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)
 	{
@@ -3205,7 +3208,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;
 	}
 
@@ -3665,7 +3668,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);
@@ -3846,7 +3849,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;
 	}
 
@@ -4597,7 +4600,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_common.c b/src/backend/access/heap/heapam_common.c
new file mode 100644
index 0000000..a1d9859
--- /dev/null
+++ b/src/backend/access/heap/heapam_common.c
@@ -0,0 +1,162 @@
+/*-------------------------------------------------------------------------
+ *
+ * heapam_common.c
+ *	  heapam 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/heap/heapam_common.c
+ *
+ *
+ * NOTES
+ *	  This file contains the storage_ routines which implement
+ *	  the POSTGRES storage access method used for all POSTGRES
+ *	  relations.
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/heapam_common.h"
+#include "access/htup_details.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);
+}
+
+
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index fbffb3a..77a5721 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -21,6 +21,7 @@
 #include "postgres.h"
 
 #include "access/heapam.h"
+#include "access/heapam_common.h"
 #include "access/heapam_xlog.h"
 #include "access/hio.h"
 #include "access/htup_details.h"
@@ -47,6 +48,1597 @@
 #include "utils/rel.h"
 #include "utils/tqual.h"
 
+/* local functions */
+static bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
+
+/*-------------------------------------------------------------------------
+ *
+ * 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
+ * has now committed or aborted (and it is safe to set the hint bits).
+ * If the hint bits are changed, MarkBufferDirtyHint is called on
+ * the passed-in buffer.  The caller must hold not only a pin, but at least
+ * shared buffer content lock on the buffer containing the tuple.
+ *
+ * NOTE: When using a non-MVCC snapshot, we must check
+ * TransactionIdIsInProgress (which looks in the PGXACT array)
+ * before TransactionIdDidCommit/TransactionIdDidAbort (which look in
+ * pg_xact).  Otherwise we have a race condition: we might decide that a
+ * just-committed transaction crashed, because none of the tests succeed.
+ * xact.c is careful to record commit/abort in pg_xact before it unsets
+ * MyPgXact->xid in the PGXACT array.  That fixes that problem, but it
+ * also means there is a window where TransactionIdIsInProgress and
+ * TransactionIdDidCommit will both return true.  If we check only
+ * TransactionIdDidCommit, we could consider a tuple committed when a
+ * later GetSnapshotData call will still think the originating transaction
+ * is in progress, which leads to application-level inconsistency.  The
+ * upshot is that we gotta check TransactionIdIsInProgress first in all
+ * code paths, except for a few cases where we are looking at
+ * subtransactions of our own main transaction and so there can't be any
+ * race condition.
+ *
+ * When using an MVCC snapshot, we rely on XidInMVCCSnapshot rather than
+ * TransactionIdIsInProgress, but the logic is otherwise the same: do not
+ * check pg_xact until after deciding that the xact is no longer in progress.
+ *
+ *
+ * Summary of visibility functions:
+ *
+ *	 HeapTupleSatisfiesMVCC()
+ *		  visible to supplied snapshot, excludes current command
+ *	 HeapTupleSatisfiesUpdate()
+ *		  visible to instant snapshot, with user-supplied command
+ *		  counter and more complex result
+ *	 HeapTupleSatisfiesSelf()
+ *		  visible to instant snapshot and current command
+ *	 HeapTupleSatisfiesDirty()
+ *		  like HeapTupleSatisfiesSelf(), but includes open transactions
+ *	 HeapTupleSatisfiesVacuum()
+ *		  visible to any running transaction, used by VACUUM
+ *	 HeapTupleSatisfiesToast()
+ *		  visible unless part of interrupted vacuum, used for TOAST
+ *	 HeapTupleSatisfiesAny()
+ *		  all tuples are visible
+ *
+ * -------------------------------------------------------------------------
+ */
+
+/*
+ * HeapTupleSatisfiesSelf
+ *		True iff heap tuple is valid "for itself".
+ *
+ *	Here, we consider the effects of:
+ *		all committed transactions (as of the current instant)
+ *		previous commands of this transaction
+ *		changes made by the current command
+ *
+ * Note:
+ *		Assumes heap tuple is valid.
+ *
+ * The satisfaction of "itself" requires the following:
+ *
+ * ((Xmin == my-transaction &&				the row was updated by the current transaction, and
+ *		(Xmax is null						it was not deleted
+ *		 [|| Xmax != my-transaction)])			[or it was deleted by another transaction]
+ * ||
+ *
+ * (Xmin is committed &&					the row was modified by a committed transaction, and
+ *		(Xmax is null ||					the row has not been deleted, or
+ *			(Xmax != my-transaction &&			the row was deleted by another transaction
+ *			 Xmax is not committed)))			that has not been committed
+ */
+static bool
+HeapTupleSatisfiesSelf(StorageTuple stup, Snapshot snapshot, Buffer buffer)
+{
+	HeapTuple htup = (HeapTuple)stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return false;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return false;
+			if (!TransactionIdIsInProgress(xvac))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (TransactionIdIsInProgress(xvac))
+					return false;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return true;
+
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
+				return true;
+
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleGetUpdateXid(tuple);
+
+				/* not LOCKED_ONLY, so it has to have an xmax */
+				Assert(TransactionIdIsValid(xmax));
+
+				/* updating subtransaction must have aborted */
+				if (!TransactionIdIsCurrentTransactionId(xmax))
+					return true;
+				else
+					return false;
+			}
+
+			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+			{
+				/* deleting subtransaction must have aborted */
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+				return true;
+			}
+
+			return false;
+		}
+		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+			return false;
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return false;
+		}
+	}
+
+	/* by here, the inserting transaction has committed */
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
+		return true;
+
+	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+		return false;			/* updated by other */
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+
+		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 true;
+		if (TransactionIdDidCommit(xmax))
+			return false;
+		/* it must have aborted or crashed */
+		return true;
+	}
+
+	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+		return false;
+	}
+
+	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+		return true;
+
+	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		/* it must have aborted or crashed */
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return true;
+	}
+
+	/* xmax transaction committed */
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	{
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return true;
+	}
+
+	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+				HeapTupleHeaderGetRawXmax(tuple));
+	return false;
+}
+
+/*
+ * HeapTupleSatisfiesAny
+ *		Dummy "satisfies" routine: any tuple satisfies SnapshotAny.
+ */
+static bool
+HeapTupleSatisfiesAny(StorageTuple stup, Snapshot snapshot, Buffer buffer)
+{
+	return true;
+}
+
+/*
+ * HeapTupleSatisfiesToast
+ *		True iff heap tuple is valid as a TOAST row.
+ *
+ * This is a simplified version that only checks for VACUUM moving conditions.
+ * It's appropriate for TOAST usage because TOAST really doesn't want to do
+ * its own time qual checks; if you can see the main table row that contains
+ * a TOAST reference, you should be able to see the TOASTed value.  However,
+ * vacuuming a TOAST table is independent of the main table, and in case such
+ * a vacuum fails partway through, we'd better do this much checking.
+ *
+ * Among other things, this means you can't do UPDATEs of rows in a TOAST
+ * table.
+ */
+static bool
+HeapTupleSatisfiesToast(StorageTuple stup, Snapshot snapshot,
+						Buffer buffer)
+{
+	HeapTuple htup = (HeapTuple)stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return false;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return false;
+			if (!TransactionIdIsInProgress(xvac))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (TransactionIdIsInProgress(xvac))
+					return false;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+			}
+		}
+
+		/*
+		 * An invalid Xmin can be left behind by a speculative insertion that
+		 * is canceled by super-deleting the tuple.  This also applies to
+		 * TOAST tuples created during speculative insertion.
+		 */
+		else if (!TransactionIdIsValid(HeapTupleHeaderGetXmin(tuple)))
+			return false;
+	}
+
+	/* otherwise assume the tuple is valid for TOAST. */
+	return true;
+}
+
+/*
+ * HeapTupleSatisfiesUpdate
+ *
+ *	This function returns a more detailed result code than most of the
+ *	functions in this file, since UPDATE needs to know more than "is it
+ *	visible?".  It also allows for user-supplied CommandId rather than
+ *	relying on CurrentCommandId.
+ *
+ *	The possible return codes are:
+ *
+ *	HeapTupleInvisible: the tuple didn't exist at all when the scan started,
+ *	e.g. it was created by a later CommandId.
+ *
+ *	HeapTupleMayBeUpdated: The tuple is valid and visible, so it may be
+ *	updated.
+ *
+ *	HeapTupleSelfUpdated: The tuple was updated by the current transaction,
+ *	after the current scan started.
+ *
+ *	HeapTupleUpdated: The tuple was updated by a committed transaction.
+ *
+ *	HeapTupleBeingUpdated: The tuple is being updated by an in-progress
+ *	transaction other than the current transaction.  (Note: this includes
+ *	the case where the tuple is share-locked by a MultiXact, even if the
+ *	MultiXact includes the current transaction.  Callers that want to
+ *	distinguish that case must test for it themselves.)
+ */
+static HTSU_Result
+HeapTupleSatisfiesUpdate(StorageTuple stup, CommandId curcid,
+						 Buffer buffer)
+{
+	HeapTuple htup = (HeapTuple)stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return HeapTupleInvisible;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return HeapTupleInvisible;
+			if (!TransactionIdIsInProgress(xvac))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return HeapTupleInvisible;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (TransactionIdIsInProgress(xvac))
+					return HeapTupleInvisible;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return HeapTupleInvisible;
+				}
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (HeapTupleHeaderGetCmin(tuple) >= curcid)
+				return HeapTupleInvisible;	/* inserted after scan started */
+
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return HeapTupleMayBeUpdated;
+
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleHeaderGetRawXmax(tuple);
+
+				/*
+				 * Careful here: even though this tuple was created by our own
+				 * transaction, it might be locked by other transactions, if
+				 * the original version was key-share locked when we updated
+				 * it.
+				 */
+
+				if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+				{
+					if (MultiXactIdIsRunning(xmax, true))
+						return HeapTupleBeingUpdated;
+					else
+						return HeapTupleMayBeUpdated;
+				}
+
+				/*
+				 * If the locker is gone, then there is nothing of interest
+				 * left in this Xmax; otherwise, report the tuple as
+				 * locked/updated.
+				 */
+				if (!TransactionIdIsInProgress(xmax))
+					return HeapTupleMayBeUpdated;
+				return HeapTupleBeingUpdated;
+			}
+
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleGetUpdateXid(tuple);
+
+				/* not LOCKED_ONLY, so it has to have an xmax */
+				Assert(TransactionIdIsValid(xmax));
+
+				/* deleting subtransaction must have aborted */
+				if (!TransactionIdIsCurrentTransactionId(xmax))
+				{
+					if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
+											 false))
+						return HeapTupleBeingUpdated;
+					return HeapTupleMayBeUpdated;
+				}
+				else
+				{
+					if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+						return HeapTupleSelfUpdated;	/* updated after scan
+														 * started */
+					else
+						return HeapTupleInvisible;	/* updated before scan
+													 * started */
+				}
+			}
+
+			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+			{
+				/* deleting subtransaction must have aborted */
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+				return HeapTupleMayBeUpdated;
+			}
+
+			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+				return HeapTupleSelfUpdated;	/* updated after scan started */
+			else
+				return HeapTupleInvisible;	/* updated before scan started */
+		}
+		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+			return HeapTupleInvisible;
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return HeapTupleInvisible;
+		}
+	}
+
+	/* by here, the inserting transaction has committed */
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
+		return HeapTupleMayBeUpdated;
+
+	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return HeapTupleMayBeUpdated;
+		return HeapTupleUpdated;	/* updated by other */
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		if (HEAP_LOCKED_UPGRADED(tuple->t_infomask))
+			return HeapTupleMayBeUpdated;
+
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+		{
+			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), true))
+				return HeapTupleBeingUpdated;
+
+			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
+			return HeapTupleMayBeUpdated;
+		}
+
+		xmax = HeapTupleGetUpdateXid(tuple);
+		if (!TransactionIdIsValid(xmax))
+		{
+			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+				return HeapTupleBeingUpdated;
+		}
+
+		/* not LOCKED_ONLY, so it has to have an xmax */
+		Assert(TransactionIdIsValid(xmax));
+
+		if (TransactionIdIsCurrentTransactionId(xmax))
+		{
+			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+				return HeapTupleSelfUpdated;	/* updated after scan started */
+			else
+				return HeapTupleInvisible;	/* updated before scan started */
+		}
+
+		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+			return HeapTupleBeingUpdated;
+
+		if (TransactionIdDidCommit(xmax))
+			return HeapTupleUpdated;
+
+		/*
+		 * By here, the update in the Xmax is either aborted or crashed, but
+		 * what about the other members?
+		 */
+
+		if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+		{
+			/*
+			 * There's no member, even just a locker, alive anymore, so we can
+			 * mark the Xmax as invalid.
+			 */
+			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+						InvalidTransactionId);
+			return HeapTupleMayBeUpdated;
+		}
+		else
+		{
+			/* There are lockers running */
+			return HeapTupleBeingUpdated;
+		}
+	}
+
+	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return HeapTupleBeingUpdated;
+		if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+			return HeapTupleSelfUpdated;	/* updated after scan started */
+		else
+			return HeapTupleInvisible;	/* updated before scan started */
+	}
+
+	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+		return HeapTupleBeingUpdated;
+
+	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		/* it must have aborted or crashed */
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return HeapTupleMayBeUpdated;
+	}
+
+	/* xmax transaction committed */
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	{
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return HeapTupleMayBeUpdated;
+	}
+
+	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+				HeapTupleHeaderGetRawXmax(tuple));
+	return HeapTupleUpdated;	/* updated by other */
+}
+
+/*
+ * HeapTupleSatisfiesDirty
+ *		True iff heap tuple is valid including effects of open transactions.
+ *
+ *	Here, we consider the effects of:
+ *		all committed and in-progress transactions (as of the current instant)
+ *		previous commands of this transaction
+ *		changes made by the current command
+ *
+ * This is essentially like HeapTupleSatisfiesSelf as far as effects of
+ * the current transaction and committed/aborted xacts are concerned.
+ * However, we also include the effects of other xacts still in progress.
+ *
+ * A special hack is that the passed-in snapshot struct is used as an
+ * output argument to return the xids of concurrent xacts that affected the
+ * tuple.  snapshot->xmin is set to the tuple's xmin if that is another
+ * transaction that's still in progress; or to InvalidTransactionId if the
+ * tuple's xmin is committed good, committed dead, or my own xact.
+ * Similarly for snapshot->xmax and the tuple's xmax.  If the tuple was
+ * inserted speculatively, meaning that the inserter might still back down
+ * on the insertion without aborting the whole transaction, the associated
+ * token is also returned in snapshot->speculativeToken.
+ */
+static bool
+HeapTupleSatisfiesDirty(StorageTuple stup, Snapshot snapshot,
+						Buffer buffer)
+{
+	HeapTuple htup = (HeapTuple)stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	snapshot->xmin = snapshot->xmax = InvalidTransactionId;
+	snapshot->speculativeToken = 0;
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return false;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return false;
+			if (!TransactionIdIsInProgress(xvac))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (TransactionIdIsInProgress(xvac))
+					return false;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return true;
+
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
+				return true;
+
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleGetUpdateXid(tuple);
+
+				/* not LOCKED_ONLY, so it has to have an xmax */
+				Assert(TransactionIdIsValid(xmax));
+
+				/* updating subtransaction must have aborted */
+				if (!TransactionIdIsCurrentTransactionId(xmax))
+					return true;
+				else
+					return false;
+			}
+
+			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+			{
+				/* deleting subtransaction must have aborted */
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+				return true;
+			}
+
+			return false;
+		}
+		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			/*
+			 * Return the speculative token to caller.  Caller can worry about
+			 * xmax, since it requires a conclusively locked row version, and
+			 * a concurrent update to this tuple is a conflict of its
+			 * purposes.
+			 */
+			if (HeapTupleHeaderIsSpeculative(tuple))
+			{
+				snapshot->speculativeToken =
+					HeapTupleHeaderGetSpeculativeToken(tuple);
+
+				Assert(snapshot->speculativeToken != 0);
+			}
+
+			snapshot->xmin = HeapTupleHeaderGetRawXmin(tuple);
+			/* XXX shouldn't we fall through to look at xmax? */
+			return true;		/* in insertion by other */
+		}
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return false;
+		}
+	}
+
+	/* by here, the inserting transaction has committed */
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
+		return true;
+
+	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+		return false;			/* updated by other */
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+
+		xmax = HeapTupleGetUpdateXid(tuple);
+
+		/* not LOCKED_ONLY, so it has to have an xmax */
+		Assert(TransactionIdIsValid(xmax));
+
+		if (TransactionIdIsCurrentTransactionId(xmax))
+			return false;
+		if (TransactionIdIsInProgress(xmax))
+		{
+			snapshot->xmax = xmax;
+			return true;
+		}
+		if (TransactionIdDidCommit(xmax))
+			return false;
+		/* it must have aborted or crashed */
+		return true;
+	}
+
+	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+		return false;
+	}
+
+	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			snapshot->xmax = HeapTupleHeaderGetRawXmax(tuple);
+		return true;
+	}
+
+	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		/* it must have aborted or crashed */
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return true;
+	}
+
+	/* xmax transaction committed */
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	{
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return true;
+	}
+
+	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+				HeapTupleHeaderGetRawXmax(tuple));
+	return false;				/* updated by other */
+}
+
+/*
+ * HeapTupleSatisfiesMVCC
+ *		True iff heap tuple is valid for the given MVCC snapshot.
+ *
+ *	Here, we consider the effects of:
+ *		all transactions committed as of the time of the given snapshot
+ *		previous commands of this transaction
+ *
+ *	Does _not_ include:
+ *		transactions shown as in-progress by the snapshot
+ *		transactions started after the snapshot was taken
+ *		changes made by the current command
+ *
+ * Notice that here, we will not update the tuple status hint bits if the
+ * inserting/deleting transaction is still running according to our snapshot,
+ * even if in reality it's committed or aborted by now.  This is intentional.
+ * Checking the true transaction state would require access to high-traffic
+ * shared data structures, creating contention we'd rather do without, and it
+ * would not change the result of our visibility check anyway.  The hint bits
+ * will be updated by the first visitor that has a snapshot new enough to see
+ * the inserting/deleting transaction as done.  In the meantime, the cost of
+ * leaving the hint bits unset is basically that each HeapTupleSatisfiesMVCC
+ * call will need to run TransactionIdIsCurrentTransactionId in addition to
+ * XidInMVCCSnapshot (but it would have to do the latter anyway).  In the old
+ * coding where we tried to set the hint bits as soon as possible, we instead
+ * did TransactionIdIsInProgress in each call --- to no avail, as long as the
+ * inserting/deleting transaction was still running --- which was more cycles
+ * and more contention on the PGXACT array.
+ */
+static bool
+HeapTupleSatisfiesMVCC(StorageTuple stup, Snapshot snapshot,
+					   Buffer buffer)
+{
+	HeapTuple htup = (HeapTuple)stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return false;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return false;
+			if (!XidInMVCCSnapshot(xvac, snapshot))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (XidInMVCCSnapshot(xvac, snapshot))
+					return false;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (HeapTupleHeaderGetCmin(tuple) >= snapshot->curcid)
+				return false;	/* inserted after scan started */
+
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return true;
+
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
+				return true;
+
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleGetUpdateXid(tuple);
+
+				/* not LOCKED_ONLY, so it has to have an xmax */
+				Assert(TransactionIdIsValid(xmax));
+
+				/* updating subtransaction must have aborted */
+				if (!TransactionIdIsCurrentTransactionId(xmax))
+					return true;
+				else if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+					return true;	/* updated after scan started */
+				else
+					return false;	/* updated before scan started */
+			}
+
+			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+			{
+				/* deleting subtransaction must have aborted */
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+				return true;
+			}
+
+			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+				return true;	/* deleted after scan started */
+			else
+				return false;	/* deleted before scan started */
+		}
+		else if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
+			return false;
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return false;
+		}
+	}
+	else
+	{
+		/* xmin is committed, but maybe not according to our snapshot */
+		if (!HeapTupleHeaderXminFrozen(tuple) &&
+			XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
+			return false;		/* treat as still in progress */
+	}
+
+	/* by here, the inserting transaction has committed */
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
+		return true;
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+		return true;
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		/* already checked above */
+		Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
+
+		xmax = HeapTupleGetUpdateXid(tuple);
+
+		/* not LOCKED_ONLY, so it has to have an xmax */
+		Assert(TransactionIdIsValid(xmax));
+
+		if (TransactionIdIsCurrentTransactionId(xmax))
+		{
+			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+				return true;	/* deleted after scan started */
+			else
+				return false;	/* deleted before scan started */
+		}
+		if (XidInMVCCSnapshot(xmax, snapshot))
+			return true;
+		if (TransactionIdDidCommit(xmax))
+			return false;		/* updating transaction committed */
+		/* it must have aborted or crashed */
+		return true;
+	}
+
+	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
+	{
+		if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+		{
+			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+				return true;	/* deleted after scan started */
+			else
+				return false;	/* deleted before scan started */
+		}
+
+		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
+			return true;
+
+		if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+						InvalidTransactionId);
+			return true;
+		}
+
+		/* xmax transaction committed */
+		SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+					HeapTupleHeaderGetRawXmax(tuple));
+	}
+	else
+	{
+		/* xmax is committed, but maybe not according to our snapshot */
+		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
+			return true;		/* treat as still in progress */
+	}
+
+	/* xmax transaction committed */
+
+	return false;
+}
+
+
+/*
+ * HeapTupleSatisfiesVacuum
+ *
+ *	Determine the status of tuples for VACUUM purposes.  Here, what
+ *	we mainly want to know is if a tuple is potentially visible to *any*
+ *	running transaction.  If so, it can't be removed yet by VACUUM.
+ *
+ * OldestXmin is a cutoff XID (obtained from GetOldestXmin()).  Tuples
+ * deleted by XIDs >= OldestXmin are deemed "recently dead"; they might
+ * still be visible to some open transaction, so we can't remove them,
+ * even if we see that the deleting transaction has committed.
+ */
+static HTSV_Result
+HeapTupleSatisfiesVacuum(StorageTuple stup, TransactionId OldestXmin,
+						 Buffer buffer)
+{
+	HeapTuple htup = (HeapTuple)stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	/*
+	 * Has inserting transaction committed?
+	 *
+	 * If the inserting transaction aborted, then the tuple was never visible
+	 * to any other transaction, so we can delete it immediately.
+	 */
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return HEAPTUPLE_DEAD;
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return HEAPTUPLE_DELETE_IN_PROGRESS;
+			if (TransactionIdIsInProgress(xvac))
+				return HEAPTUPLE_DELETE_IN_PROGRESS;
+			if (TransactionIdDidCommit(xvac))
+			{
+				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+							InvalidTransactionId);
+				return HEAPTUPLE_DEAD;
+			}
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						InvalidTransactionId);
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return HEAPTUPLE_INSERT_IN_PROGRESS;
+			if (TransactionIdIsInProgress(xvac))
+				return HEAPTUPLE_INSERT_IN_PROGRESS;
+			if (TransactionIdDidCommit(xvac))
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			else
+			{
+				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+							InvalidTransactionId);
+				return HEAPTUPLE_DEAD;
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return HEAPTUPLE_INSERT_IN_PROGRESS;
+			/* only locked? run infomask-only check first, for performance */
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask) ||
+				HeapTupleHeaderIsOnlyLocked(tuple))
+				return HEAPTUPLE_INSERT_IN_PROGRESS;
+			/* inserted and then deleted by same xact */
+			if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple)))
+				return HEAPTUPLE_DELETE_IN_PROGRESS;
+			/* deleting subtransaction must have aborted */
+			return HEAPTUPLE_INSERT_IN_PROGRESS;
+		}
+		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			/*
+			 * It'd be possible to discern between INSERT/DELETE in progress
+			 * here by looking at xmax - but that doesn't seem beneficial for
+			 * the majority of callers and even detrimental for some. We'd
+			 * rather have callers look at/wait for xmin than xmax. It's
+			 * always correct to return INSERT_IN_PROGRESS because that's
+			 * what's happening from the view of other backends.
+			 */
+			return HEAPTUPLE_INSERT_IN_PROGRESS;
+		}
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/*
+			 * Not in Progress, Not Committed, so either Aborted or crashed
+			 */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return HEAPTUPLE_DEAD;
+		}
+
+		/*
+		 * At this point the xmin is known committed, but we might not have
+		 * been able to set the hint bit yet; so we can no longer Assert that
+		 * it's set.
+		 */
+	}
+
+	/*
+	 * Okay, the inserter committed, so it was good at some point.  Now what
+	 * about the deleting transaction?
+	 */
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)
+		return HEAPTUPLE_LIVE;
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	{
+		/*
+		 * "Deleting" xact really only locked it, so the tuple is live in any
+		 * case.  However, we should make sure that either XMAX_COMMITTED or
+		 * XMAX_INVALID gets set once the xact is gone, to reduce the costs of
+		 * examining the tuple for future xacts.
+		 */
+		if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
+		{
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				/*
+				 * If it's a pre-pg_upgrade tuple, the multixact cannot
+				 * possibly be running; otherwise have to check.
+				 */
+				if (!HEAP_LOCKED_UPGRADED(tuple->t_infomask) &&
+					MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
+										 true))
+					return HEAPTUPLE_LIVE;
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
+			}
+			else
+			{
+				if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+					return HEAPTUPLE_LIVE;
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+			}
+		}
+
+		/*
+		 * We don't really care whether xmax did commit, abort or crash. We
+		 * know that xmax did lock the tuple, but it did not and will never
+		 * actually update it.
+		 */
+
+		return HEAPTUPLE_LIVE;
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+		{
+			/* already checked above */
+			Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
+
+			xmax = HeapTupleGetUpdateXid(tuple);
+
+			/* not LOCKED_ONLY, so it has to have an xmax */
+			Assert(TransactionIdIsValid(xmax));
+
+			if (TransactionIdIsInProgress(xmax))
+				return HEAPTUPLE_DELETE_IN_PROGRESS;
+			else if (TransactionIdDidCommit(xmax))
+				/* there are still lockers around -- can't return DEAD here */
+				return HEAPTUPLE_RECENTLY_DEAD;
+			/* updating transaction aborted */
+			return HEAPTUPLE_LIVE;
+		}
+
+		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED));
+
+		xmax = HeapTupleGetUpdateXid(tuple);
+
+		/* not LOCKED_ONLY, so it has to have an xmax */
+		Assert(TransactionIdIsValid(xmax));
+
+		/* multi is not running -- updating xact cannot be */
+		Assert(!TransactionIdIsInProgress(xmax));
+		if (TransactionIdDidCommit(xmax))
+		{
+			if (!TransactionIdPrecedes(xmax, OldestXmin))
+				return HEAPTUPLE_RECENTLY_DEAD;
+			else
+				return HEAPTUPLE_DEAD;
+		}
+
+		/*
+		 * Not in Progress, Not Committed, so either Aborted or crashed.
+		 * Remove the Xmax.
+		 */
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
+		return HEAPTUPLE_LIVE;
+	}
+
+	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
+	{
+		if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+			return HEAPTUPLE_DELETE_IN_PROGRESS;
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+						HeapTupleHeaderGetRawXmax(tuple));
+		else
+		{
+			/*
+			 * Not in Progress, Not Committed, so either Aborted or crashed
+			 */
+			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+						InvalidTransactionId);
+			return HEAPTUPLE_LIVE;
+		}
+
+		/*
+		 * At this point the xmax is known committed, but we might not have
+		 * been able to set the hint bit yet; so we can no longer Assert that
+		 * it's set.
+		 */
+	}
+
+	/*
+	 * Deleter committed, but perhaps it was recent enough that some open
+	 * transactions could still see the tuple.
+	 */
+	if (!TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin))
+		return HEAPTUPLE_RECENTLY_DEAD;
+
+	/* Otherwise, it's dead and removable */
+	return HEAPTUPLE_DEAD;
+}
+
+/*
+ * 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 for known-committed
+ * XIDs which could not be ours anyway.
+ */
+static 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;
+}
+
+/*
+ * check whether the transaction id 'xid' is in the pre-sorted array 'xip'.
+ */
+static bool
+TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num)
+{
+	return bsearch(&xid, xip, num,
+				   sizeof(TransactionId), xidComparator) != NULL;
+}
+
+/*
+ * See the comments for HeapTupleSatisfiesMVCC for the semantics this function
+ * obeys.
+ *
+ * Only usable on tuples from catalog tables!
+ *
+ * We don't need to support HEAP_MOVED_(IN|OFF) for now because we only support
+ * reading catalog pages which couldn't have been created in an older version.
+ *
+ * We don't set any hint bits in here as it seems unlikely to be beneficial as
+ * those should already be set by normal access and it seems to be too
+ * dangerous to do so as the semantics of doing so during timetravel are more
+ * complicated than when dealing "only" with the present.
+ */
+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);
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	/* inserting transaction aborted */
+	if (HeapTupleHeaderXminInvalid(tuple))
+	{
+		Assert(!TransactionIdDidCommit(xmin));
+		return false;
+	}
+	/* check if it's one of our txids, toplevel is also in there */
+	else if (TransactionIdInArray(xmin, snapshot->subxip, snapshot->subxcnt))
+	{
+		bool		resolved;
+		CommandId	cmin = HeapTupleHeaderGetRawCommandId(tuple);
+		CommandId	cmax = InvalidCommandId;
+
+		/*
+		 * another transaction might have (tried to) delete this tuple or
+		 * cmin/cmax was stored in a combocid. So we need to lookup the actual
+		 * values externally.
+		 */
+		resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot,
+												 htup, buffer,
+												 &cmin, &cmax);
+
+		if (!resolved)
+			elog(ERROR, "could not resolve cmin/cmax of catalog tuple");
+
+		Assert(cmin != InvalidCommandId);
+
+		if (cmin >= snapshot->curcid)
+			return false;		/* inserted after scan started */
+		/* fall through */
+	}
+	/* committed before our xmin horizon. Do a normal visibility check. */
+	else if (TransactionIdPrecedes(xmin, snapshot->xmin))
+	{
+		Assert(!(HeapTupleHeaderXminCommitted(tuple) &&
+				 !TransactionIdDidCommit(xmin)));
+
+		/* check for hint bit first, consult clog afterwards */
+		if (!HeapTupleHeaderXminCommitted(tuple) &&
+			!TransactionIdDidCommit(xmin))
+			return false;
+		/* fall through */
+	}
+	/* beyond our xmax horizon, i.e. invisible */
+	else if (TransactionIdFollowsOrEquals(xmin, snapshot->xmax))
+	{
+		return false;
+	}
+	/* check if it's a committed transaction in [xmin, xmax) */
+	else if (TransactionIdInArray(xmin, snapshot->xip, snapshot->xcnt))
+	{
+		/* fall through */
+	}
+
+	/*
+	 * none of the above, i.e. between [xmin, xmax) but hasn't committed. I.e.
+	 * invisible.
+	 */
+	else
+	{
+		return false;
+	}
+
+	/* at this point we know xmin is visible, go on to check xmax */
+
+	/* xid invalid or aborted */
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)
+		return true;
+	/* locked tuples are always visible */
+	else if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+		return true;
+
+	/*
+	 * We can see multis here if we're looking at user tables or if somebody
+	 * SELECT ... FOR SHARE/UPDATE a system table.
+	 */
+	else if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		xmax = HeapTupleGetUpdateXid(tuple);
+	}
+
+	/* check if it's one of our txids, toplevel is also in there */
+	if (TransactionIdInArray(xmax, snapshot->subxip, snapshot->subxcnt))
+	{
+		bool		resolved;
+		CommandId	cmin;
+		CommandId	cmax = HeapTupleHeaderGetRawCommandId(tuple);
+
+		/* Lookup actual cmin/cmax values */
+		resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot,
+												 htup, buffer,
+												 &cmin, &cmax);
+
+		if (!resolved)
+			elog(ERROR, "could not resolve combocid to cmax");
+
+		Assert(cmax != InvalidCommandId);
+
+		if (cmax >= snapshot->curcid)
+			return true;		/* deleted after scan started */
+		else
+			return false;		/* deleted before scan started */
+	}
+	/* below xmin horizon, normal transaction state is valid */
+	else if (TransactionIdPrecedes(xmax, snapshot->xmin))
+	{
+		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED &&
+				 !TransactionIdDidCommit(xmax)));
+
+		/* check hint bit first */
+		if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+			return false;
+
+		/* check clog */
+		return !TransactionIdDidCommit(xmax);
+	}
+	/* above xmax horizon, we cannot possibly see the deleting transaction */
+	else if (TransactionIdFollowsOrEquals(xmax, snapshot->xmax))
+		return true;
+	/* xmax is between [xmin, xmax), check known committed array */
+	else if (TransactionIdInArray(xmax, snapshot->xip, snapshot->xcnt))
+		return false;
+	/* xmax is between [xmin, xmax), but known not to have committed yet */
+	else
+		return true;
+}
 
 Datum
 heapam_storage_handler(PG_FUNCTION_ARGS)
@@ -54,5 +1646,14 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
 
 
+	amroutine->snapshot_satisfies[MVCC_VISIBILITY] = HeapTupleSatisfiesMVCC;
+	amroutine->snapshot_satisfies[SELF_VISIBILITY] = HeapTupleSatisfiesSelf;
+	amroutine->snapshot_satisfies[ANY_VISIBILITY] = HeapTupleSatisfiesAny;
+	amroutine->snapshot_satisfies[TOAST_VISIBILITY] = HeapTupleSatisfiesToast;
+	amroutine->snapshot_satisfies[DIRTY_VISIBILITY] = HeapTupleSatisfiesDirty;
+	amroutine->snapshot_satisfies[HISTORIC_MVCC_VISIBILITY] = HeapTupleSatisfiesHistoricMVCC;
+
+	amroutine->snapshot_satisfiesUpdate = HeapTupleSatisfiesUpdate;
+	amroutine->snapshot_satisfiesVacuum = HeapTupleSatisfiesVacuum;
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 52231ac..7425068 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/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index bd560e4..191f088 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -108,6 +108,7 @@
 #include "miscadmin.h"
 
 #include "access/heapam.h"
+#include "access/heapam_common.h"
 #include "access/heapam_xlog.h"
 #include "access/rewriteheap.h"
 #include "access/transam.h"
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 05d7da0..01321a2 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/catalog/index.c b/src/backend/catalog/index.c
index c7b2f03..0240df7 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2222,6 +2222,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	TransactionId OldestXmin;
 	BlockNumber root_blkno = InvalidBlockNumber;
 	OffsetNumber root_offsets[MaxHeapTuplesPerPage];
+	StorageAmRoutine *method;
 
 	/*
 	 * sanity checks
@@ -2277,6 +2278,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 */
@@ -2357,7 +2359,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 			 */
 			LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 
-			switch (HeapTupleSatisfiesVacuum(heapTuple, OldestXmin,
+			switch (method->snapshot_satisfiesVacuum(heapTuple, OldestXmin,
 											 scan->rs_cbuf))
 			{
 				case HEAPTUPLE_DEAD:
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index fbad13e..255a797 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1069,7 +1069,7 @@ acquire_sample_rows(Relation onerel, int elevel,
 			targtuple.t_data = (HeapTupleHeader) PageGetItem(targpage, itemid);
 			targtuple.t_len = ItemIdGetLength(itemid);
 
-			switch (HeapTupleSatisfiesVacuum(&targtuple,
+			switch (onerel->rd_stamroutine->snapshot_satisfiesVacuum(&targtuple,
 											 OldestXmin,
 											 targbuffer))
 			{
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 48f1e6e..dbcc5bc 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 45b1859..dccda5a 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -975,7 +975,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:
 
@@ -2140,7 +2140,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 79f534e..02e8f3d 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -428,7 +428,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 e12721a..daa7c7b 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -190,6 +190,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
  */
 static void
 ExecCheckHeapTupleVisible(EState *estate,
+						  Relation rel,
 						  HeapTuple tuple,
 						  Buffer buffer)
 {
@@ -201,7 +202,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
@@ -236,7 +237,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);
 }
 
@@ -1307,7 +1308,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 9c74a83..6a118d1 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 fba57a0..095d1ed 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 6a6d9d6..22948cb 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 c2891e6..5a6d216 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -289,7 +289,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 5a6e6fa..f17b1c5 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 08a08c8..1f96913 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;
@@ -2040,7 +2040,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),
@@ -2137,7 +2137,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/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c
deleted file mode 100644
index f9da9e1..0000000
--- a/src/backend/utils/time/tqual.c
+++ /dev/null
@@ -1,1787 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * tqual.c
- *	  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
- * has now committed or aborted (and it is safe to set the hint bits).
- * If the hint bits are changed, MarkBufferDirtyHint is called on
- * the passed-in buffer.  The caller must hold not only a pin, but at least
- * shared buffer content lock on the buffer containing the tuple.
- *
- * NOTE: When using a non-MVCC snapshot, we must check
- * TransactionIdIsInProgress (which looks in the PGXACT array)
- * before TransactionIdDidCommit/TransactionIdDidAbort (which look in
- * pg_xact).  Otherwise we have a race condition: we might decide that a
- * just-committed transaction crashed, because none of the tests succeed.
- * xact.c is careful to record commit/abort in pg_xact before it unsets
- * MyPgXact->xid in the PGXACT array.  That fixes that problem, but it
- * also means there is a window where TransactionIdIsInProgress and
- * TransactionIdDidCommit will both return true.  If we check only
- * TransactionIdDidCommit, we could consider a tuple committed when a
- * later GetSnapshotData call will still think the originating transaction
- * is in progress, which leads to application-level inconsistency.  The
- * upshot is that we gotta check TransactionIdIsInProgress first in all
- * code paths, except for a few cases where we are looking at
- * subtransactions of our own main transaction and so there can't be any
- * race condition.
- *
- * When using an MVCC snapshot, we rely on XidInMVCCSnapshot rather than
- * TransactionIdIsInProgress, but the logic is otherwise the same: do not
- * check pg_xact until after deciding that the xact is no longer in progress.
- *
- *
- * Summary of visibility functions:
- *
- *	 HeapTupleSatisfiesMVCC()
- *		  visible to supplied snapshot, excludes current command
- *	 HeapTupleSatisfiesUpdate()
- *		  visible to instant snapshot, with user-supplied command
- *		  counter and more complex result
- *	 HeapTupleSatisfiesSelf()
- *		  visible to instant snapshot and current command
- *	 HeapTupleSatisfiesDirty()
- *		  like HeapTupleSatisfiesSelf(), but includes open transactions
- *	 HeapTupleSatisfiesVacuum()
- *		  visible to any running transaction, used by VACUUM
- *	 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};
-
-/* local functions */
-static 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);
-}
-
-/*
- * 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
- *		True iff heap tuple is valid "for itself".
- *
- *	Here, we consider the effects of:
- *		all committed transactions (as of the current instant)
- *		previous commands of this transaction
- *		changes made by the current command
- *
- * Note:
- *		Assumes heap tuple is valid.
- *
- * The satisfaction of "itself" requires the following:
- *
- * ((Xmin == my-transaction &&				the row was updated by the current transaction, and
- *		(Xmax is null						it was not deleted
- *		 [|| Xmax != my-transaction)])			[or it was deleted by another transaction]
- * ||
- *
- * (Xmin is committed &&					the row was modified by a committed transaction, and
- *		(Xmax is null ||					the row has not been deleted, or
- *			(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)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return false;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return false;
-			if (!TransactionIdIsInProgress(xvac))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (TransactionIdIsInProgress(xvac))
-					return false;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return true;
-
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
-				return true;
-
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleGetUpdateXid(tuple);
-
-				/* not LOCKED_ONLY, so it has to have an xmax */
-				Assert(TransactionIdIsValid(xmax));
-
-				/* updating subtransaction must have aborted */
-				if (!TransactionIdIsCurrentTransactionId(xmax))
-					return true;
-				else
-					return false;
-			}
-
-			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-			{
-				/* deleting subtransaction must have aborted */
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-				return true;
-			}
-
-			return false;
-		}
-		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
-			return false;
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return false;
-		}
-	}
-
-	/* by here, the inserting transaction has committed */
-
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
-		return true;
-
-	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-		return false;			/* updated by other */
-	}
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-
-		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 true;
-		if (TransactionIdDidCommit(xmax))
-			return false;
-		/* it must have aborted or crashed */
-		return true;
-	}
-
-	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-		return false;
-	}
-
-	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-		return true;
-
-	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		/* it must have aborted or crashed */
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return true;
-	}
-
-	/* xmax transaction committed */
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-	{
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return true;
-	}
-
-	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-				HeapTupleHeaderGetRawXmax(tuple));
-	return false;
-}
-
-/*
- * HeapTupleSatisfiesAny
- *		Dummy "satisfies" routine: any tuple satisfies SnapshotAny.
- */
-bool
-HeapTupleSatisfiesAny(HeapTuple htup, Snapshot snapshot, Buffer buffer)
-{
-	return true;
-}
-
-/*
- * HeapTupleSatisfiesToast
- *		True iff heap tuple is valid as a TOAST row.
- *
- * This is a simplified version that only checks for VACUUM moving conditions.
- * It's appropriate for TOAST usage because TOAST really doesn't want to do
- * its own time qual checks; if you can see the main table row that contains
- * a TOAST reference, you should be able to see the TOASTed value.  However,
- * vacuuming a TOAST table is independent of the main table, and in case such
- * a vacuum fails partway through, we'd better do this much checking.
- *
- * Among other things, this means you can't do UPDATEs of rows in a TOAST
- * table.
- */
-bool
-HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot,
-						Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return false;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return false;
-			if (!TransactionIdIsInProgress(xvac))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (TransactionIdIsInProgress(xvac))
-					return false;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-			}
-		}
-
-		/*
-		 * An invalid Xmin can be left behind by a speculative insertion that
-		 * is canceled by super-deleting the tuple.  This also applies to
-		 * TOAST tuples created during speculative insertion.
-		 */
-		else if (!TransactionIdIsValid(HeapTupleHeaderGetXmin(tuple)))
-			return false;
-	}
-
-	/* otherwise assume the tuple is valid for TOAST. */
-	return true;
-}
-
-/*
- * HeapTupleSatisfiesUpdate
- *
- *	This function returns a more detailed result code than most of the
- *	functions in this file, since UPDATE needs to know more than "is it
- *	visible?".  It also allows for user-supplied CommandId rather than
- *	relying on CurrentCommandId.
- *
- *	The possible return codes are:
- *
- *	HeapTupleInvisible: the tuple didn't exist at all when the scan started,
- *	e.g. it was created by a later CommandId.
- *
- *	HeapTupleMayBeUpdated: The tuple is valid and visible, so it may be
- *	updated.
- *
- *	HeapTupleSelfUpdated: The tuple was updated by the current transaction,
- *	after the current scan started.
- *
- *	HeapTupleUpdated: The tuple was updated by a committed transaction.
- *
- *	HeapTupleBeingUpdated: The tuple is being updated by an in-progress
- *	transaction other than the current transaction.  (Note: this includes
- *	the case where the tuple is share-locked by a MultiXact, even if the
- *	MultiXact includes the current transaction.  Callers that want to
- *	distinguish that case must test for it themselves.)
- */
-HTSU_Result
-HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
-						 Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return HeapTupleInvisible;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return HeapTupleInvisible;
-			if (!TransactionIdIsInProgress(xvac))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return HeapTupleInvisible;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (TransactionIdIsInProgress(xvac))
-					return HeapTupleInvisible;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return HeapTupleInvisible;
-				}
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (HeapTupleHeaderGetCmin(tuple) >= curcid)
-				return HeapTupleInvisible;	/* inserted after scan started */
-
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return HeapTupleMayBeUpdated;
-
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleHeaderGetRawXmax(tuple);
-
-				/*
-				 * Careful here: even though this tuple was created by our own
-				 * transaction, it might be locked by other transactions, if
-				 * the original version was key-share locked when we updated
-				 * it.
-				 */
-
-				if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-				{
-					if (MultiXactIdIsRunning(xmax, true))
-						return HeapTupleBeingUpdated;
-					else
-						return HeapTupleMayBeUpdated;
-				}
-
-				/*
-				 * If the locker is gone, then there is nothing of interest
-				 * left in this Xmax; otherwise, report the tuple as
-				 * locked/updated.
-				 */
-				if (!TransactionIdIsInProgress(xmax))
-					return HeapTupleMayBeUpdated;
-				return HeapTupleBeingUpdated;
-			}
-
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleGetUpdateXid(tuple);
-
-				/* not LOCKED_ONLY, so it has to have an xmax */
-				Assert(TransactionIdIsValid(xmax));
-
-				/* deleting subtransaction must have aborted */
-				if (!TransactionIdIsCurrentTransactionId(xmax))
-				{
-					if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
-											 false))
-						return HeapTupleBeingUpdated;
-					return HeapTupleMayBeUpdated;
-				}
-				else
-				{
-					if (HeapTupleHeaderGetCmax(tuple) >= curcid)
-						return HeapTupleSelfUpdated;	/* updated after scan
-														 * started */
-					else
-						return HeapTupleInvisible;	/* updated before scan
-													 * started */
-				}
-			}
-
-			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-			{
-				/* deleting subtransaction must have aborted */
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-				return HeapTupleMayBeUpdated;
-			}
-
-			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
-				return HeapTupleSelfUpdated;	/* updated after scan started */
-			else
-				return HeapTupleInvisible;	/* updated before scan started */
-		}
-		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
-			return HeapTupleInvisible;
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return HeapTupleInvisible;
-		}
-	}
-
-	/* by here, the inserting transaction has committed */
-
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
-		return HeapTupleMayBeUpdated;
-
-	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return HeapTupleMayBeUpdated;
-		return HeapTupleUpdated;	/* updated by other */
-	}
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		if (HEAP_LOCKED_UPGRADED(tuple->t_infomask))
-			return HeapTupleMayBeUpdated;
-
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-		{
-			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), true))
-				return HeapTupleBeingUpdated;
-
-			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
-			return HeapTupleMayBeUpdated;
-		}
-
-		xmax = HeapTupleGetUpdateXid(tuple);
-		if (!TransactionIdIsValid(xmax))
-		{
-			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
-				return HeapTupleBeingUpdated;
-		}
-
-		/* not LOCKED_ONLY, so it has to have an xmax */
-		Assert(TransactionIdIsValid(xmax));
-
-		if (TransactionIdIsCurrentTransactionId(xmax))
-		{
-			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
-				return HeapTupleSelfUpdated;	/* updated after scan started */
-			else
-				return HeapTupleInvisible;	/* updated before scan started */
-		}
-
-		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
-			return HeapTupleBeingUpdated;
-
-		if (TransactionIdDidCommit(xmax))
-			return HeapTupleUpdated;
-
-		/*
-		 * By here, the update in the Xmax is either aborted or crashed, but
-		 * what about the other members?
-		 */
-
-		if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
-		{
-			/*
-			 * There's no member, even just a locker, alive anymore, so we can
-			 * mark the Xmax as invalid.
-			 */
-			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-						InvalidTransactionId);
-			return HeapTupleMayBeUpdated;
-		}
-		else
-		{
-			/* There are lockers running */
-			return HeapTupleBeingUpdated;
-		}
-	}
-
-	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return HeapTupleBeingUpdated;
-		if (HeapTupleHeaderGetCmax(tuple) >= curcid)
-			return HeapTupleSelfUpdated;	/* updated after scan started */
-		else
-			return HeapTupleInvisible;	/* updated before scan started */
-	}
-
-	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-		return HeapTupleBeingUpdated;
-
-	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		/* it must have aborted or crashed */
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return HeapTupleMayBeUpdated;
-	}
-
-	/* xmax transaction committed */
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-	{
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return HeapTupleMayBeUpdated;
-	}
-
-	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-				HeapTupleHeaderGetRawXmax(tuple));
-	return HeapTupleUpdated;	/* updated by other */
-}
-
-/*
- * HeapTupleSatisfiesDirty
- *		True iff heap tuple is valid including effects of open transactions.
- *
- *	Here, we consider the effects of:
- *		all committed and in-progress transactions (as of the current instant)
- *		previous commands of this transaction
- *		changes made by the current command
- *
- * This is essentially like HeapTupleSatisfiesSelf as far as effects of
- * the current transaction and committed/aborted xacts are concerned.
- * However, we also include the effects of other xacts still in progress.
- *
- * A special hack is that the passed-in snapshot struct is used as an
- * output argument to return the xids of concurrent xacts that affected the
- * tuple.  snapshot->xmin is set to the tuple's xmin if that is another
- * transaction that's still in progress; or to InvalidTransactionId if the
- * tuple's xmin is committed good, committed dead, or my own xact.
- * Similarly for snapshot->xmax and the tuple's xmax.  If the tuple was
- * inserted speculatively, meaning that the inserter might still back down
- * on the insertion without aborting the whole transaction, the associated
- * token is also returned in snapshot->speculativeToken.
- */
-bool
-HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
-						Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	snapshot->xmin = snapshot->xmax = InvalidTransactionId;
-	snapshot->speculativeToken = 0;
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return false;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return false;
-			if (!TransactionIdIsInProgress(xvac))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (TransactionIdIsInProgress(xvac))
-					return false;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return true;
-
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
-				return true;
-
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleGetUpdateXid(tuple);
-
-				/* not LOCKED_ONLY, so it has to have an xmax */
-				Assert(TransactionIdIsValid(xmax));
-
-				/* updating subtransaction must have aborted */
-				if (!TransactionIdIsCurrentTransactionId(xmax))
-					return true;
-				else
-					return false;
-			}
-
-			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-			{
-				/* deleting subtransaction must have aborted */
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-				return true;
-			}
-
-			return false;
-		}
-		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			/*
-			 * Return the speculative token to caller.  Caller can worry about
-			 * xmax, since it requires a conclusively locked row version, and
-			 * a concurrent update to this tuple is a conflict of its
-			 * purposes.
-			 */
-			if (HeapTupleHeaderIsSpeculative(tuple))
-			{
-				snapshot->speculativeToken =
-					HeapTupleHeaderGetSpeculativeToken(tuple);
-
-				Assert(snapshot->speculativeToken != 0);
-			}
-
-			snapshot->xmin = HeapTupleHeaderGetRawXmin(tuple);
-			/* XXX shouldn't we fall through to look at xmax? */
-			return true;		/* in insertion by other */
-		}
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return false;
-		}
-	}
-
-	/* by here, the inserting transaction has committed */
-
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
-		return true;
-
-	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-		return false;			/* updated by other */
-	}
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-
-		xmax = HeapTupleGetUpdateXid(tuple);
-
-		/* not LOCKED_ONLY, so it has to have an xmax */
-		Assert(TransactionIdIsValid(xmax));
-
-		if (TransactionIdIsCurrentTransactionId(xmax))
-			return false;
-		if (TransactionIdIsInProgress(xmax))
-		{
-			snapshot->xmax = xmax;
-			return true;
-		}
-		if (TransactionIdDidCommit(xmax))
-			return false;
-		/* it must have aborted or crashed */
-		return true;
-	}
-
-	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-		return false;
-	}
-
-	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			snapshot->xmax = HeapTupleHeaderGetRawXmax(tuple);
-		return true;
-	}
-
-	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		/* it must have aborted or crashed */
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return true;
-	}
-
-	/* xmax transaction committed */
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-	{
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return true;
-	}
-
-	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-				HeapTupleHeaderGetRawXmax(tuple));
-	return false;				/* updated by other */
-}
-
-/*
- * HeapTupleSatisfiesMVCC
- *		True iff heap tuple is valid for the given MVCC snapshot.
- *
- *	Here, we consider the effects of:
- *		all transactions committed as of the time of the given snapshot
- *		previous commands of this transaction
- *
- *	Does _not_ include:
- *		transactions shown as in-progress by the snapshot
- *		transactions started after the snapshot was taken
- *		changes made by the current command
- *
- * Notice that here, we will not update the tuple status hint bits if the
- * inserting/deleting transaction is still running according to our snapshot,
- * even if in reality it's committed or aborted by now.  This is intentional.
- * Checking the true transaction state would require access to high-traffic
- * shared data structures, creating contention we'd rather do without, and it
- * would not change the result of our visibility check anyway.  The hint bits
- * will be updated by the first visitor that has a snapshot new enough to see
- * the inserting/deleting transaction as done.  In the meantime, the cost of
- * leaving the hint bits unset is basically that each HeapTupleSatisfiesMVCC
- * call will need to run TransactionIdIsCurrentTransactionId in addition to
- * XidInMVCCSnapshot (but it would have to do the latter anyway).  In the old
- * coding where we tried to set the hint bits as soon as possible, we instead
- * did TransactionIdIsInProgress in each call --- to no avail, as long as the
- * inserting/deleting transaction was still running --- which was more cycles
- * and more contention on the PGXACT array.
- */
-bool
-HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
-					   Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return false;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return false;
-			if (!XidInMVCCSnapshot(xvac, snapshot))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (XidInMVCCSnapshot(xvac, snapshot))
-					return false;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (HeapTupleHeaderGetCmin(tuple) >= snapshot->curcid)
-				return false;	/* inserted after scan started */
-
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return true;
-
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
-				return true;
-
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleGetUpdateXid(tuple);
-
-				/* not LOCKED_ONLY, so it has to have an xmax */
-				Assert(TransactionIdIsValid(xmax));
-
-				/* updating subtransaction must have aborted */
-				if (!TransactionIdIsCurrentTransactionId(xmax))
-					return true;
-				else if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
-					return true;	/* updated after scan started */
-				else
-					return false;	/* updated before scan started */
-			}
-
-			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-			{
-				/* deleting subtransaction must have aborted */
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-				return true;
-			}
-
-			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
-				return true;	/* deleted after scan started */
-			else
-				return false;	/* deleted before scan started */
-		}
-		else if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
-			return false;
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return false;
-		}
-	}
-	else
-	{
-		/* xmin is committed, but maybe not according to our snapshot */
-		if (!HeapTupleHeaderXminFrozen(tuple) &&
-			XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
-			return false;		/* treat as still in progress */
-	}
-
-	/* by here, the inserting transaction has committed */
-
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
-		return true;
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-		return true;
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		/* already checked above */
-		Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
-
-		xmax = HeapTupleGetUpdateXid(tuple);
-
-		/* not LOCKED_ONLY, so it has to have an xmax */
-		Assert(TransactionIdIsValid(xmax));
-
-		if (TransactionIdIsCurrentTransactionId(xmax))
-		{
-			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
-				return true;	/* deleted after scan started */
-			else
-				return false;	/* deleted before scan started */
-		}
-		if (XidInMVCCSnapshot(xmax, snapshot))
-			return true;
-		if (TransactionIdDidCommit(xmax))
-			return false;		/* updating transaction committed */
-		/* it must have aborted or crashed */
-		return true;
-	}
-
-	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
-	{
-		if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-		{
-			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
-				return true;	/* deleted after scan started */
-			else
-				return false;	/* deleted before scan started */
-		}
-
-		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
-			return true;
-
-		if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-						InvalidTransactionId);
-			return true;
-		}
-
-		/* xmax transaction committed */
-		SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-					HeapTupleHeaderGetRawXmax(tuple));
-	}
-	else
-	{
-		/* xmax is committed, but maybe not according to our snapshot */
-		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
-			return true;		/* treat as still in progress */
-	}
-
-	/* xmax transaction committed */
-
-	return false;
-}
-
-
-/*
- * HeapTupleSatisfiesVacuum
- *
- *	Determine the status of tuples for VACUUM purposes.  Here, what
- *	we mainly want to know is if a tuple is potentially visible to *any*
- *	running transaction.  If so, it can't be removed yet by VACUUM.
- *
- * OldestXmin is a cutoff XID (obtained from GetOldestXmin()).  Tuples
- * deleted by XIDs >= OldestXmin are deemed "recently dead"; they might
- * still be visible to some open transaction, so we can't remove them,
- * even if we see that the deleting transaction has committed.
- */
-HTSV_Result
-HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
-						 Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	/*
-	 * Has inserting transaction committed?
-	 *
-	 * If the inserting transaction aborted, then the tuple was never visible
-	 * to any other transaction, so we can delete it immediately.
-	 */
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return HEAPTUPLE_DEAD;
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return HEAPTUPLE_DELETE_IN_PROGRESS;
-			if (TransactionIdIsInProgress(xvac))
-				return HEAPTUPLE_DELETE_IN_PROGRESS;
-			if (TransactionIdDidCommit(xvac))
-			{
-				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-							InvalidTransactionId);
-				return HEAPTUPLE_DEAD;
-			}
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						InvalidTransactionId);
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return HEAPTUPLE_INSERT_IN_PROGRESS;
-			if (TransactionIdIsInProgress(xvac))
-				return HEAPTUPLE_INSERT_IN_PROGRESS;
-			if (TransactionIdDidCommit(xvac))
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			else
-			{
-				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-							InvalidTransactionId);
-				return HEAPTUPLE_DEAD;
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return HEAPTUPLE_INSERT_IN_PROGRESS;
-			/* only locked? run infomask-only check first, for performance */
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask) ||
-				HeapTupleHeaderIsOnlyLocked(tuple))
-				return HEAPTUPLE_INSERT_IN_PROGRESS;
-			/* inserted and then deleted by same xact */
-			if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple)))
-				return HEAPTUPLE_DELETE_IN_PROGRESS;
-			/* deleting subtransaction must have aborted */
-			return HEAPTUPLE_INSERT_IN_PROGRESS;
-		}
-		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			/*
-			 * It'd be possible to discern between INSERT/DELETE in progress
-			 * here by looking at xmax - but that doesn't seem beneficial for
-			 * the majority of callers and even detrimental for some. We'd
-			 * rather have callers look at/wait for xmin than xmax. It's
-			 * always correct to return INSERT_IN_PROGRESS because that's
-			 * what's happening from the view of other backends.
-			 */
-			return HEAPTUPLE_INSERT_IN_PROGRESS;
-		}
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/*
-			 * Not in Progress, Not Committed, so either Aborted or crashed
-			 */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return HEAPTUPLE_DEAD;
-		}
-
-		/*
-		 * At this point the xmin is known committed, but we might not have
-		 * been able to set the hint bit yet; so we can no longer Assert that
-		 * it's set.
-		 */
-	}
-
-	/*
-	 * Okay, the inserter committed, so it was good at some point.  Now what
-	 * about the deleting transaction?
-	 */
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)
-		return HEAPTUPLE_LIVE;
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-	{
-		/*
-		 * "Deleting" xact really only locked it, so the tuple is live in any
-		 * case.  However, we should make sure that either XMAX_COMMITTED or
-		 * XMAX_INVALID gets set once the xact is gone, to reduce the costs of
-		 * examining the tuple for future xacts.
-		 */
-		if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
-		{
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				/*
-				 * If it's a pre-pg_upgrade tuple, the multixact cannot
-				 * possibly be running; otherwise have to check.
-				 */
-				if (!HEAP_LOCKED_UPGRADED(tuple->t_infomask) &&
-					MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
-										 true))
-					return HEAPTUPLE_LIVE;
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
-			}
-			else
-			{
-				if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-					return HEAPTUPLE_LIVE;
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-			}
-		}
-
-		/*
-		 * We don't really care whether xmax did commit, abort or crash. We
-		 * know that xmax did lock the tuple, but it did not and will never
-		 * actually update it.
-		 */
-
-		return HEAPTUPLE_LIVE;
-	}
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
-		{
-			/* already checked above */
-			Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
-
-			xmax = HeapTupleGetUpdateXid(tuple);
-
-			/* not LOCKED_ONLY, so it has to have an xmax */
-			Assert(TransactionIdIsValid(xmax));
-
-			if (TransactionIdIsInProgress(xmax))
-				return HEAPTUPLE_DELETE_IN_PROGRESS;
-			else if (TransactionIdDidCommit(xmax))
-				/* there are still lockers around -- can't return DEAD here */
-				return HEAPTUPLE_RECENTLY_DEAD;
-			/* updating transaction aborted */
-			return HEAPTUPLE_LIVE;
-		}
-
-		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED));
-
-		xmax = HeapTupleGetUpdateXid(tuple);
-
-		/* not LOCKED_ONLY, so it has to have an xmax */
-		Assert(TransactionIdIsValid(xmax));
-
-		/* multi is not running -- updating xact cannot be */
-		Assert(!TransactionIdIsInProgress(xmax));
-		if (TransactionIdDidCommit(xmax))
-		{
-			if (!TransactionIdPrecedes(xmax, OldestXmin))
-				return HEAPTUPLE_RECENTLY_DEAD;
-			else
-				return HEAPTUPLE_DEAD;
-		}
-
-		/*
-		 * Not in Progress, Not Committed, so either Aborted or crashed.
-		 * Remove the Xmax.
-		 */
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
-		return HEAPTUPLE_LIVE;
-	}
-
-	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
-	{
-		if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-			return HEAPTUPLE_DELETE_IN_PROGRESS;
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-						HeapTupleHeaderGetRawXmax(tuple));
-		else
-		{
-			/*
-			 * Not in Progress, Not Committed, so either Aborted or crashed
-			 */
-			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-						InvalidTransactionId);
-			return HEAPTUPLE_LIVE;
-		}
-
-		/*
-		 * At this point the xmax is known committed, but we might not have
-		 * been able to set the hint bit yet; so we can no longer Assert that
-		 * it's set.
-		 */
-	}
-
-	/*
-	 * Deleter committed, but perhaps it was recent enough that some open
-	 * transactions could still see the tuple.
-	 */
-	if (!TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin))
-		return HEAPTUPLE_RECENTLY_DEAD;
-
-	/* Otherwise, it's dead and removable */
-	return 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 for known-committed
- * XIDs which could not be ours anyway.
- */
-static 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'.
- */
-static bool
-TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num)
-{
-	return bsearch(&xid, xip, num,
-				   sizeof(TransactionId), xidComparator) != NULL;
-}
-
-/*
- * See the comments for HeapTupleSatisfiesMVCC for the semantics this function
- * obeys.
- *
- * Only usable on tuples from catalog tables!
- *
- * We don't need to support HEAP_MOVED_(IN|OFF) for now because we only support
- * reading catalog pages which couldn't have been created in an older version.
- *
- * We don't set any hint bits in here as it seems unlikely to be beneficial as
- * those should already be set by normal access and it seems to be too
- * 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,
-							   Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-	TransactionId xmin = HeapTupleHeaderGetXmin(tuple);
-	TransactionId xmax = HeapTupleHeaderGetRawXmax(tuple);
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	/* inserting transaction aborted */
-	if (HeapTupleHeaderXminInvalid(tuple))
-	{
-		Assert(!TransactionIdDidCommit(xmin));
-		return false;
-	}
-	/* check if it's one of our txids, toplevel is also in there */
-	else if (TransactionIdInArray(xmin, snapshot->subxip, snapshot->subxcnt))
-	{
-		bool		resolved;
-		CommandId	cmin = HeapTupleHeaderGetRawCommandId(tuple);
-		CommandId	cmax = InvalidCommandId;
-
-		/*
-		 * another transaction might have (tried to) delete this tuple or
-		 * cmin/cmax was stored in a combocid. So we need to lookup the actual
-		 * values externally.
-		 */
-		resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot,
-												 htup, buffer,
-												 &cmin, &cmax);
-
-		if (!resolved)
-			elog(ERROR, "could not resolve cmin/cmax of catalog tuple");
-
-		Assert(cmin != InvalidCommandId);
-
-		if (cmin >= snapshot->curcid)
-			return false;		/* inserted after scan started */
-		/* fall through */
-	}
-	/* committed before our xmin horizon. Do a normal visibility check. */
-	else if (TransactionIdPrecedes(xmin, snapshot->xmin))
-	{
-		Assert(!(HeapTupleHeaderXminCommitted(tuple) &&
-				 !TransactionIdDidCommit(xmin)));
-
-		/* check for hint bit first, consult clog afterwards */
-		if (!HeapTupleHeaderXminCommitted(tuple) &&
-			!TransactionIdDidCommit(xmin))
-			return false;
-		/* fall through */
-	}
-	/* beyond our xmax horizon, i.e. invisible */
-	else if (TransactionIdFollowsOrEquals(xmin, snapshot->xmax))
-	{
-		return false;
-	}
-	/* check if it's a committed transaction in [xmin, xmax) */
-	else if (TransactionIdInArray(xmin, snapshot->xip, snapshot->xcnt))
-	{
-		/* fall through */
-	}
-
-	/*
-	 * none of the above, i.e. between [xmin, xmax) but hasn't committed. I.e.
-	 * invisible.
-	 */
-	else
-	{
-		return false;
-	}
-
-	/* at this point we know xmin is visible, go on to check xmax */
-
-	/* xid invalid or aborted */
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)
-		return true;
-	/* locked tuples are always visible */
-	else if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-		return true;
-
-	/*
-	 * We can see multis here if we're looking at user tables or if somebody
-	 * SELECT ... FOR SHARE/UPDATE a system table.
-	 */
-	else if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		xmax = HeapTupleGetUpdateXid(tuple);
-	}
-
-	/* check if it's one of our txids, toplevel is also in there */
-	if (TransactionIdInArray(xmax, snapshot->subxip, snapshot->subxcnt))
-	{
-		bool		resolved;
-		CommandId	cmin;
-		CommandId	cmax = HeapTupleHeaderGetRawCommandId(tuple);
-
-		/* Lookup actual cmin/cmax values */
-		resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot,
-												 htup, buffer,
-												 &cmin, &cmax);
-
-		if (!resolved)
-			elog(ERROR, "could not resolve combocid to cmax");
-
-		Assert(cmax != InvalidCommandId);
-
-		if (cmax >= snapshot->curcid)
-			return true;		/* deleted after scan started */
-		else
-			return false;		/* deleted before scan started */
-	}
-	/* below xmin horizon, normal transaction state is valid */
-	else if (TransactionIdPrecedes(xmax, snapshot->xmin))
-	{
-		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED &&
-				 !TransactionIdDidCommit(xmax)));
-
-		/* check hint bit first */
-		if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
-			return false;
-
-		/* check clog */
-		return !TransactionIdDidCommit(xmax);
-	}
-	/* above xmax horizon, we cannot possibly see the deleting transaction */
-	else if (TransactionIdFollowsOrEquals(xmax, snapshot->xmax))
-		return true;
-	/* xmax is between [xmin, xmax), check known committed array */
-	else if (TransactionIdInArray(xmax, snapshot->xip, snapshot->xcnt))
-		return false;
-	/* xmax is between [xmin, xmax), but known not to have committed yet */
-	else
-		return true;
-}
diff --git a/src/include/access/heapam_common.h b/src/include/access/heapam_common.h
new file mode 100644
index 0000000..ff63cf3
--- /dev/null
+++ b/src/include/access/heapam_common.h
@@ -0,0 +1,96 @@
+/*-------------------------------------------------------------------------
+ *
+ * heapam_shared.h
+ *	  POSTGRES heap access method definitions shared across
+ *	  server and heapam methods.
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/heapam_shared.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef HEAPAM_SHARED_H
+#define HEAPAM_SHARED_H
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/sdir.h"
+#include "access/skey.h"
+#include "access/transam.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "nodes/lockoptions.h"
+#include "nodes/primnodes.h"
+#include "storage/bufpage.h"
+#include "storage/bufmgr.h"
+#include "storage/lockdefs.h"
+#include "storage/lmgr.h"
+#include "utils/relcache.h"
+#include "utils/snapshot.h"
+
+
+/* in heap/heapam_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);
+
+/*
+ * 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							/* HEAPAM_H */
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 98b63fc..b8b823b 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -14,13 +14,13 @@
 #ifndef BUFMGR_H
 #define BUFMGR_H
 
+#include "access/storageamapi.h"
 #include "storage/block.h"
 #include "storage/buf.h"
 #include "storage/bufpage.h"
 #include "storage/relfilenode.h"
 #include "utils/relcache.h"
 #include "utils/snapmgr.h"
-#include "utils/tqual.h"
 
 typedef void *Block;
 
@@ -268,8 +268,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 074cc81..96f14ec 100644
--- a/src/include/utils/snapshot.h
+++ b/src/include/utils/snapshot.h
@@ -51,7 +51,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 0fb2e1c..ed8f644 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,46 +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))
-
-
-/* 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 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 void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
-					 uint16 infomask, TransactionId xid);
-extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
-
-/*
- * To avoid leaking too much knowledge about reorderbuffer implementation
- * details this is implemented in reorderbuffer.c not tqual.c.
- */
-struct HTAB;
-extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data,
-							  Snapshot snapshot,
-							  HeapTuple htup,
-							  Buffer buffer,
-							  CommandId *cmin, CommandId *cmax);
+#define HeapTupleSatisfiesVisibility(method, tuple, snapshot, buffer) \
+	(((method)->snapshot_satisfies[(snapshot)->visibility_type]) (tuple, snapshot, buffer))
 
 /*
  * We don't provide a static SnapshotDirty variable because it would be
@@ -89,15 +52,26 @@ 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 SnapshotToast.  We need
  * 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))
 
+/*
+ * To avoid leaking too much knowledge about reorderbuffer implementation
+ * details this is implemented in reorderbuffer.c not tqual.c.
+ */
+struct HTAB;
+extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data,
+							  Snapshot snapshot,
+							  HeapTuple htup,
+							  Buffer buffer,
+							  CommandId *cmin, CommandId *cmax);
+
 #endif							/* TQUAL_H */
-- 
1.8.3.1

0005-slot-hooks-are-added-to-storage-AM.patchapplication/octet-stream; name=0005-slot-hooks-are-added-to-storage-AM.patchDownload
From 43187d9260a7644c56f5a71d19eecb659f67acb7 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Wed, 30 Aug 2017 17:24:35 +1000
Subject: [PATCH 5/7] 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_common.c  | 404 +++++++++++++++++++++++++++++++
 src/backend/access/heap/heapam_storage.c |   1 +
 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    |  26 +-
 src/backend/executor/execReplication.c   |  92 ++-----
 src/backend/executor/execTuples.c        | 269 +++++++++-----------
 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/heapam_common.h       |   2 +
 src/include/access/htup_details.h        |  15 +-
 src/include/executor/tuptable.h          |  54 +++--
 17 files changed, 644 insertions(+), 575 deletions(-)

diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 13ee528..5ed0f15 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"
@@ -1022,111 +1023,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.
  *		It is functionally equivalent to heap_getattr, but fetches of
@@ -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_common.c b/src/backend/access/heap/heapam_common.c
index a1d9859..06a7af1 100644
--- a/src/backend/access/heap/heapam_common.c
+++ b/src/backend/access/heap/heapam_common.c
@@ -159,4 +159,408 @@ HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin)
 	return TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin);
 }
 
+/*-----------------------
+ *
+ * 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;
+}
 
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 77a5721..bf22f2e 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -1645,6 +1645,7 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 {
 	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
 
+	amroutine->slot_storageam = heapam_storage_slot_handler;
 
 	amroutine->snapshot_satisfies[MVCC_VISIBILITY] = HeapTupleSatisfiesMVCC;
 	amroutine->snapshot_satisfies[SELF_VISIBILITY] = HeapTupleSatisfiesSelf;
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index cfa3f05..8456bfd 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2695,7 +2695,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 e60210c..a0ec444 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 d2e0376..b440740 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 da0850b..8634473 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2348,7 +2348,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;
@@ -2429,7 +2429,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;
@@ -2777,7 +2777,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;
@@ -2819,7 +2819,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 	if (newSlot != NULL)
 	{
 		slot = ExecFilterJunk(relinfo->ri_junkFilter, newSlot);
-		slottuple = ExecMaterializeSlot(slot);
+		slottuple = ExecHeapifySlot(slot);
 		newtuple = slottuple;
 	}
 
@@ -2928,7 +2928,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;
@@ -3960,14 +3960,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 83e0447..ad81ab9 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -503,12 +503,12 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			int			attnum = op->d.var.attnum;
 
 			/* 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 */
 
-			*op->resvalue = heap_getsysattr(innerslot->tts_tuple, attnum,
-											innerslot->tts_tupleDescriptor,
+			*op->resvalue = slot_getattr(innerslot,
+											attnum,
 											op->resnull);
 
 			EEO_NEXT();
@@ -519,12 +519,12 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			int			attnum = op->d.var.attnum;
 
 			/* 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 */
-			*op->resvalue = heap_getsysattr(outerslot->tts_tuple, attnum,
-											outerslot->tts_tupleDescriptor,
+
+			*op->resvalue = slot_getattr(outerslot,
+											attnum,
 											op->resnull);
 
 			EEO_NEXT();
@@ -535,12 +535,12 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			int			attnum = op->d.var.attnum;
 
 			/* 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 */
 
-			*op->resvalue = heap_getsysattr(scanslot->tts_tuple, attnum,
-											scanslot->tts_tupleDescriptor,
+			*op->resvalue = slot_getattr(scanslot,
+											attnum,
 											op->resnull);
 
 			EEO_NEXT();
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index fbb8108..d424074 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,7 +226,8 @@ bool
 RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 						 TupleTableSlot *searchslot, TupleTableSlot *outslot)
 {
-	HeapTuple	scantuple;
+    TupleTableSlot *scanslot;
+    HeapTuple	scantuple;
 	HeapScanDesc scan;
 	SnapshotData snap;
 	TransactionId xwait;
@@ -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_update_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 31f814c..b683ac6 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -81,6 +81,7 @@
  */
 #include "postgres.h"
 
+#include "access/heapam_common.h"
 #include "access/htup_details.h"
 #include "access/tuptoaster.h"
 #include "funcapi.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;
 }
@@ -206,6 +206,54 @@ MakeSingleTupleTableSlot(TupleDesc tupdesc)
 }
 
 /* --------------------------------
+ *		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
  *
  *		Release a TupleTableSlot made with MakeSingleTupleTableSlot.
@@ -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,18 @@ 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 +459,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 +540,7 @@ ExecStoreAllNullTuple(TupleTableSlot *slot)
  *		however the "system columns" of the result will not be meaningful.
  * --------------------------------
  */
-HeapTuple
+StorageTuple
 ExecCopySlotTuple(TupleTableSlot *slot)
 {
 	/*
@@ -550,20 +549,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 +568,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 +598,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 +645,7 @@ MinimalTuple
 ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 {
 	MemoryContext oldContext;
+	MinimalTuple tup;
 
 	/*
 	 * sanity checks
@@ -659,11 +653,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 +664,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 +697,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 +717,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,19 +728,11 @@ 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
 	 * mustn't leave any dangling pass-by-reference datums in tts_values.
@@ -768,17 +741,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 140e82e..84ff343 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 daa7c7b..f7051b3 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -171,7 +171,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;
@@ -271,7 +271,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
@@ -403,7 +403,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -416,7 +416,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		newId = InvalidOid;
 	}
@@ -434,7 +434,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
@@ -743,7 +743,7 @@ ExecDelete(ModifyTableState *mtstate,
 		 */
 		if (slot->tts_isempty)
 			ExecStoreAllNullTuple(slot);
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
 	}
 	else
@@ -894,7 +894,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))
@@ -955,7 +955,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
@@ -974,7 +974,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW UPDATE Triggers */
@@ -988,7 +988,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
@@ -1004,7 +1004,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
@@ -1120,7 +1120,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 4c4fcf5..a8e5206 100644
--- a/src/backend/executor/tqueue.c
+++ b/src/backend/executor/tqueue.c
@@ -305,7 +305,7 @@ tqueueReceiveSlot(TupleTableSlot *slot, DestReceiver *self)
 	}
 
 	/* 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 041f387..47dc627 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -724,9 +724,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/heapam_common.h b/src/include/access/heapam_common.h
index ff63cf3..1fe15ed 100644
--- a/src/include/access/heapam_common.h
+++ b/src/include/access/heapam_common.h
@@ -40,6 +40,8 @@ extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
 					 uint16 infomask, TransactionId xid);
 extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
 extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin);
+typedef struct StorageSlotAmRoutine StorageSlotAmRoutine;
+extern StorageSlotAmRoutine* heapam_storage_slot_handler(void);
 
 /*
  * SetHintBits()
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index fa04a63..9539d67 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -21,6 +21,19 @@
 #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
  * a tuple, plus the size of the null-values bitmap (at 1 bit per column),
@@ -653,7 +666,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/executor/tuptable.h b/src/include/executor/tuptable.h
index 55f4cce..84dc293 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);
 
-- 
1.8.3.1

0006-Tuple-Insert-API-is-added-to-Storage-AM.patchapplication/octet-stream; name=0006-Tuple-Insert-API-is-added-to-Storage-AM.patchDownload
From 53ea1e536bf34699be7374a1f64bea1e10210c4b Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Wed, 30 Aug 2017 17:37:59 +1000
Subject: [PATCH 6/7] 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.

Replaced the usage of HeapTuple with storageTuple in
some places, increased the use of slot.
---
 src/backend/access/heap/Makefile         |    2 +-
 src/backend/access/heap/heapam.c         | 2737 ++++--------------------------
 src/backend/access/heap/heapam_storage.c | 2134 +++++++++++++++++++++++
 src/backend/access/heap/rewriteheap.c    |    5 +-
 src/backend/access/heap/storageam.c      |  303 ++++
 src/backend/access/heap/tuptoaster.c     |    8 +-
 src/backend/commands/copy.c              |   29 +-
 src/backend/commands/createas.c          |   18 +-
 src/backend/commands/matview.c           |   10 +-
 src/backend/commands/tablecmds.c         |    5 +-
 src/backend/commands/trigger.c           |   43 +-
 src/backend/executor/execMain.c          |  112 +-
 src/backend/executor/execReplication.c   |   35 +-
 src/backend/executor/nodeLockRows.c      |   39 +-
 src/backend/executor/nodeModifyTable.c   |  177 +-
 src/backend/executor/nodeTidscan.c       |   23 +-
 src/backend/utils/adt/tid.c              |    5 +-
 src/include/access/heapam.h              |   26 +-
 src/include/access/heapam_common.h       |  127 ++
 src/include/access/storageam.h           |   81 +
 src/include/access/storageamapi.h        |   21 +-
 src/include/commands/trigger.h           |    2 +-
 src/include/executor/executor.h          |    6 +-
 src/include/nodes/execnodes.h            |    4 +-
 24 files changed, 3242 insertions(+), 2710 deletions(-)
 create mode 100644 src/backend/access/heap/storageam.c
 create mode 100644 src/include/access/storageam.h

diff --git a/src/backend/access/heap/Makefile b/src/backend/access/heap/Makefile
index e6bc18e..162736f 100644
--- a/src/backend/access/heap/Makefile
+++ b/src/backend/access/heap/Makefile
@@ -13,7 +13,7 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = heapam.o heapam_common.o heapam_storage.o hio.o \
-	pruneheap.o rewriteheap.o storageamapi.o \
+	pruneheap.o rewriteheap.o storageam.o storageamapi.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 57aab19..1eb2c32 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -94,8 +94,6 @@ static HeapScanDesc heap_beginscan_internal(Relation relation,
 						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,
-					TransactionId xid, CommandId cid, int options);
 static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 				Buffer newbuf, HeapTuple oldtup,
 				HeapTuple newtup, HeapTuple old_key_tup,
@@ -103,108 +101,17 @@ static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 static Bitmapset *HeapDetermineModifiedColumns(Relation relation,
 							 Bitmapset *interesting_cols,
 							 HeapTuple oldtup, HeapTuple newtup);
-static bool heap_acquire_tuplock(Relation relation, ItemPointer tid,
-					 LockTupleMode mode, LockWaitPolicy wait_policy,
-					 bool *have_tuple_lock);
-static void compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
-						  uint16 old_infomask2, TransactionId add_to_xmax,
-						  LockTupleMode mode, bool is_update,
-						  TransactionId *result_xmax, uint16 *result_infomask,
-						  uint16 *result_infomask2);
-static HTSU_Result heap_lock_updated_tuple(Relation rel, HeapTuple tuple,
-						ItemPointer ctid, TransactionId xid,
-						LockTupleMode mode);
 static void GetMultiXactIdHintBits(MultiXactId multi, uint16 *new_infomask,
 					   uint16 *new_infomask2);
 static TransactionId MultiXactIdGetUpdateXid(TransactionId xmax,
 						uint16 t_infomask);
-static bool DoesMultiXactIdConflict(MultiXactId multi, uint16 infomask,
-						LockTupleMode lockmode);
-static void MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
-				Relation rel, ItemPointer ctid, XLTW_Oper oper,
-				int *remaining);
-static bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
-						   uint16 infomask, Relation rel, int *remaining);
-static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
+static bool Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
+				   uint16 infomask, bool nowait,
+				   Relation rel, ItemPointer ctid, XLTW_Oper oper,
+				   int *remaining);
 static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_modified,
 					   bool *copy);
 
-
-/*
- * Each tuple lock mode has a corresponding heavyweight lock, and one or two
- * corresponding MultiXactStatuses (one to merely lock tuples, another one to
- * update them).  This table (and the macros below) helps us determine the
- * heavyweight lock mode and MultiXactStatus values to use for any particular
- * tuple lock strength.
- *
- * Don't look at lockstatus/updstatus directly!  Use get_mxact_status_for_lock
- * instead.
- */
-static const struct
-{
-	LOCKMODE	hwlock;
-	int			lockstatus;
-	int			updstatus;
-}
-
-			tupleLockExtraInfo[MaxLockTupleMode + 1] =
-{
-	{							/* LockTupleKeyShare */
-		AccessShareLock,
-		MultiXactStatusForKeyShare,
-		-1						/* KeyShare does not allow updating tuples */
-	},
-	{							/* LockTupleShare */
-		RowShareLock,
-		MultiXactStatusForShare,
-		-1						/* Share does not allow updating tuples */
-	},
-	{							/* LockTupleNoKeyExclusive */
-		ExclusiveLock,
-		MultiXactStatusForNoKeyUpdate,
-		MultiXactStatusNoKeyUpdate
-	},
-	{							/* LockTupleExclusive */
-		AccessExclusiveLock,
-		MultiXactStatusForUpdate,
-		MultiXactStatusUpdate
-	}
-};
-
-/* Get the LOCKMODE for a given MultiXactStatus */
-#define LOCKMODE_from_mxstatus(status) \
-			(tupleLockExtraInfo[TUPLOCK_from_mxstatus((status))].hwlock)
-
-/*
- * Acquire heavyweight locks on tuples, using a LockTupleMode strength value.
- * This is more readable than having every caller translate it to lock.h's
- * LOCKMODE.
- */
-#define LockTupleTuplock(rel, tup, mode) \
-	LockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
-#define UnlockTupleTuplock(rel, tup, mode) \
-	UnlockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
-#define ConditionalLockTupleTuplock(rel, tup, mode) \
-	ConditionalLockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
-
-/*
- * This table maps tuple lock strength values for each particular
- * MultiXactStatus value.
- */
-static const int MultiXactStatusLock[MaxMultiXactStatus + 1] =
-{
-	LockTupleKeyShare,			/* ForKeyShare */
-	LockTupleShare,				/* ForShare */
-	LockTupleNoKeyExclusive,	/* ForNoKeyUpdate */
-	LockTupleExclusive,			/* ForUpdate */
-	LockTupleNoKeyExclusive,	/* NoKeyUpdate */
-	LockTupleExclusive			/* Update */
-};
-
-/* Get the LockTupleMode for a given MultiXactStatus */
-#define TUPLOCK_from_mxstatus(status) \
-			(MultiXactStatusLock[(status)])
-
 /* ----------------------------------------------------------------
  *						 heap support routines
  * ----------------------------------------------------------------
@@ -1840,158 +1747,6 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 }
 
 /*
- *	heap_fetch		- retrieve tuple with given tid
- *
- * On entry, tuple->t_self is the TID to fetch.  We pin the buffer holding
- * the tuple, fill in the remaining fields of *tuple, and check the tuple
- * against the specified snapshot.
- *
- * If successful (tuple found and passes snapshot time qual), then *userbuf
- * is set to the buffer holding the tuple and TRUE is returned.  The caller
- * must unpin the buffer when done with the tuple.
- *
- * If the tuple is not found (ie, item number references a deleted slot),
- * then tuple->t_data is set to NULL and FALSE is returned.
- *
- * If the tuple is found but fails the time qual check, then FALSE is returned
- * but tuple->t_data is left pointing to the tuple.
- *
- * keep_buf determines what is done with the buffer in the FALSE-result cases.
- * When the caller specifies keep_buf = true, we retain the pin on the buffer
- * and return it in *userbuf (so the caller must eventually unpin it); when
- * keep_buf = false, the pin is released and *userbuf is set to InvalidBuffer.
- *
- * stats_relation is the relation to charge the heap_fetch operation against
- * for statistical purposes.  (This could be the heap rel itself, an
- * associated index, or NULL to not count the fetch at all.)
- *
- * heap_fetch does not follow HOT chains: only the exact TID requested will
- * be fetched.
- *
- * It is somewhat inconsistent that we ereport() on invalid block number but
- * return false on invalid item number.  There are a couple of reasons though.
- * One is that the caller can relatively easily check the block number for
- * validity, but cannot check the item number without reading the page
- * himself.  Another is that when we are following a t_ctid link, we can be
- * reasonably confident that the page number is valid (since VACUUM shouldn't
- * truncate off the destination page without having killed the referencing
- * tuple first), but the item number might well not be good.
- */
-bool
-heap_fetch(Relation relation,
-		   Snapshot snapshot,
-		   HeapTuple tuple,
-		   Buffer *userbuf,
-		   bool keep_buf,
-		   Relation stats_relation)
-{
-	ItemPointer tid = &(tuple->t_self);
-	ItemId		lp;
-	Buffer		buffer;
-	Page		page;
-	OffsetNumber offnum;
-	bool		valid;
-
-	/*
-	 * Fetch and pin the appropriate page of the relation.
-	 */
-	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
-
-	/*
-	 * Need share lock on buffer to examine tuple commit status.
-	 */
-	LockBuffer(buffer, BUFFER_LOCK_SHARE);
-	page = BufferGetPage(buffer);
-	TestForOldSnapshot(snapshot, relation, page);
-
-	/*
-	 * We'd better check for out-of-range offnum in case of VACUUM since the
-	 * TID was obtained.
-	 */
-	offnum = ItemPointerGetOffsetNumber(tid);
-	if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
-	{
-		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-		if (keep_buf)
-			*userbuf = buffer;
-		else
-		{
-			ReleaseBuffer(buffer);
-			*userbuf = InvalidBuffer;
-		}
-		tuple->t_data = NULL;
-		return false;
-	}
-
-	/*
-	 * get the item line pointer corresponding to the requested tid
-	 */
-	lp = PageGetItemId(page, offnum);
-
-	/*
-	 * Must check for deleted tuple.
-	 */
-	if (!ItemIdIsNormal(lp))
-	{
-		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-		if (keep_buf)
-			*userbuf = buffer;
-		else
-		{
-			ReleaseBuffer(buffer);
-			*userbuf = InvalidBuffer;
-		}
-		tuple->t_data = NULL;
-		return false;
-	}
-
-	/*
-	 * fill in *tuple fields
-	 */
-	tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	tuple->t_len = ItemIdGetLength(lp);
-	tuple->t_tableOid = RelationGetRelid(relation);
-
-	/*
-	 * check time qualification of tuple, then release lock
-	 */
-	valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, tuple, snapshot, buffer);
-
-	if (valid)
-		PredicateLockTuple(relation, tuple, snapshot);
-
-	CheckForSerializableConflictOut(valid, relation, tuple, buffer, snapshot);
-
-	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-
-	if (valid)
-	{
-		/*
-		 * All checks passed, so return the tuple as valid. Caller is now
-		 * responsible for releasing the buffer.
-		 */
-		*userbuf = buffer;
-
-		/* Count the successful fetch against appropriate rel, if any */
-		if (stats_relation != NULL)
-			pgstat_count_heap_fetch(stats_relation);
-
-		return true;
-	}
-
-	/* Tuple failed time qual, but maybe caller wants to see it anyway. */
-	if (keep_buf)
-		*userbuf = buffer;
-	else
-	{
-		ReleaseBuffer(buffer);
-		*userbuf = InvalidBuffer;
-	}
-
-	return false;
-}
-
-/*
  *	heap_hot_search_buffer	- search HOT chain for tuple satisfying snapshot
  *
  * On entry, *tid is the TID of a tuple (either a simple tuple, or the root
@@ -2171,130 +1926,6 @@ heap_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot,
 	return result;
 }
 
-/*
- *	heap_get_latest_tid -  get the latest tid of a specified tuple
- *
- * Actually, this gets the latest version that is visible according to
- * the passed snapshot.  You can pass SnapshotDirty to get the very latest,
- * possibly uncommitted version.
- *
- * *tid is both an input and an output parameter: it is updated to
- * show the latest version of the row.  Note that it will not be changed
- * if no version of the row passes the snapshot test.
- */
-void
-heap_get_latest_tid(Relation relation,
-					Snapshot snapshot,
-					ItemPointer tid)
-{
-	BlockNumber blk;
-	ItemPointerData ctid;
-	TransactionId priorXmax;
-
-	/* this is to avoid Assert failures on bad input */
-	if (!ItemPointerIsValid(tid))
-		return;
-
-	/*
-	 * Since this can be called with user-supplied TID, don't trust the input
-	 * too much.  (RelationGetNumberOfBlocks is an expensive check, so we
-	 * don't check t_ctid links again this way.  Note that it would not do to
-	 * call it just once and save the result, either.)
-	 */
-	blk = ItemPointerGetBlockNumber(tid);
-	if (blk >= RelationGetNumberOfBlocks(relation))
-		elog(ERROR, "block number %u is out of range for relation \"%s\"",
-			 blk, RelationGetRelationName(relation));
-
-	/*
-	 * Loop to chase down t_ctid links.  At top of loop, ctid is the tuple we
-	 * need to examine, and *tid is the TID we will return if ctid turns out
-	 * to be bogus.
-	 *
-	 * Note that we will loop until we reach the end of the t_ctid chain.
-	 * Depending on the snapshot passed, there might be at most one visible
-	 * version of the row, but we don't try to optimize for that.
-	 */
-	ctid = *tid;
-	priorXmax = InvalidTransactionId;	/* cannot check first XMIN */
-	for (;;)
-	{
-		Buffer		buffer;
-		Page		page;
-		OffsetNumber offnum;
-		ItemId		lp;
-		HeapTupleData tp;
-		bool		valid;
-
-		/*
-		 * Read, pin, and lock the page.
-		 */
-		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&ctid));
-		LockBuffer(buffer, BUFFER_LOCK_SHARE);
-		page = BufferGetPage(buffer);
-		TestForOldSnapshot(snapshot, relation, page);
-
-		/*
-		 * Check for bogus item number.  This is not treated as an error
-		 * condition because it can happen while following a t_ctid link. We
-		 * just assume that the prior tid is OK and return it unchanged.
-		 */
-		offnum = ItemPointerGetOffsetNumber(&ctid);
-		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-		lp = PageGetItemId(page, offnum);
-		if (!ItemIdIsNormal(lp))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-
-		/* OK to access the tuple */
-		tp.t_self = ctid;
-		tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-		tp.t_len = ItemIdGetLength(lp);
-		tp.t_tableOid = RelationGetRelid(relation);
-
-		/*
-		 * After following a t_ctid link, we might arrive at an unrelated
-		 * tuple.  Check for XMIN match.
-		 */
-		if (TransactionIdIsValid(priorXmax) &&
-			!TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(tp.t_data)))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-
-		/*
-		 * Check time qualification of tuple; if visible, set it as the new
-		 * result candidate.
-		 */
-		valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, &tp, snapshot, buffer);
-		CheckForSerializableConflictOut(valid, relation, &tp, buffer, snapshot);
-		if (valid)
-			*tid = ctid;
-
-		/*
-		 * If there's a valid t_ctid link, follow it, else we're done.
-		 */
-		if ((tp.t_data->t_infomask & HEAP_XMAX_INVALID) ||
-			HeapTupleHeaderIsOnlyLocked(tp.t_data) ||
-			ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-
-		ctid = tp.t_data->t_ctid;
-		priorXmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
-		UnlockReleaseBuffer(buffer);
-	}							/* end of loop */
-}
-
 
 /*
  * UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends
@@ -2312,7 +1943,7 @@ heap_get_latest_tid(Relation relation,
  *
  * Note this is not allowed for tuples whose xmax is a multixact.
  */
-static void
+void
 UpdateXmaxHintBits(HeapTupleHeader tuple, Buffer buffer, TransactionId xid)
 {
 	Assert(TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple), xid));
@@ -2595,7 +2226,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
  * tuple if not. Note that in any case, the header fields are also set in
  * the original tuple.
  */
-static HeapTuple
+HeapTuple
 heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
 					CommandId cid, int options)
 {
@@ -2663,412 +2294,110 @@ heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
 }
 
 /*
- *	heap_multi_insert	- insert multiple tuple into a heap
+ *	simple_heap_insert - insert a tuple
+ *
+ * Currently, this routine differs from heap_insert only in supplying
+ * a default command ID and not allowing access to the speedup options.
  *
- * This is like heap_insert(), but inserts multiple tuples in one operation.
- * That's faster than calling heap_insert() in a loop, because when multiple
- * tuples can be inserted on a single page, we can write just a single WAL
- * record covering all of them, and only need to lock/unlock the page once.
+ * This should be used rather than using heap_insert directly in most places
+ * where we are modifying system catalogs.
+ */
+Oid
+simple_heap_insert(Relation relation, HeapTuple tup)
+{
+	return heap_insert(relation, tup, GetCurrentCommandId(true), 0, NULL);
+}
+
+/*
+ * Given infomask/infomask2, compute the bits that must be saved in the
+ * "infobits" field of xl_heap_delete, xl_heap_update, xl_heap_lock,
+ * xl_heap_lock_updated WAL records.
  *
- * Note: this leaks memory into the current memory context. You can create a
- * temporary context before calling this, if that's a problem.
+ * See fix_infomask_from_infobits.
  */
-void
-heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
-				  CommandId cid, int options, BulkInsertState bistate)
+uint8
+compute_infobits(uint16 infomask, uint16 infomask2)
+{
+	return
+		((infomask & HEAP_XMAX_IS_MULTI) != 0 ? XLHL_XMAX_IS_MULTI : 0) |
+		((infomask & HEAP_XMAX_LOCK_ONLY) != 0 ? XLHL_XMAX_LOCK_ONLY : 0) |
+		((infomask & HEAP_XMAX_EXCL_LOCK) != 0 ? XLHL_XMAX_EXCL_LOCK : 0) |
+	/* note we ignore HEAP_XMAX_SHR_LOCK here */
+		((infomask & HEAP_XMAX_KEYSHR_LOCK) != 0 ? XLHL_XMAX_KEYSHR_LOCK : 0) |
+		((infomask2 & HEAP_KEYS_UPDATED) != 0 ?
+		 XLHL_KEYS_UPDATED : 0);
+}
+
+
+
+/*
+ *	heap_delete - delete a tuple
+ *
+ * NB: do not call this directly unless you are prepared to deal with
+ * concurrent-update conditions.  Use simple_heap_delete instead.
+ *
+ *	relation - table to be modified (caller must hold suitable lock)
+ *	tid - TID of tuple to be deleted
+ *	cid - delete command ID (used for visibility test, and stored into
+ *		cmax if successful)
+ *	crosscheck - if not InvalidSnapshot, also check tuple against this
+ *	wait - true if should wait for any conflicting update to commit/abort
+ *	hufd - output parameter, filled in failure cases (see below)
+ *
+ * Normal, successful return value is HeapTupleMayBeUpdated, which
+ * actually means we did delete it.  Failure return codes are
+ * HeapTupleSelfUpdated, HeapTupleUpdated, or HeapTupleBeingUpdated
+ * (the last only possible if wait == false).
+ *
+ * In the failure cases, 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.
+ */
+HTSU_Result
+heap_delete(Relation relation, ItemPointer tid,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			HeapUpdateFailureData *hufd)
 {
+	HTSU_Result result;
 	TransactionId xid = GetCurrentTransactionId();
-	HeapTuple  *heaptuples;
-	int			i;
-	int			ndone;
-	char	   *scratch = NULL;
+	ItemId		lp;
+	HeapTupleData tp;
 	Page		page;
-	bool		needwal;
-	Size		saveFreeSpace;
-	bool		need_tuple_data = RelationIsLogicallyLogged(relation);
-	bool		need_cids = RelationIsAccessibleInLogicalDecoding(relation);
-
-	needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation);
-	saveFreeSpace = RelationGetTargetPageFreeSpace(relation,
-												   HEAP_DEFAULT_FILLFACTOR);
+	BlockNumber block;
+	Buffer		buffer;
+	Buffer		vmbuffer = InvalidBuffer;
+	TransactionId new_xmax;
+	uint16		new_infomask,
+				new_infomask2;
+	bool		have_tuple_lock = false;
+	bool		iscombo;
+	bool		all_visible_cleared = false;
+	HeapTuple	old_key_tuple = NULL;	/* replica identity of the tuple */
+	bool		old_key_copied = false;
 
-	/* Toast and set header data in all the tuples */
-	heaptuples = palloc(ntuples * sizeof(HeapTuple));
-	for (i = 0; i < ntuples; i++)
-		heaptuples[i] = heap_prepare_insert(relation, tuples[i],
-											xid, cid, options);
+	Assert(ItemPointerIsValid(tid));
 
 	/*
-	 * Allocate some memory to use for constructing the WAL record. Using
-	 * palloc() within a critical section is not safe, so we allocate this
-	 * beforehand.
+	 * Forbid this during a parallel operation, lest it allocate a combocid.
+	 * Other workers might need that combocid for visibility checks, and we
+	 * have no provision for broadcasting it to them.
 	 */
-	if (needwal)
-		scratch = palloc(BLCKSZ);
+	if (IsInParallelMode())
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+				 errmsg("cannot delete tuples during a parallel operation")));
+
+	block = ItemPointerGetBlockNumber(tid);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
 
 	/*
-	 * We're about to do the actual inserts -- but check for conflict first,
-	 * to minimize the possibility of having to roll back work we've just
-	 * done.
-	 *
-	 * A check here does not definitively prevent a serialization anomaly;
-	 * that check MUST be done at least past the point of acquiring an
-	 * exclusive buffer content lock on every buffer that will be affected,
-	 * and MAY be done after all inserts are reflected in the buffers and
-	 * those locks are released; otherwise there race condition.  Since
-	 * multiple buffers can be locked and unlocked in the loop below, and it
-	 * would not be feasible to identify and lock all of those buffers before
-	 * the loop, we must do a final check at the end.
-	 *
-	 * The check here could be omitted with no loss of correctness; it is
-	 * present strictly as an optimization.
-	 *
-	 * For heap inserts, we only need to check for table-level SSI locks. Our
-	 * new tuples can't possibly conflict with existing tuple locks, and heap
-	 * page locks are only consolidated versions of tuple locks; they do not
-	 * lock "gaps" as index page locks do.  So we don't need to specify a
-	 * buffer when making the call, which makes for a faster check.
-	 */
-	CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
-
-	ndone = 0;
-	while (ndone < ntuples)
-	{
-		Buffer		buffer;
-		Buffer		vmbuffer = InvalidBuffer;
-		bool		all_visible_cleared = false;
-		int			nthispage;
-
-		CHECK_FOR_INTERRUPTS();
-
-		/*
-		 * Find buffer where at least the next tuple will fit.  If the page is
-		 * all-visible, this will also pin the requisite visibility map page.
-		 */
-		buffer = RelationGetBufferForTuple(relation, heaptuples[ndone]->t_len,
-										   InvalidBuffer, options, bistate,
-										   &vmbuffer, NULL);
-		page = BufferGetPage(buffer);
-
-		/* NO EREPORT(ERROR) from here till changes are logged */
-		START_CRIT_SECTION();
-
-		/*
-		 * RelationGetBufferForTuple has ensured that the first tuple fits.
-		 * Put that on the page, and then as many other tuples as fit.
-		 */
-		RelationPutHeapTuple(relation, buffer, heaptuples[ndone], false);
-		for (nthispage = 1; ndone + nthispage < ntuples; nthispage++)
-		{
-			HeapTuple	heaptup = heaptuples[ndone + nthispage];
-
-			if (PageGetHeapFreeSpace(page) < MAXALIGN(heaptup->t_len) + saveFreeSpace)
-				break;
-
-			RelationPutHeapTuple(relation, buffer, heaptup, false);
-
-			/*
-			 * We don't use heap_multi_insert for catalog tuples yet, but
-			 * better be prepared...
-			 */
-			if (needwal && need_cids)
-				log_heap_new_cid(relation, heaptup);
-		}
-
-		if (PageIsAllVisible(page))
-		{
-			all_visible_cleared = true;
-			PageClearAllVisible(page);
-			visibilitymap_clear(relation,
-								BufferGetBlockNumber(buffer),
-								vmbuffer, VISIBILITYMAP_VALID_BITS);
-		}
-
-		/*
-		 * XXX Should we set PageSetPrunable on this page ? See heap_insert()
-		 */
-
-		MarkBufferDirty(buffer);
-
-		/* XLOG stuff */
-		if (needwal)
-		{
-			XLogRecPtr	recptr;
-			xl_heap_multi_insert *xlrec;
-			uint8		info = XLOG_HEAP2_MULTI_INSERT;
-			char	   *tupledata;
-			int			totaldatalen;
-			char	   *scratchptr = scratch;
-			bool		init;
-			int			bufflags = 0;
-
-			/*
-			 * If the page was previously empty, we can reinit the page
-			 * instead of restoring the whole thing.
-			 */
-			init = (ItemPointerGetOffsetNumber(&(heaptuples[ndone]->t_self)) == FirstOffsetNumber &&
-					PageGetMaxOffsetNumber(page) == FirstOffsetNumber + nthispage - 1);
-
-			/* allocate xl_heap_multi_insert struct from the scratch area */
-			xlrec = (xl_heap_multi_insert *) scratchptr;
-			scratchptr += SizeOfHeapMultiInsert;
-
-			/*
-			 * Allocate offsets array. Unless we're reinitializing the page,
-			 * in that case the tuples are stored in order starting at
-			 * FirstOffsetNumber and we don't need to store the offsets
-			 * explicitly.
-			 */
-			if (!init)
-				scratchptr += nthispage * sizeof(OffsetNumber);
-
-			/* the rest of the scratch space is used for tuple data */
-			tupledata = scratchptr;
-
-			xlrec->flags = all_visible_cleared ? XLH_INSERT_ALL_VISIBLE_CLEARED : 0;
-			xlrec->ntuples = nthispage;
-
-			/*
-			 * Write out an xl_multi_insert_tuple and the tuple data itself
-			 * for each tuple.
-			 */
-			for (i = 0; i < nthispage; i++)
-			{
-				HeapTuple	heaptup = heaptuples[ndone + i];
-				xl_multi_insert_tuple *tuphdr;
-				int			datalen;
-
-				if (!init)
-					xlrec->offsets[i] = ItemPointerGetOffsetNumber(&heaptup->t_self);
-				/* xl_multi_insert_tuple needs two-byte alignment. */
-				tuphdr = (xl_multi_insert_tuple *) SHORTALIGN(scratchptr);
-				scratchptr = ((char *) tuphdr) + SizeOfMultiInsertTuple;
-
-				tuphdr->t_infomask2 = heaptup->t_data->t_infomask2;
-				tuphdr->t_infomask = heaptup->t_data->t_infomask;
-				tuphdr->t_hoff = heaptup->t_data->t_hoff;
-
-				/* write bitmap [+ padding] [+ oid] + data */
-				datalen = heaptup->t_len - SizeofHeapTupleHeader;
-				memcpy(scratchptr,
-					   (char *) heaptup->t_data + SizeofHeapTupleHeader,
-					   datalen);
-				tuphdr->datalen = datalen;
-				scratchptr += datalen;
-			}
-			totaldatalen = scratchptr - tupledata;
-			Assert((scratchptr - scratch) < BLCKSZ);
-
-			if (need_tuple_data)
-				xlrec->flags |= XLH_INSERT_CONTAINS_NEW_TUPLE;
-
-			/*
-			 * Signal that this is the last xl_heap_multi_insert record
-			 * emitted by this call to heap_multi_insert(). Needed for logical
-			 * decoding so it knows when to cleanup temporary data.
-			 */
-			if (ndone + nthispage == ntuples)
-				xlrec->flags |= XLH_INSERT_LAST_IN_MULTI;
-
-			if (init)
-			{
-				info |= XLOG_HEAP_INIT_PAGE;
-				bufflags |= REGBUF_WILL_INIT;
-			}
-
-			/*
-			 * If we're doing logical decoding, include the new tuple data
-			 * even if we take a full-page image of the page.
-			 */
-			if (need_tuple_data)
-				bufflags |= REGBUF_KEEP_DATA;
-
-			XLogBeginInsert();
-			XLogRegisterData((char *) xlrec, tupledata - scratch);
-			XLogRegisterBuffer(0, buffer, REGBUF_STANDARD | bufflags);
-
-			XLogRegisterBufData(0, tupledata, totaldatalen);
-
-			/* filtering by origin on a row level is much more efficient */
-			XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
-
-			recptr = XLogInsert(RM_HEAP2_ID, info);
-
-			PageSetLSN(page, recptr);
-		}
-
-		END_CRIT_SECTION();
-
-		UnlockReleaseBuffer(buffer);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
-
-		ndone += nthispage;
-	}
-
-	/*
-	 * We're done with the actual inserts.  Check for conflicts again, to
-	 * ensure that all rw-conflicts in to these inserts are detected.  Without
-	 * this final check, a sequential scan of the heap may have locked the
-	 * table after the "before" check, missing one opportunity to detect the
-	 * conflict, and then scanned the table before the new tuples were there,
-	 * missing the other chance to detect the conflict.
-	 *
-	 * For heap inserts, we only need to check for table-level SSI locks. Our
-	 * new tuples can't possibly conflict with existing tuple locks, and heap
-	 * page locks are only consolidated versions of tuple locks; they do not
-	 * lock "gaps" as index page locks do.  So we don't need to specify a
-	 * buffer when making the call.
-	 */
-	CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
-
-	/*
-	 * If tuples are cachable, mark them for invalidation from the caches in
-	 * case we abort.  Note it is OK to do this after releasing the buffer,
-	 * because the heaptuples data structure is all in local memory, not in
-	 * the shared buffer.
-	 */
-	if (IsCatalogRelation(relation))
-	{
-		for (i = 0; i < ntuples; i++)
-			CacheInvalidateHeapTuple(relation, heaptuples[i], NULL);
-	}
-
-	/*
-	 * Copy t_self fields back to the caller's original tuples. This does
-	 * nothing for untoasted tuples (tuples[i] == heaptuples[i)], but it's
-	 * probably faster to always copy than check.
-	 */
-	for (i = 0; i < ntuples; i++)
-		tuples[i]->t_self = heaptuples[i]->t_self;
-
-	pgstat_count_heap_insert(relation, ntuples);
-}
-
-/*
- *	simple_heap_insert - insert a tuple
- *
- * Currently, this routine differs from heap_insert only in supplying
- * a default command ID and not allowing access to the speedup options.
- *
- * This should be used rather than using heap_insert directly in most places
- * where we are modifying system catalogs.
- */
-Oid
-simple_heap_insert(Relation relation, HeapTuple tup)
-{
-	return heap_insert(relation, tup, GetCurrentCommandId(true), 0, NULL);
-}
-
-/*
- * Given infomask/infomask2, compute the bits that must be saved in the
- * "infobits" field of xl_heap_delete, xl_heap_update, xl_heap_lock,
- * xl_heap_lock_updated WAL records.
- *
- * See fix_infomask_from_infobits.
- */
-static uint8
-compute_infobits(uint16 infomask, uint16 infomask2)
-{
-	return
-		((infomask & HEAP_XMAX_IS_MULTI) != 0 ? XLHL_XMAX_IS_MULTI : 0) |
-		((infomask & HEAP_XMAX_LOCK_ONLY) != 0 ? XLHL_XMAX_LOCK_ONLY : 0) |
-		((infomask & HEAP_XMAX_EXCL_LOCK) != 0 ? XLHL_XMAX_EXCL_LOCK : 0) |
-	/* note we ignore HEAP_XMAX_SHR_LOCK here */
-		((infomask & HEAP_XMAX_KEYSHR_LOCK) != 0 ? XLHL_XMAX_KEYSHR_LOCK : 0) |
-		((infomask2 & HEAP_KEYS_UPDATED) != 0 ?
-		 XLHL_KEYS_UPDATED : 0);
-}
-
-/*
- * Given two versions of the same t_infomask for a tuple, compare them and
- * return whether the relevant status for a tuple Xmax has changed.  This is
- * used after a buffer lock has been released and reacquired: we want to ensure
- * that the tuple state continues to be the same it was when we previously
- * examined it.
- *
- * Note the Xmax field itself must be compared separately.
- */
-static inline bool
-xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask)
-{
-	const uint16 interesting =
-	HEAP_XMAX_IS_MULTI | HEAP_XMAX_LOCK_ONLY | HEAP_LOCK_MASK;
-
-	if ((new_infomask & interesting) != (old_infomask & interesting))
-		return true;
-
-	return false;
-}
-
-/*
- *	heap_delete - delete a tuple
- *
- * NB: do not call this directly unless you are prepared to deal with
- * concurrent-update conditions.  Use simple_heap_delete instead.
- *
- *	relation - table to be modified (caller must hold suitable lock)
- *	tid - TID of tuple to be deleted
- *	cid - delete command ID (used for visibility test, and stored into
- *		cmax if successful)
- *	crosscheck - if not InvalidSnapshot, also check tuple against this
- *	wait - true if should wait for any conflicting update to commit/abort
- *	hufd - output parameter, filled in failure cases (see below)
- *
- * Normal, successful return value is HeapTupleMayBeUpdated, which
- * actually means we did delete it.  Failure return codes are
- * HeapTupleSelfUpdated, HeapTupleUpdated, or HeapTupleBeingUpdated
- * (the last only possible if wait == false).
- *
- * In the failure cases, 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.
- */
-HTSU_Result
-heap_delete(Relation relation, ItemPointer tid,
-			CommandId cid, Snapshot crosscheck, bool wait,
-			HeapUpdateFailureData *hufd)
-{
-	HTSU_Result result;
-	TransactionId xid = GetCurrentTransactionId();
-	ItemId		lp;
-	HeapTupleData tp;
-	Page		page;
-	BlockNumber block;
-	Buffer		buffer;
-	Buffer		vmbuffer = InvalidBuffer;
-	TransactionId new_xmax;
-	uint16		new_infomask,
-				new_infomask2;
-	bool		have_tuple_lock = false;
-	bool		iscombo;
-	bool		all_visible_cleared = false;
-	HeapTuple	old_key_tuple = NULL;	/* replica identity of the tuple */
-	bool		old_key_copied = false;
-
-	Assert(ItemPointerIsValid(tid));
-
-	/*
-	 * Forbid this during a parallel operation, lest it allocate a combocid.
-	 * Other workers might need that combocid for visibility checks, and we
-	 * have no provision for broadcasting it to them.
-	 */
-	if (IsInParallelMode())
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
-				 errmsg("cannot delete tuples during a parallel operation")));
-
-	block = ItemPointerGetBlockNumber(tid);
-	buffer = ReadBuffer(relation, block);
-	page = BufferGetPage(buffer);
-
-	/*
-	 * Before locking the buffer, pin the visibility map page if it appears to
-	 * be necessary.  Since we haven't got the lock yet, someone else might be
-	 * in the middle of changing this, so we'll need to recheck after we have
-	 * the lock.
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
 	 */
 	if (PageIsAllVisible(page))
 		visibilitymap_pin(relation, block, &vmbuffer);
@@ -4503,7 +3832,7 @@ simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup)
 /*
  * Return the MultiXactStatus corresponding to the given tuple lock mode.
  */
-static MultiXactStatus
+MultiXactStatus
 get_mxact_status_for_lock(LockTupleMode mode, bool is_update)
 {
 	int			retval;
@@ -4521,724 +3850,34 @@ get_mxact_status_for_lock(LockTupleMode mode, bool is_update)
 }
 
 /*
- *	heap_lock_tuple - lock a tuple in shared or exclusive mode
- *
- * Note that this acquires a buffer pin, which the caller must release.
- *
- * Input parameters:
- *	relation: relation containing tuple (caller must hold suitable lock)
- *	tuple->t_self: TID of tuple to lock (rest of struct need not be valid)
- *	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
- *	follow_updates: if true, follow the update chain to also lock descendant
- *		tuples.
- *
- * Output parameters:
- *	*tuple: all fields filled in
- *	*buffer: set to buffer holding tuple (pinned but not locked at exit)
- *	*hufd: filled in failure cases (see below)
- *
- * 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
+ * Acquire heavyweight lock on the given tuple, in preparation for acquiring
+ * its normal, Xmax-based tuple lock.
  *
- * 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.
+ * have_tuple_lock is an input and output parameter: on input, it indicates
+ * whether the lock has previously been acquired (and this function does
+ * nothing in that case).  If this function returns success, have_tuple_lock
+ * has been flipped to true.
  *
- * See README.tuplock for a thorough explanation of this mechanism.
+ * Returns false if it was unable to obtain the lock; this can only happen if
+ * wait_policy is Skip.
  */
-HTSU_Result
-heap_lock_tuple(Relation relation, HeapTuple tuple,
-				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
-				bool follow_updates,
-				Buffer *buffer, HeapUpdateFailureData *hufd)
+bool
+heap_acquire_tuplock(Relation relation, ItemPointer tid, LockTupleMode mode,
+					 LockWaitPolicy wait_policy, bool *have_tuple_lock)
 {
-	HTSU_Result result;
-	ItemPointer tid = &(tuple->t_self);
-	ItemId		lp;
-	Page		page;
-	Buffer		vmbuffer = InvalidBuffer;
-	BlockNumber block;
-	TransactionId xid,
-				xmax;
-	uint16		old_infomask,
-				new_infomask,
-				new_infomask2;
-	bool		first_time = true;
-	bool		have_tuple_lock = false;
-	bool		cleared_all_frozen = false;
-
-	*buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
-	block = ItemPointerGetBlockNumber(tid);
-
-	/*
-	 * Before locking the buffer, pin the visibility map page if it appears to
-	 * be necessary.  Since we haven't got the lock yet, someone else might be
-	 * in the middle of changing this, so we'll need to recheck after we have
-	 * the lock.
-	 */
-	if (PageIsAllVisible(BufferGetPage(*buffer)))
-		visibilitymap_pin(relation, block, &vmbuffer);
-
-	LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-	page = BufferGetPage(*buffer);
-	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
-	Assert(ItemIdIsNormal(lp));
+	if (*have_tuple_lock)
+		return true;
 
-	tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	tuple->t_len = ItemIdGetLength(lp);
-	tuple->t_tableOid = RelationGetRelid(relation);
+	switch (wait_policy)
+	{
+		case LockWaitBlock:
+			LockTupleTuplock(relation, tid, mode);
+			break;
 
-l3:
-	result = relation->rd_stamroutine->snapshot_satisfiesUpdate(tuple, cid, *buffer);
-
-	if (result == HeapTupleInvisible)
-	{
-		/*
-		 * This is possible, but only when locking a tuple for ON CONFLICT
-		 * UPDATE.  We return this value here rather than throwing an error in
-		 * order to give that case the opportunity to throw a more specific
-		 * error.
-		 */
-		result = HeapTupleInvisible;
-		goto out_locked;
-	}
-	else if (result == HeapTupleBeingUpdated || result == HeapTupleUpdated)
-	{
-		TransactionId xwait;
-		uint16		infomask;
-		uint16		infomask2;
-		bool		require_sleep;
-		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);
-
-		LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
-
-		/*
-		 * If any subtransaction of the current top transaction already holds
-		 * a lock as strong as or stronger than what we're requesting, we
-		 * effectively hold the desired lock already.  We *must* succeed
-		 * without trying to take the tuple lock, else we will deadlock
-		 * against anyone wanting to acquire a stronger lock.
-		 *
-		 * Note we only do this the first time we loop on the HTSU result;
-		 * there is no point in testing in subsequent passes, because
-		 * evidently our own transaction cannot have acquired a new lock after
-		 * the first time we checked.
-		 */
-		if (first_time)
-		{
-			first_time = false;
-
-			if (infomask & HEAP_XMAX_IS_MULTI)
-			{
-				int			i;
-				int			nmembers;
-				MultiXactMember *members;
-
-				/*
-				 * We don't need to allow old multixacts here; if that had
-				 * been the case, HeapTupleSatisfiesUpdate would have returned
-				 * MayBeUpdated and we wouldn't be here.
-				 */
-				nmembers =
-					GetMultiXactIdMembers(xwait, &members, false,
-										  HEAP_XMAX_IS_LOCKED_ONLY(infomask));
-
-				for (i = 0; i < nmembers; i++)
-				{
-					/* only consider members of our own transaction */
-					if (!TransactionIdIsCurrentTransactionId(members[i].xid))
-						continue;
-
-					if (TUPLOCK_from_mxstatus(members[i].status) >= mode)
-					{
-						pfree(members);
-						result = HeapTupleMayBeUpdated;
-						goto out_unlocked;
-					}
-				}
-
-				if (members)
-					pfree(members);
-			}
-			else if (TransactionIdIsCurrentTransactionId(xwait))
-			{
-				switch (mode)
-				{
-					case LockTupleKeyShare:
-						Assert(HEAP_XMAX_IS_KEYSHR_LOCKED(infomask) ||
-							   HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
-							   HEAP_XMAX_IS_EXCL_LOCKED(infomask));
-						result = HeapTupleMayBeUpdated;
-						goto out_unlocked;
-					case LockTupleShare:
-						if (HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
-							HEAP_XMAX_IS_EXCL_LOCKED(infomask))
-						{
-							result = HeapTupleMayBeUpdated;
-							goto out_unlocked;
-						}
-						break;
-					case LockTupleNoKeyExclusive:
-						if (HEAP_XMAX_IS_EXCL_LOCKED(infomask))
-						{
-							result = HeapTupleMayBeUpdated;
-							goto out_unlocked;
-						}
-						break;
-					case LockTupleExclusive:
-						if (HEAP_XMAX_IS_EXCL_LOCKED(infomask) &&
-							infomask2 & HEAP_KEYS_UPDATED)
-						{
-							result = HeapTupleMayBeUpdated;
-							goto out_unlocked;
-						}
-						break;
-				}
-			}
-		}
-
-		/*
-		 * Initially assume that we will have to wait for the locking
-		 * transaction(s) to finish.  We check various cases below in which
-		 * this can be turned off.
-		 */
-		require_sleep = true;
-		if (mode == LockTupleKeyShare)
-		{
-			/*
-			 * If we're requesting KeyShare, and there's no update present, we
-			 * don't need to wait.  Even if there is an update, we can still
-			 * continue if the key hasn't been modified.
-			 *
-			 * However, if there are updates, we need to walk the update chain
-			 * to mark future versions of the row as locked, too.  That way,
-			 * if somebody deletes that future version, we're protected
-			 * against the key going away.  This locking of future versions
-			 * could block momentarily, if a concurrent transaction is
-			 * deleting a key; or it could return a value to the effect that
-			 * the transaction deleting the key has already committed.  So we
-			 * do this before re-locking the buffer; otherwise this would be
-			 * prone to deadlocks.
-			 *
-			 * Note that the TID we're locking was grabbed before we unlocked
-			 * the buffer.  For it to change while we're not looking, the
-			 * other properties we're testing for below after re-locking the
-			 * buffer would also change, in which case we would restart this
-			 * loop above.
-			 */
-			if (!(infomask2 & HEAP_KEYS_UPDATED))
-			{
-				bool		updated;
-
-				updated = !HEAP_XMAX_IS_LOCKED_ONLY(infomask);
-
-				/*
-				 * If there are updates, follow the update chain; bail out if
-				 * that cannot be done.
-				 */
-				if (follow_updates && updated)
-				{
-					HTSU_Result res;
-
-					res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
-												  GetCurrentTransactionId(),
-												  mode);
-					if (res != HeapTupleMayBeUpdated)
-					{
-						result = res;
-						/* recovery code expects to have buffer lock held */
-						LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-						goto failed;
-					}
-				}
-
-				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-				/*
-				 * Make sure it's still an appropriate lock, else start over.
-				 * Also, if it wasn't updated before we released the lock, but
-				 * is updated now, we start over too; the reason is that we
-				 * 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) ||
-					 !updated))
-					goto l3;
-
-				/* Things look okay, so we can skip sleeping */
-				require_sleep = false;
-
-				/*
-				 * Note we allow Xmax to change here; other updaters/lockers
-				 * could have modified it before we grabbed the buffer lock.
-				 * However, this is not a problem, because with the recheck we
-				 * just did we ensure that they still don't conflict with the
-				 * lock we want.
-				 */
-			}
-		}
-		else if (mode == LockTupleShare)
-		{
-			/*
-			 * If we're requesting Share, we can similarly avoid sleeping if
-			 * there's no update and no exclusive lock present.
-			 */
-			if (HEAP_XMAX_IS_LOCKED_ONLY(infomask) &&
-				!HEAP_XMAX_IS_EXCL_LOCKED(infomask))
-			{
-				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-				/*
-				 * 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))
-					goto l3;
-				require_sleep = false;
-			}
-		}
-		else if (mode == LockTupleNoKeyExclusive)
-		{
-			/*
-			 * If we're requesting NoKeyExclusive, we might also be able to
-			 * avoid sleeping; just ensure that there no conflicting lock
-			 * already acquired.
-			 */
-			if (infomask & HEAP_XMAX_IS_MULTI)
-			{
-				if (!DoesMultiXactIdConflict((MultiXactId) xwait, infomask,
-											 mode))
-				{
-					/*
-					 * No conflict, but if the xmax changed under us 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),
-											 xwait))
-						goto l3;
-
-					/* otherwise, we're good */
-					require_sleep = false;
-				}
-			}
-			else if (HEAP_XMAX_IS_KEYSHR_LOCKED(infomask))
-			{
-				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-				/* if the xmax changed in the meantime, start over */
-				if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
-					!TransactionIdEquals(
-										 HeapTupleHeaderGetRawXmax(tuple->t_data),
-										 xwait))
-					goto l3;
-				/* otherwise, we're good */
-				require_sleep = false;
-			}
-		}
-
-		/*
-		 * As a check independent from those above, we can also avoid sleeping
-		 * if the current transaction is the sole locker of the tuple.  Note
-		 * that the strength of the lock already held is irrelevant; this is
-		 * not about recording the lock in Xmax (which will be done regardless
-		 * of this optimization, below).  Also, note that the cases where we
-		 * hold a lock stronger than we are requesting are already handled
-		 * above by not doing anything.
-		 *
-		 * Note we only deal with the non-multixact case here; MultiXactIdWait
-		 * is well equipped to deal with this situation on its own.
-		 */
-		if (require_sleep && !(infomask & HEAP_XMAX_IS_MULTI) &&
-			TransactionIdIsCurrentTransactionId(xwait))
-		{
-			/* ... 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),
-									 xwait))
-				goto l3;
-			Assert(HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask));
-			require_sleep = false;
-		}
-
-		/*
-		 * Time to sleep on the other transaction/multixact, if necessary.
-		 *
-		 * If the other transaction is an update that's already committed,
-		 * then sleeping cannot possibly do any good: if we're required to
-		 * sleep, get out to raise an error instead.
-		 *
-		 * By here, we either have already acquired the buffer exclusive lock,
-		 * 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)
-		{
-			LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-			goto failed;
-		}
-		else if (require_sleep)
-		{
-			/*
-			 * Acquire tuple lock to establish our priority for the tuple, or
-			 * die trying.  LockTuple will release us when we are next-in-line
-			 * for the tuple.  We must do this even if we are share-locking.
-			 *
-			 * If we are forced to "start over" below, we keep the tuple lock;
-			 * this arranges that we stay at the head of the line while
-			 * rechecking tuple state.
-			 */
-			if (!heap_acquire_tuplock(relation, tid, mode, wait_policy,
-									  &have_tuple_lock))
-			{
-				/*
-				 * This can only happen if wait_policy is Skip and the lock
-				 * couldn't be obtained.
-				 */
-				result = HeapTupleWouldBlock;
-				/* recovery code expects to have buffer lock held */
-				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-				goto failed;
-			}
-
-			if (infomask & HEAP_XMAX_IS_MULTI)
-			{
-				MultiXactStatus status = get_mxact_status_for_lock(mode, false);
-
-				/* We only ever lock tuples, never update them */
-				if (status >= MultiXactStatusNoKeyUpdate)
-					elog(ERROR, "invalid lock mode in heap_lock_tuple");
-
-				/* wait for multixact to end, or die trying  */
-				switch (wait_policy)
-				{
-					case LockWaitBlock:
-						MultiXactIdWait((MultiXactId) xwait, status, infomask,
-										relation, &tuple->t_self, XLTW_Lock, NULL);
-						break;
-					case LockWaitSkip:
-						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
-														status, infomask, relation,
-														NULL))
-						{
-							result = HeapTupleWouldBlock;
-							/* recovery code expects to have buffer lock held */
-							LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-							goto failed;
-						}
-						break;
-					case LockWaitError:
-						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
-														status, infomask, relation,
-														NULL))
-							ereport(ERROR,
-									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-									 errmsg("could not obtain lock on row in relation \"%s\"",
-											RelationGetRelationName(relation))));
-
-						break;
-				}
-
-				/*
-				 * Of course, the multixact might not be done here: if we're
-				 * requesting a light lock mode, other transactions with light
-				 * locks could still be alive, as well as locks owned by our
-				 * own xact or other subxacts of this backend.  We need to
-				 * preserve the surviving MultiXact members.  Note that it
-				 * isn't absolutely necessary in the latter case, but doing so
-				 * is simpler.
-				 */
-			}
-			else
-			{
-				/* wait for regular transaction to end, or die trying */
-				switch (wait_policy)
-				{
-					case LockWaitBlock:
-						XactLockTableWait(xwait, relation, &tuple->t_self,
-										  XLTW_Lock);
-						break;
-					case LockWaitSkip:
-						if (!ConditionalXactLockTableWait(xwait))
-						{
-							result = HeapTupleWouldBlock;
-							/* recovery code expects to have buffer lock held */
-							LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-							goto failed;
-						}
-						break;
-					case LockWaitError:
-						if (!ConditionalXactLockTableWait(xwait))
-							ereport(ERROR,
-									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-									 errmsg("could not obtain lock on row in relation \"%s\"",
-											RelationGetRelationName(relation))));
-						break;
-				}
-			}
-
-			/* if there are updates, follow the update chain */
-			if (follow_updates && !HEAP_XMAX_IS_LOCKED_ONLY(infomask))
-			{
-				HTSU_Result res;
-
-				res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
-											  GetCurrentTransactionId(),
-											  mode);
-				if (res != HeapTupleMayBeUpdated)
-				{
-					result = res;
-					/* recovery code expects to have buffer lock held */
-					LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-					goto failed;
-				}
-			}
-
-			LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-			/*
-			 * xwait is done, but if xwait had just locked the tuple then some
-			 * 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),
-									 xwait))
-				goto l3;
-
-			if (!(infomask & HEAP_XMAX_IS_MULTI))
-			{
-				/*
-				 * Otherwise check if it committed or aborted.  Note we cannot
-				 * be here if the tuple was only locked by somebody who didn't
-				 * conflict with us; that would have been handled above.  So
-				 * that transaction must necessarily be gone by now.  But
-				 * don't check for this in the multixact case, because some
-				 * locker transactions might still be running.
-				 */
-				UpdateXmaxHintBits(tuple->t_data, *buffer, xwait);
-			}
-		}
-
-		/* By here, we're certain that we hold buffer exclusive lock again */
-
-		/*
-		 * We may lock if previous xmax aborted, or if it committed but only
-		 * locked the tuple without updating it; or if we didn't have to wait
-		 * 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))
-			result = HeapTupleMayBeUpdated;
-		else
-			result = HeapTupleUpdated;
-	}
-
-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);
-		if (result == HeapTupleSelfUpdated)
-			hufd->cmax = HeapTupleHeaderGetCmax(tuple->t_data);
-		else
-			hufd->cmax = InvalidCommandId;
-		goto out_locked;
-	}
-
-	/*
-	 * If we didn't pin the visibility map page and the page has become all
-	 * visible while we were busy locking the buffer, or during some
-	 * subsequent window during which we had it unlocked, we'll have to unlock
-	 * and re-lock, to avoid holding the buffer lock across I/O.  That's a bit
-	 * unfortunate, especially since we'll now have to recheck whether the
-	 * tuple has been locked or updated under us, but hopefully it won't
-	 * happen very often.
-	 */
-	if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
-	{
-		LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
-		visibilitymap_pin(relation, block, &vmbuffer);
-		LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-		goto l3;
-	}
-
-	xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
-	old_infomask = tuple->t_data->t_infomask;
-
-	/*
-	 * If this is the first possibly-multixact-able operation in the current
-	 * transaction, set my per-backend OldestMemberMXactId setting. We can be
-	 * certain that the transaction will never become a member of any older
-	 * MultiXactIds than that.  (We have to do this even if we end up just
-	 * using our own TransactionId below, since some other backend could
-	 * incorporate our XID into a MultiXact immediately afterwards.)
-	 */
-	MultiXactIdSetOldestMember();
-
-	/*
-	 * Compute the new xmax and infomask to store into the tuple.  Note we do
-	 * 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,
-							  GetCurrentTransactionId(), mode, false,
-							  &xid, &new_infomask, &new_infomask2);
-
-	START_CRIT_SECTION();
-
-	/*
-	 * Store transaction information of xact locking the tuple.
-	 *
-	 * Note: Cmax is meaningless in this context, so don't set it; this avoids
-	 * possibly generating a useless combo CID.  Moreover, if we're locking a
-	 * previously updated tuple, it's important to preserve the Cmax.
-	 *
-	 * 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;
-	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
-		HeapTupleHeaderClearHotUpdated(tuple->t_data);
-	HeapTupleHeaderSetXmax(tuple->t_data, xid);
-
-	/*
-	 * Make sure there is no forward chain link in t_ctid.  Note that in the
-	 * cases where the tuple has been updated, we must not overwrite t_ctid,
-	 * because it was set by the updater.  Moreover, if the tuple has been
-	 * updated, we need to follow the update chain to lock the new versions of
-	 * the tuple as well.
-	 */
-	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
-		tuple->t_data->t_ctid = *tid;
-
-	/* Clear only the all-frozen bit on visibility map if needed */
-	if (PageIsAllVisible(page) &&
-		visibilitymap_clear(relation, block, vmbuffer,
-							VISIBILITYMAP_ALL_FROZEN))
-		cleared_all_frozen = true;
-
-
-	MarkBufferDirty(*buffer);
-
-	/*
-	 * XLOG stuff.  You might think that we don't need an XLOG record because
-	 * there is no state change worth restoring after a crash.  You would be
-	 * wrong however: we have just written either a TransactionId or a
-	 * MultiXactId that may never have been seen on disk before, and we need
-	 * to make sure that there are XLOG entries covering those ID numbers.
-	 * Else the same IDs might be re-used after a crash, which would be
-	 * disastrous if this page made it to disk before the crash.  Essentially
-	 * we have to enforce the WAL log-before-data rule even in this case.
-	 * (Also, in a PITR log-shipping or 2PC environment, we have to have XLOG
-	 * entries for everything anyway.)
-	 */
-	if (RelationNeedsWAL(relation))
-	{
-		xl_heap_lock xlrec;
-		XLogRecPtr	recptr;
-
-		XLogBeginInsert();
-		XLogRegisterBuffer(0, *buffer, REGBUF_STANDARD);
-
-		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
-		xlrec.locking_xid = xid;
-		xlrec.infobits_set = compute_infobits(new_infomask,
-											  tuple->t_data->t_infomask2);
-		xlrec.flags = cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
-		XLogRegisterData((char *) &xlrec, SizeOfHeapLock);
-
-		/* we don't decode row locks atm, so no need to log the origin */
-
-		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_LOCK);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	result = HeapTupleMayBeUpdated;
-
-out_locked:
-	LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
-
-out_unlocked:
-	if (BufferIsValid(vmbuffer))
-		ReleaseBuffer(vmbuffer);
-
-	/*
-	 * Don't update the visibility map here. Locking a tuple doesn't change
-	 * visibility info.
-	 */
-
-	/*
-	 * Now that we have successfully marked the tuple as locked, we can
-	 * release the lmgr tuple lock, if we had it.
-	 */
-	if (have_tuple_lock)
-		UnlockTupleTuplock(relation, tid, mode);
-
-	return result;
-}
-
-/*
- * Acquire heavyweight lock on the given tuple, in preparation for acquiring
- * its normal, Xmax-based tuple lock.
- *
- * have_tuple_lock is an input and output parameter: on input, it indicates
- * whether the lock has previously been acquired (and this function does
- * nothing in that case).  If this function returns success, have_tuple_lock
- * has been flipped to true.
- *
- * Returns false if it was unable to obtain the lock; this can only happen if
- * wait_policy is Skip.
- */
-static bool
-heap_acquire_tuplock(Relation relation, ItemPointer tid, LockTupleMode mode,
-					 LockWaitPolicy wait_policy, bool *have_tuple_lock)
-{
-	if (*have_tuple_lock)
-		return true;
-
-	switch (wait_policy)
-	{
-		case LockWaitBlock:
-			LockTupleTuplock(relation, tid, mode);
-			break;
-
-		case LockWaitSkip:
-			if (!ConditionalLockTupleTuplock(relation, tid, mode))
-				return false;
-			break;
+		case LockWaitSkip:
+			if (!ConditionalLockTupleTuplock(relation, tid, mode))
+				return false;
+			break;
 
 		case LockWaitError:
 			if (!ConditionalLockTupleTuplock(relation, tid, mode))
@@ -5271,7 +3910,7 @@ heap_acquire_tuplock(Relation relation, ItemPointer tid, LockTupleMode mode,
  * window, but it's still possible to end up creating an unnecessary
  * MultiXactId.  Fortunately this is harmless.
  */
-static void
+void
 compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
 						  uint16 old_infomask2, TransactionId add_to_xmax,
 						  LockTupleMode mode, bool is_update,
@@ -5318,916 +3957,226 @@ l5:
 					break;
 				case LockTupleNoKeyExclusive:
 					new_xmax = add_to_xmax;
-					new_infomask |= HEAP_XMAX_EXCL_LOCK;
-					break;
-				case LockTupleExclusive:
-					new_xmax = add_to_xmax;
-					new_infomask |= HEAP_XMAX_EXCL_LOCK;
-					new_infomask2 |= HEAP_KEYS_UPDATED;
-					break;
-				default:
-					new_xmax = InvalidTransactionId;	/* silence compiler */
-					elog(ERROR, "invalid lock mode");
-			}
-		}
-	}
-	else if (old_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		MultiXactStatus new_status;
-
-		/*
-		 * Currently we don't allow XMAX_COMMITTED to be set for multis, so
-		 * cross-check.
-		 */
-		Assert(!(old_infomask & HEAP_XMAX_COMMITTED));
-
-		/*
-		 * A multixact together with LOCK_ONLY set but neither lock bit set
-		 * (i.e. a pg_upgraded share locked tuple) cannot possibly be running
-		 * anymore.  This check is critical for databases upgraded by
-		 * pg_upgrade; both MultiXactIdIsRunning and MultiXactIdExpand assume
-		 * that such multis are never passed.
-		 */
-		if (HEAP_LOCKED_UPGRADED(old_infomask))
-		{
-			old_infomask &= ~HEAP_XMAX_IS_MULTI;
-			old_infomask |= HEAP_XMAX_INVALID;
-			goto l5;
-		}
-
-		/*
-		 * If the XMAX is already a MultiXactId, then we need to expand it to
-		 * include add_to_xmax; but if all the members were lockers and are
-		 * all gone, we can do away with the IS_MULTI bit and just set
-		 * add_to_xmax as the only locker/updater.  If all lockers are gone
-		 * and we have an updater that aborted, we can also do without a
-		 * multi.
-		 *
-		 * The cost of doing GetMultiXactIdMembers would be paid by
-		 * MultiXactIdExpand if we weren't to do this, so this check is not
-		 * incurring extra work anyhow.
-		 */
-		if (!MultiXactIdIsRunning(xmax, HEAP_XMAX_IS_LOCKED_ONLY(old_infomask)))
-		{
-			if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) ||
-				!TransactionIdDidCommit(MultiXactIdGetUpdateXid(xmax,
-																old_infomask)))
-			{
-				/*
-				 * Reset these bits and restart; otherwise fall through to
-				 * create a new multi below.
-				 */
-				old_infomask &= ~HEAP_XMAX_IS_MULTI;
-				old_infomask |= HEAP_XMAX_INVALID;
-				goto l5;
-			}
-		}
-
-		new_status = get_mxact_status_for_lock(mode, is_update);
-
-		new_xmax = MultiXactIdExpand((MultiXactId) xmax, add_to_xmax,
-									 new_status);
-		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
-	}
-	else if (old_infomask & HEAP_XMAX_COMMITTED)
-	{
-		/*
-		 * It's a committed update, so we need to preserve him as updater of
-		 * the tuple.
-		 */
-		MultiXactStatus status;
-		MultiXactStatus new_status;
-
-		if (old_infomask2 & HEAP_KEYS_UPDATED)
-			status = MultiXactStatusUpdate;
-		else
-			status = MultiXactStatusNoKeyUpdate;
-
-		new_status = get_mxact_status_for_lock(mode, is_update);
-
-		/*
-		 * since it's not running, it's obviously impossible for the old
-		 * updater to be identical to the current one, so we need not check
-		 * for that case as we do in the block above.
-		 */
-		new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
-		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
-	}
-	else if (TransactionIdIsInProgress(xmax))
-	{
-		/*
-		 * If the XMAX is a valid, in-progress TransactionId, then we need to
-		 * create a new MultiXactId that includes both the old locker or
-		 * updater and our own TransactionId.
-		 */
-		MultiXactStatus new_status;
-		MultiXactStatus old_status;
-		LockTupleMode old_mode;
-
-		if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
-		{
-			if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
-				old_status = MultiXactStatusForKeyShare;
-			else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
-				old_status = MultiXactStatusForShare;
-			else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
-			{
-				if (old_infomask2 & HEAP_KEYS_UPDATED)
-					old_status = MultiXactStatusForUpdate;
-				else
-					old_status = MultiXactStatusForNoKeyUpdate;
-			}
-			else
-			{
-				/*
-				 * LOCK_ONLY can be present alone only when a page has been
-				 * upgraded by pg_upgrade.  But in that case,
-				 * TransactionIdIsInProgress() should have returned false.  We
-				 * assume it's no longer locked in this case.
-				 */
-				elog(WARNING, "LOCK_ONLY found for Xid in progress %u", xmax);
-				old_infomask |= HEAP_XMAX_INVALID;
-				old_infomask &= ~HEAP_XMAX_LOCK_ONLY;
-				goto l5;
-			}
-		}
-		else
-		{
-			/* it's an update, but which kind? */
-			if (old_infomask2 & HEAP_KEYS_UPDATED)
-				old_status = MultiXactStatusUpdate;
-			else
-				old_status = MultiXactStatusNoKeyUpdate;
-		}
-
-		old_mode = TUPLOCK_from_mxstatus(old_status);
-
-		/*
-		 * If the lock to be acquired is for the same TransactionId as the
-		 * existing lock, there's an optimization possible: consider only the
-		 * strongest of both locks as the only one present, and restart.
-		 */
-		if (xmax == add_to_xmax)
-		{
-			/*
-			 * Note that it's not possible for the original tuple to be
-			 * updated: we wouldn't be here because the tuple would have been
-			 * invisible and we wouldn't try to update it.  As a subtlety,
-			 * this code can also run when traversing an update chain to lock
-			 * future versions of a tuple.  But we wouldn't be here either,
-			 * because the add_to_xmax would be different from the original
-			 * updater.
-			 */
-			Assert(HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
-
-			/* acquire the strongest of both */
-			if (mode < old_mode)
-				mode = old_mode;
-			/* mustn't touch is_update */
-
-			old_infomask |= HEAP_XMAX_INVALID;
-			goto l5;
-		}
-
-		/* otherwise, just fall back to creating a new multixact */
-		new_status = get_mxact_status_for_lock(mode, is_update);
-		new_xmax = MultiXactIdCreate(xmax, old_status,
-									 add_to_xmax, new_status);
-		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
-	}
-	else if (!HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) &&
-			 TransactionIdDidCommit(xmax))
-	{
-		/*
-		 * It's a committed update, so we gotta preserve him as updater of the
-		 * tuple.
-		 */
-		MultiXactStatus status;
-		MultiXactStatus new_status;
-
-		if (old_infomask2 & HEAP_KEYS_UPDATED)
-			status = MultiXactStatusUpdate;
-		else
-			status = MultiXactStatusNoKeyUpdate;
-
-		new_status = get_mxact_status_for_lock(mode, is_update);
-
-		/*
-		 * since it's not running, it's obviously impossible for the old
-		 * updater to be identical to the current one, so we need not check
-		 * for that case as we do in the block above.
-		 */
-		new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
-		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
-	}
-	else
-	{
-		/*
-		 * Can get here iff the locking/updating transaction was running when
-		 * the infomask was extracted from the tuple, but finished before
-		 * TransactionIdIsInProgress got to run.  Deal with it as if there was
-		 * no locker at all in the first place.
-		 */
-		old_infomask |= HEAP_XMAX_INVALID;
-		goto l5;
-	}
-
-	*result_infomask = new_infomask;
-	*result_infomask2 = new_infomask2;
-	*result_xmax = new_xmax;
-}
-
-/*
- * Subroutine for heap_lock_updated_tuple_rec.
- *
- * Given a hypothetical multixact status held by the transaction identified
- * with the given xid, does the current transaction need to wait, fail, or can
- * it continue if it wanted to acquire a lock of the given mode?  "needwait"
- * is set to true if waiting is necessary; if it can continue, then
- * HeapTupleMayBeUpdated is returned.  If the lock is already held by the
- * current transaction, return HeapTupleSelfUpdated.  In case of a conflict
- * with another transaction, a different HeapTupleSatisfiesUpdate return code
- * is returned.
- *
- * The held status is said to be hypothetical because it might correspond to a
- * lock held by a single Xid, i.e. not a real MultiXactId; we express it this
- * way for simplicity of API.
- */
-static HTSU_Result
-test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid,
-						   LockTupleMode mode, bool *needwait)
-{
-	MultiXactStatus wantedstatus;
-
-	*needwait = false;
-	wantedstatus = get_mxact_status_for_lock(mode, false);
-
-	/*
-	 * Note: we *must* check TransactionIdIsInProgress before
-	 * TransactionIdDidAbort/Commit; see comment at top of tqual.c for an
-	 * explanation.
-	 */
-	if (TransactionIdIsCurrentTransactionId(xid))
-	{
-		/*
-		 * The tuple has already been locked by our own transaction.  This is
-		 * very rare but can happen if multiple transactions are trying to
-		 * lock an ancient version of the same tuple.
-		 */
-		return HeapTupleSelfUpdated;
-	}
-	else if (TransactionIdIsInProgress(xid))
-	{
-		/*
-		 * If the locking transaction is running, what we do depends on
-		 * whether the lock modes conflict: if they do, then we must wait for
-		 * it to finish; otherwise we can fall through to lock this tuple
-		 * version without waiting.
-		 */
-		if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
-								LOCKMODE_from_mxstatus(wantedstatus)))
-		{
-			*needwait = true;
-		}
-
-		/*
-		 * If we set needwait above, then this value doesn't matter;
-		 * otherwise, this value signals to caller that it's okay to proceed.
-		 */
-		return HeapTupleMayBeUpdated;
-	}
-	else if (TransactionIdDidAbort(xid))
-		return HeapTupleMayBeUpdated;
-	else if (TransactionIdDidCommit(xid))
-	{
-		/*
-		 * The other transaction committed.  If it was only a locker, then the
-		 * lock is completely gone now and we can return success; but if it
-		 * was an update, then what we do depends on whether the two lock
-		 * modes conflict.  If they conflict, then we must report error to
-		 * caller. But if they don't, we can fall through to allow the current
-		 * transaction to lock the tuple.
-		 *
-		 * Note: the reason we worry about ISUPDATE here is because as soon as
-		 * a transaction ends, all its locks are gone and meaningless, and
-		 * thus we can ignore them; whereas its updates persist.  In the
-		 * TransactionIdIsInProgress case, above, we don't need to check
-		 * because we know the lock is still "alive" and thus a conflict needs
-		 * always be checked.
-		 */
-		if (!ISUPDATE_from_mxstatus(status))
-			return HeapTupleMayBeUpdated;
-
-		if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
-								LOCKMODE_from_mxstatus(wantedstatus)))
-			/* bummer */
-			return HeapTupleUpdated;
-
-		return HeapTupleMayBeUpdated;
-	}
-
-	/* Not in progress, not aborted, not committed -- must have crashed */
-	return HeapTupleMayBeUpdated;
-}
-
-
-/*
- * Recursive part of heap_lock_updated_tuple
- *
- * Fetch the tuple pointed to by tid in rel, and mark it as locked by the given
- * xid with the given mode; if this tuple is updated, recurse to lock the new
- * version as well.
- */
-static HTSU_Result
-heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid,
-							LockTupleMode mode)
-{
-	HTSU_Result result;
-	ItemPointerData tupid;
-	HeapTupleData mytup;
-	Buffer		buf;
-	uint16		new_infomask,
-				new_infomask2,
-				old_infomask,
-				old_infomask2;
-	TransactionId xmax,
-				new_xmax;
-	TransactionId priorXmax = InvalidTransactionId;
-	bool		cleared_all_frozen = false;
-	Buffer		vmbuffer = InvalidBuffer;
-	BlockNumber block;
-
-	ItemPointerCopy(tid, &tupid);
-
-	for (;;)
-	{
-		new_infomask = 0;
-		new_xmax = InvalidTransactionId;
-		block = ItemPointerGetBlockNumber(&tupid);
-		ItemPointerCopy(&tupid, &(mytup.t_self));
-
-		if (!heap_fetch(rel, SnapshotAny, &mytup, &buf, false, NULL))
-		{
-			/*
-			 * if we fail to find the updated version of the tuple, it's
-			 * because it was vacuumed/pruned away after its creator
-			 * transaction aborted.  So behave as if we got to the end of the
-			 * chain, and there's no further tuple to lock: return success to
-			 * caller.
-			 */
-			return HeapTupleMayBeUpdated;
-		}
-
-l4:
-		CHECK_FOR_INTERRUPTS();
-
-		/*
-		 * Before locking the buffer, pin the visibility map page if it
-		 * appears to be necessary.  Since we haven't got the lock yet,
-		 * someone else might be in the middle of changing this, so we'll need
-		 * to recheck after we have the lock.
-		 */
-		if (PageIsAllVisible(BufferGetPage(buf)))
-			visibilitymap_pin(rel, block, &vmbuffer);
-		else
-			vmbuffer = InvalidBuffer;
-
-		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
-
-		/*
-		 * If we didn't pin the visibility map page and the page has become
-		 * all visible while we were busy locking the buffer, we'll have to
-		 * unlock and re-lock, to avoid holding the buffer lock across I/O.
-		 * That's a bit unfortunate, but hopefully shouldn't happen often.
-		 */
-		if (vmbuffer == InvalidBuffer && PageIsAllVisible(BufferGetPage(buf)))
-		{
-			LockBuffer(buf, BUFFER_LOCK_UNLOCK);
-			visibilitymap_pin(rel, block, &vmbuffer);
-			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
-		}
-
-		/*
-		 * Check the tuple XMIN against prior XMAX, if any.  If we reached the
-		 * end of the chain, we're done, so return success.
-		 */
-		if (TransactionIdIsValid(priorXmax) &&
-			!TransactionIdEquals(HeapTupleHeaderGetXmin(mytup.t_data),
-								 priorXmax))
-		{
-			result = HeapTupleMayBeUpdated;
-			goto out_locked;
-		}
-
-		/*
-		 * Also check Xmin: if this tuple was created by an aborted
-		 * (sub)transaction, then we already locked the last live one in the
-		 * chain, thus we're done, so return success.
-		 */
-		if (TransactionIdDidAbort(HeapTupleHeaderGetXmin(mytup.t_data)))
-		{
-			UnlockReleaseBuffer(buf);
-			return HeapTupleMayBeUpdated;
-		}
-
-		old_infomask = mytup.t_data->t_infomask;
-		old_infomask2 = mytup.t_data->t_infomask2;
-		xmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
-
-		/*
-		 * If this tuple version has been updated or locked by some concurrent
-		 * transaction(s), what we do depends on whether our lock mode
-		 * conflicts with what those other transactions hold, and also on the
-		 * status of them.
-		 */
-		if (!(old_infomask & HEAP_XMAX_INVALID))
-		{
-			TransactionId rawxmax;
-			bool		needwait;
-
-			rawxmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
-			if (old_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				int			nmembers;
-				int			i;
-				MultiXactMember *members;
-
-				/*
-				 * We don't need a test for pg_upgrade'd tuples: this is only
-				 * applied to tuples after the first in an update chain.  Said
-				 * first tuple in the chain may well be locked-in-9.2-and-
-				 * pg_upgraded, but that one was already locked by our caller,
-				 * not us; and any subsequent ones cannot be because our
-				 * caller must necessarily have obtained a snapshot later than
-				 * the pg_upgrade itself.
-				 */
-				Assert(!HEAP_LOCKED_UPGRADED(mytup.t_data->t_infomask));
-
-				nmembers = GetMultiXactIdMembers(rawxmax, &members, false,
-												 HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
-				for (i = 0; i < nmembers; i++)
-				{
-					result = test_lockmode_for_conflict(members[i].status,
-														members[i].xid,
-														mode, &needwait);
-
-					/*
-					 * If the tuple was already locked by ourselves in a
-					 * previous iteration of this (say heap_lock_tuple was
-					 * forced to restart the locking loop because of a change
-					 * in xmax), then we hold the lock already on this tuple
-					 * version and we don't need to do anything; and this is
-					 * not an error condition either.  We just need to skip
-					 * this tuple and continue locking the next version in the
-					 * update chain.
-					 */
-					if (result == HeapTupleSelfUpdated)
-					{
-						pfree(members);
-						goto next;
-					}
-
-					if (needwait)
-					{
-						LockBuffer(buf, BUFFER_LOCK_UNLOCK);
-						XactLockTableWait(members[i].xid, rel,
-										  &mytup.t_self,
-										  XLTW_LockUpdated);
-						pfree(members);
-						goto l4;
-					}
-					if (result != HeapTupleMayBeUpdated)
-					{
-						pfree(members);
-						goto out_locked;
-					}
-				}
-				if (members)
-					pfree(members);
-			}
-			else
-			{
-				MultiXactStatus status;
-
-				/*
-				 * For a non-multi Xmax, we first need to compute the
-				 * corresponding MultiXactStatus by using the infomask bits.
-				 */
-				if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
-				{
-					if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
-						status = MultiXactStatusForKeyShare;
-					else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
-						status = MultiXactStatusForShare;
-					else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
-					{
-						if (old_infomask2 & HEAP_KEYS_UPDATED)
-							status = MultiXactStatusForUpdate;
-						else
-							status = MultiXactStatusForNoKeyUpdate;
-					}
-					else
-					{
-						/*
-						 * LOCK_ONLY present alone (a pg_upgraded tuple marked
-						 * as share-locked in the old cluster) shouldn't be
-						 * seen in the middle of an update chain.
-						 */
-						elog(ERROR, "invalid lock status in tuple");
-					}
-				}
-				else
-				{
-					/* it's an update, but which kind? */
-					if (old_infomask2 & HEAP_KEYS_UPDATED)
-						status = MultiXactStatusUpdate;
-					else
-						status = MultiXactStatusNoKeyUpdate;
-				}
-
-				result = test_lockmode_for_conflict(status, rawxmax, mode,
-													&needwait);
-
-				/*
-				 * If the tuple was already locked by ourselves in a previous
-				 * iteration of this (say heap_lock_tuple was forced to
-				 * restart the locking loop because of a change in xmax), then
-				 * we hold the lock already on this tuple version and we don't
-				 * need to do anything; and this is not an error condition
-				 * either.  We just need to skip this tuple and continue
-				 * locking the next version in the update chain.
-				 */
-				if (result == HeapTupleSelfUpdated)
-					goto next;
-
-				if (needwait)
-				{
-					LockBuffer(buf, BUFFER_LOCK_UNLOCK);
-					XactLockTableWait(rawxmax, rel, &mytup.t_self,
-									  XLTW_LockUpdated);
-					goto l4;
-				}
-				if (result != HeapTupleMayBeUpdated)
-				{
-					goto out_locked;
-				}
-			}
-		}
-
-		/* compute the new Xmax and infomask values for the tuple ... */
-		compute_new_xmax_infomask(xmax, old_infomask, mytup.t_data->t_infomask2,
-								  xid, mode, false,
-								  &new_xmax, &new_infomask, &new_infomask2);
-
-		if (PageIsAllVisible(BufferGetPage(buf)) &&
-			visibilitymap_clear(rel, block, vmbuffer,
-								VISIBILITYMAP_ALL_FROZEN))
-			cleared_all_frozen = true;
-
-		START_CRIT_SECTION();
-
-		/* ... and set them */
-		HeapTupleHeaderSetXmax(mytup.t_data, new_xmax);
-		mytup.t_data->t_infomask &= ~HEAP_XMAX_BITS;
-		mytup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
-		mytup.t_data->t_infomask |= new_infomask;
-		mytup.t_data->t_infomask2 |= new_infomask2;
-
-		MarkBufferDirty(buf);
-
-		/* XLOG stuff */
-		if (RelationNeedsWAL(rel))
-		{
-			xl_heap_lock_updated xlrec;
-			XLogRecPtr	recptr;
-			Page		page = BufferGetPage(buf);
-
-			XLogBeginInsert();
-			XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
-
-			xlrec.offnum = ItemPointerGetOffsetNumber(&mytup.t_self);
-			xlrec.xmax = new_xmax;
-			xlrec.infobits_set = compute_infobits(new_infomask, new_infomask2);
-			xlrec.flags =
-				cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
-
-			XLogRegisterData((char *) &xlrec, SizeOfHeapLockUpdated);
-
-			recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_LOCK_UPDATED);
-
-			PageSetLSN(page, recptr);
-		}
-
-		END_CRIT_SECTION();
-
-next:
-		/* if we find the end of update chain, we're done. */
-		if (mytup.t_data->t_infomask & HEAP_XMAX_INVALID ||
-			ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid) ||
-			HeapTupleHeaderIsOnlyLocked(mytup.t_data))
-		{
-			result = HeapTupleMayBeUpdated;
-			goto out_locked;
-		}
-
-		/* tail recursion */
-		priorXmax = HeapTupleHeaderGetUpdateXid(mytup.t_data);
-		ItemPointerCopy(&(mytup.t_data->t_ctid), &tupid);
-		UnlockReleaseBuffer(buf);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
-	}
-
-	result = HeapTupleMayBeUpdated;
-
-out_locked:
-	UnlockReleaseBuffer(buf);
-
-	if (vmbuffer != InvalidBuffer)
-		ReleaseBuffer(vmbuffer);
-
-	return result;
-
-}
-
-/*
- * heap_lock_updated_tuple
- *		Follow update chain when locking an updated tuple, acquiring locks (row
- *		marks) on the updated versions.
- *
- * The initial tuple is assumed to be already locked.
- *
- * This function doesn't check visibility, it just unconditionally marks the
- * tuple(s) as locked.  If any tuple in the updated chain is being deleted
- * concurrently (or updated with the key being modified), sleep until the
- * transaction doing it is finished.
- *
- * Note that we don't acquire heavyweight tuple locks on the tuples we walk
- * when we have to wait for other transactions to release them, as opposed to
- * what heap_lock_tuple does.  The reason is that having more than one
- * transaction walking the chain is probably uncommon enough that risk of
- * starvation is not likely: one of the preconditions for being here is that
- * the snapshot in use predates the update that created this tuple (because we
- * started at an earlier version of the tuple), but at the same time such a
- * transaction cannot be using repeatable read or serializable isolation
- * levels, because that would lead to a serializability failure.
- */
-static HTSU_Result
-heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid,
-						TransactionId xid, LockTupleMode mode)
-{
-	if (!ItemPointerEquals(&tuple->t_self, ctid))
-	{
-		/*
-		 * If this is the first possibly-multixact-able operation in the
-		 * current transaction, set my per-backend OldestMemberMXactId
-		 * setting. We can be certain that the transaction will never become a
-		 * member of any older MultiXactIds than that.  (We have to do this
-		 * even if we end up just using our own TransactionId below, since
-		 * some other backend could incorporate our XID into a MultiXact
-		 * immediately afterwards.)
-		 */
-		MultiXactIdSetOldestMember();
-
-		return heap_lock_updated_tuple_rec(rel, ctid, xid, mode);
-	}
-
-	/* nothing to lock */
-	return HeapTupleMayBeUpdated;
-}
-
-/*
- *	heap_finish_speculative - mark speculative insertion as successful
- *
- * To successfully finish a speculative insertion we have to clear speculative
- * token from tuple.  To do so the t_ctid field, which will contain a
- * speculative token value, is modified in place to point to the tuple itself,
- * which is characteristic of a newly inserted ordinary tuple.
- *
- * NB: It is not ok to commit without either finishing or aborting a
- * speculative insertion.  We could treat speculative tuples of committed
- * transactions implicitly as completed, but then we would have to be prepared
- * to deal with speculative tokens on committed tuples.  That wouldn't be
- * difficult - no-one looks at the ctid field of a tuple with invalid xmax -
- * but clearing the token at completion isn't very expensive either.
- * An explicit confirmation WAL record also makes logical decoding simpler.
- */
-void
-heap_finish_speculative(Relation relation, HeapTuple tuple)
-{
-	Buffer		buffer;
-	Page		page;
-	OffsetNumber offnum;
-	ItemId		lp = NULL;
-	HeapTupleHeader htup;
-
-	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&(tuple->t_self)));
-	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
-	page = (Page) BufferGetPage(buffer);
-
-	offnum = ItemPointerGetOffsetNumber(&(tuple->t_self));
-	if (PageGetMaxOffsetNumber(page) >= offnum)
-		lp = PageGetItemId(page, offnum);
-
-	if (PageGetMaxOffsetNumber(page) < offnum || !ItemIdIsNormal(lp))
-		elog(ERROR, "invalid lp");
-
-	htup = (HeapTupleHeader) PageGetItem(page, lp);
-
-	/* SpecTokenOffsetNumber should be distinguishable from any real offset */
-	StaticAssertStmt(MaxOffsetNumber < SpecTokenOffsetNumber,
-					 "invalid speculative token constant");
-
-	/* NO EREPORT(ERROR) from here till changes are logged */
-	START_CRIT_SECTION();
-
-	Assert(HeapTupleHeaderIsSpeculative(tuple->t_data));
-
-	MarkBufferDirty(buffer);
-
-	/*
-	 * Replace the speculative insertion token with a real t_ctid, pointing to
-	 * itself like it does on regular tuples.
-	 */
-	htup->t_ctid = tuple->t_self;
-
-	/* XLOG stuff */
-	if (RelationNeedsWAL(relation))
-	{
-		xl_heap_confirm xlrec;
-		XLogRecPtr	recptr;
-
-		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
-
-		XLogBeginInsert();
-
-		/* We want the same filtering on this as on a plain insert */
-		XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
-
-		XLogRegisterData((char *) &xlrec, SizeOfHeapConfirm);
-		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
-
-		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_CONFIRM);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	UnlockReleaseBuffer(buffer);
-}
-
-/*
- *	heap_abort_speculative - kill a speculatively inserted tuple
- *
- * Marks a tuple that was speculatively inserted in the same command as dead,
- * by setting its xmin as invalid.  That makes it immediately appear as dead
- * to all transactions, including our own.  In particular, it makes
- * HeapTupleSatisfiesDirty() regard the tuple as dead, so that another backend
- * inserting a duplicate key value won't unnecessarily wait for our whole
- * transaction to finish (it'll just wait for our speculative insertion to
- * finish).
- *
- * Killing the tuple prevents "unprincipled deadlocks", which are deadlocks
- * that arise due to a mutual dependency that is not user visible.  By
- * definition, unprincipled deadlocks cannot be prevented by the user
- * reordering lock acquisition in client code, because the implementation level
- * lock acquisitions are not under the user's direct control.  If speculative
- * inserters did not take this precaution, then under high concurrency they
- * could deadlock with each other, which would not be acceptable.
- *
- * This is somewhat redundant with heap_delete, but we prefer to have a
- * dedicated routine with stripped down requirements.  Note that this is also
- * used to delete the TOAST tuples created during speculative insertion.
- *
- * This routine does not affect logical decoding as it only looks at
- * confirmation records.
- */
-void
-heap_abort_speculative(Relation relation, HeapTuple tuple)
-{
-	TransactionId xid = GetCurrentTransactionId();
-	ItemPointer tid = &(tuple->t_self);
-	ItemId		lp;
-	HeapTupleData tp;
-	Page		page;
-	BlockNumber block;
-	Buffer		buffer;
-
-	Assert(ItemPointerIsValid(tid));
-
-	block = ItemPointerGetBlockNumber(tid);
-	buffer = ReadBuffer(relation, block);
-	page = BufferGetPage(buffer);
-
-	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
-
-	/*
-	 * Page can't be all visible, we just inserted into it, and are still
-	 * running.
-	 */
-	Assert(!PageIsAllVisible(page));
-
-	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
-	Assert(ItemIdIsNormal(lp));
-
-	tp.t_tableOid = RelationGetRelid(relation);
-	tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	tp.t_len = ItemIdGetLength(lp);
-	tp.t_self = *tid;
-
-	/*
-	 * Sanity check that the tuple really is a speculatively inserted tuple,
-	 * inserted by us.
-	 */
-	if (tp.t_data->t_choice.t_heap.t_xmin != xid)
-		elog(ERROR, "attempted to kill a tuple inserted by another transaction");
-	if (!(IsToastRelation(relation) || HeapTupleHeaderIsSpeculative(tp.t_data)))
-		elog(ERROR, "attempted to kill a non-speculative tuple");
-	Assert(!HeapTupleHeaderIsHeapOnly(tp.t_data));
+					new_infomask |= HEAP_XMAX_EXCL_LOCK;
+					break;
+				case LockTupleExclusive:
+					new_xmax = add_to_xmax;
+					new_infomask |= HEAP_XMAX_EXCL_LOCK;
+					new_infomask2 |= HEAP_KEYS_UPDATED;
+					break;
+				default:
+					new_xmax = InvalidTransactionId;	/* silence compiler */
+					elog(ERROR, "invalid lock mode");
+			}
+		}
+	}
+	else if (old_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		MultiXactStatus new_status;
 
-	/*
-	 * No need to check for serializable conflicts here.  There is never a
-	 * need for a combocid, either.  No need to extract replica identity, or
-	 * do anything special with infomask bits.
-	 */
+		/*
+		 * Currently we don't allow XMAX_COMMITTED to be set for multis, so
+		 * cross-check.
+		 */
+		Assert(!(old_infomask & HEAP_XMAX_COMMITTED));
 
-	START_CRIT_SECTION();
+		/*
+		 * A multixact together with LOCK_ONLY set but neither lock bit set
+		 * (i.e. a pg_upgraded share locked tuple) cannot possibly be running
+		 * anymore.  This check is critical for databases upgraded by
+		 * pg_upgrade; both MultiXactIdIsRunning and MultiXactIdExpand assume
+		 * that such multis are never passed.
+		 */
+		if (HEAP_LOCKED_UPGRADED(old_infomask))
+		{
+			old_infomask &= ~HEAP_XMAX_IS_MULTI;
+			old_infomask |= HEAP_XMAX_INVALID;
+			goto l5;
+		}
 
-	/*
-	 * The tuple will become DEAD immediately.  Flag that this page
-	 * immediately is a candidate for pruning by setting xmin to
-	 * RecentGlobalXmin.  That's not pretty, but it doesn't seem worth
-	 * inventing a nicer API for this.
-	 */
-	Assert(TransactionIdIsValid(RecentGlobalXmin));
-	PageSetPrunable(page, RecentGlobalXmin);
+		/*
+		 * If the XMAX is already a MultiXactId, then we need to expand it to
+		 * include add_to_xmax; but if all the members were lockers and are
+		 * all gone, we can do away with the IS_MULTI bit and just set
+		 * add_to_xmax as the only locker/updater.  If all lockers are gone
+		 * and we have an updater that aborted, we can also do without a
+		 * multi.
+		 *
+		 * The cost of doing GetMultiXactIdMembers would be paid by
+		 * MultiXactIdExpand if we weren't to do this, so this check is not
+		 * incurring extra work anyhow.
+		 */
+		if (!MultiXactIdIsRunning(xmax, HEAP_XMAX_IS_LOCKED_ONLY(old_infomask)))
+		{
+			if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) ||
+				!TransactionIdDidCommit(MultiXactIdGetUpdateXid(xmax,
+																old_infomask)))
+			{
+				/*
+				 * Reset these bits and restart; otherwise fall through to
+				 * create a new multi below.
+				 */
+				old_infomask &= ~HEAP_XMAX_IS_MULTI;
+				old_infomask |= HEAP_XMAX_INVALID;
+				goto l5;
+			}
+		}
 
-	/* store transaction information of xact deleting the tuple */
-	tp.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
-	tp.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+		new_status = get_mxact_status_for_lock(mode, is_update);
 
-	/*
-	 * Set the tuple header xmin to InvalidTransactionId.  This makes the
-	 * tuple immediately invisible everyone.  (In particular, to any
-	 * transactions waiting on the speculative token, woken up later.)
-	 */
-	HeapTupleHeaderSetXmin(tp.t_data, InvalidTransactionId);
+		new_xmax = MultiXactIdExpand((MultiXactId) xmax, add_to_xmax,
+									 new_status);
+		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
+	}
+	else if (old_infomask & HEAP_XMAX_COMMITTED)
+	{
+		/*
+		 * It's a committed update, so we need to preserve him as updater of
+		 * the tuple.
+		 */
+		MultiXactStatus status;
+		MultiXactStatus new_status;
 
-	/* Clear the speculative insertion token too */
-	tp.t_data->t_ctid = tp.t_self;
+		if (old_infomask2 & HEAP_KEYS_UPDATED)
+			status = MultiXactStatusUpdate;
+		else
+			status = MultiXactStatusNoKeyUpdate;
 
-	MarkBufferDirty(buffer);
+		new_status = get_mxact_status_for_lock(mode, is_update);
 
-	/*
-	 * XLOG stuff
-	 *
-	 * The WAL records generated here match heap_delete().  The same recovery
-	 * routines are used.
-	 */
-	if (RelationNeedsWAL(relation))
+		/*
+		 * since it's not running, it's obviously impossible for the old
+		 * updater to be identical to the current one, so we need not check
+		 * for that case as we do in the block above.
+		 */
+		new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
+		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
+	}
+	else if (TransactionIdIsInProgress(xmax))
 	{
-		xl_heap_delete xlrec;
-		XLogRecPtr	recptr;
+		/*
+		 * If the XMAX is a valid, in-progress TransactionId, then we need to
+		 * create a new MultiXactId that includes both the old locker or
+		 * updater and our own TransactionId.
+		 */
+		MultiXactStatus new_status;
+		MultiXactStatus old_status;
+		LockTupleMode old_mode;
 
-		xlrec.flags = XLH_DELETE_IS_SUPER;
-		xlrec.infobits_set = compute_infobits(tp.t_data->t_infomask,
-											  tp.t_data->t_infomask2);
-		xlrec.offnum = ItemPointerGetOffsetNumber(&tp.t_self);
-		xlrec.xmax = xid;
+		if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
+		{
+			if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
+				old_status = MultiXactStatusForKeyShare;
+			else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
+				old_status = MultiXactStatusForShare;
+			else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
+			{
+				if (old_infomask2 & HEAP_KEYS_UPDATED)
+					old_status = MultiXactStatusForUpdate;
+				else
+					old_status = MultiXactStatusForNoKeyUpdate;
+			}
+			else
+			{
+				/*
+				 * LOCK_ONLY can be present alone only when a page has been
+				 * upgraded by pg_upgrade.  But in that case,
+				 * TransactionIdIsInProgress() should have returned false.  We
+				 * assume it's no longer locked in this case.
+				 */
+				elog(WARNING, "LOCK_ONLY found for Xid in progress %u", xmax);
+				old_infomask |= HEAP_XMAX_INVALID;
+				old_infomask &= ~HEAP_XMAX_LOCK_ONLY;
+				goto l5;
+			}
+		}
+		else
+		{
+			/* it's an update, but which kind? */
+			if (old_infomask2 & HEAP_KEYS_UPDATED)
+				old_status = MultiXactStatusUpdate;
+			else
+				old_status = MultiXactStatusNoKeyUpdate;
+		}
 
-		XLogBeginInsert();
-		XLogRegisterData((char *) &xlrec, SizeOfHeapDelete);
-		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
+		old_mode = TUPLOCK_from_mxstatus(old_status);
+
+		/*
+		 * If the lock to be acquired is for the same TransactionId as the
+		 * existing lock, there's an optimization possible: consider only the
+		 * strongest of both locks as the only one present, and restart.
+		 */
+		if (xmax == add_to_xmax)
+		{
+			/*
+			 * Note that it's not possible for the original tuple to be
+			 * updated: we wouldn't be here because the tuple would have been
+			 * invisible and we wouldn't try to update it.  As a subtlety,
+			 * this code can also run when traversing an update chain to lock
+			 * future versions of a tuple.  But we wouldn't be here either,
+			 * because the add_to_xmax would be different from the original
+			 * updater.
+			 */
+			Assert(HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
 
-		/* No replica identity & replication origin logged */
+			/* acquire the strongest of both */
+			if (mode < old_mode)
+				mode = old_mode;
+			/* mustn't touch is_update */
 
-		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_DELETE);
+			old_infomask |= HEAP_XMAX_INVALID;
+			goto l5;
+		}
 
-		PageSetLSN(page, recptr);
+		/* otherwise, just fall back to creating a new multixact */
+		new_status = get_mxact_status_for_lock(mode, is_update);
+		new_xmax = MultiXactIdCreate(xmax, old_status,
+									 add_to_xmax, new_status);
+		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
 	}
+	else if (!HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) &&
+			 TransactionIdDidCommit(xmax))
+	{
+		/*
+		 * It's a committed update, so we gotta preserve him as updater of the
+		 * tuple.
+		 */
+		MultiXactStatus status;
+		MultiXactStatus new_status;
 
-	END_CRIT_SECTION();
+		if (old_infomask2 & HEAP_KEYS_UPDATED)
+			status = MultiXactStatusUpdate;
+		else
+			status = MultiXactStatusNoKeyUpdate;
 
-	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+		new_status = get_mxact_status_for_lock(mode, is_update);
 
-	if (HeapTupleHasExternal(&tp))
+		/*
+		 * since it's not running, it's obviously impossible for the old
+		 * updater to be identical to the current one, so we need not check
+		 * for that case as we do in the block above.
+		 */
+		new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
+		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
+	}
+	else
 	{
-		Assert(!IsToastRelation(relation));
-		toast_delete(relation, &tp, true);
+		/*
+		 * Can get here iff the locking/updating transaction was running when
+		 * the infomask was extracted from the tuple, but finished before
+		 * TransactionIdIsInProgress got to run.  Deal with it as if there was
+		 * no locker at all in the first place.
+		 */
+		old_infomask |= HEAP_XMAX_INVALID;
+		goto l5;
 	}
 
-	/*
-	 * Never need to mark tuple for invalidation, since catalogs don't support
-	 * speculative insertion
-	 */
+	*result_infomask = new_infomask;
+	*result_infomask2 = new_infomask2;
+	*result_xmax = new_xmax;
+}
 
-	/* Now we can release the buffer */
-	ReleaseBuffer(buffer);
 
-	/* count deletion, as we counted the insertion too */
-	pgstat_count_heap_delete(relation);
-}
 
 /*
  * heap_inplace_update - update a tuple "in place" (ie, overwrite it)
@@ -6992,7 +4941,7 @@ HeapTupleGetUpdateXid(HeapTupleHeader tuple)
  *
  * The passed infomask pairs up with the given multixact in the tuple header.
  */
-static bool
+bool
 DoesMultiXactIdConflict(MultiXactId multi, uint16 infomask,
 						LockTupleMode lockmode)
 {
@@ -7159,7 +5108,7 @@ Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
  * We return (in *remaining, if not NULL) the number of members that are still
  * running, including any (non-aborted) subtransactions of our own transaction.
  */
-static void
+void
 MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
 				Relation rel, ItemPointer ctid, XLTW_Oper oper,
 				int *remaining)
@@ -7181,7 +5130,7 @@ MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
  * We return (in *remaining, if not NULL) the number of members that are still
  * running, including any (non-aborted) subtransactions of our own transaction.
  */
-static bool
+bool
 ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
 						   uint16 infomask, Relation rel, int *remaining)
 {
@@ -7743,7 +5692,7 @@ log_heap_update(Relation reln, Buffer oldbuf,
  * This is only used in wal_level >= WAL_LEVEL_LOGICAL, and only for catalog
  * tuples.
  */
-static XLogRecPtr
+XLogRecPtr
 log_heap_new_cid(Relation relation, HeapTuple tup)
 {
 	xl_heap_new_cid xlrec;
@@ -9120,46 +7069,6 @@ heap2_redo(XLogReaderState *record)
 }
 
 /*
- *	heap_sync		- sync a heap, for use when no WAL has been written
- *
- * This forces the heap contents (including TOAST heap if any) down to disk.
- * If we skipped using WAL, and WAL is otherwise needed, we must force the
- * relation down to disk before it's safe to commit the transaction.  This
- * requires writing out any dirty buffers and then doing a forced fsync.
- *
- * Indexes are not touched.  (Currently, index operations associated with
- * the commands that use this are WAL-logged and so do not need fsync.
- * That behavior might change someday, but in any case it's likely that
- * any fsync decisions required would be per-index and hence not appropriate
- * to be done here.)
- */
-void
-heap_sync(Relation rel)
-{
-	/* non-WAL-logged tables never need fsync */
-	if (!RelationNeedsWAL(rel))
-		return;
-
-	/* main heap */
-	FlushRelationBuffers(rel);
-	/* FlushRelationBuffers will have opened rd_smgr */
-	smgrimmedsync(rel->rd_smgr, MAIN_FORKNUM);
-
-	/* FSM is not critical, don't bother syncing it */
-
-	/* toast heap, if any */
-	if (OidIsValid(rel->rd_rel->reltoastrelid))
-	{
-		Relation	toastrel;
-
-		toastrel = heap_open(rel->rd_rel->reltoastrelid, AccessShareLock);
-		FlushRelationBuffers(toastrel);
-		smgrimmedsync(toastrel->rd_smgr, MAIN_FORKNUM);
-		heap_close(toastrel, AccessShareLock);
-	}
-}
-
-/*
  * Mask a heap page before performing consistency checks on it.
  */
 void
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index bf22f2e..d08a99e 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -1640,11 +1640,2145 @@ HeapTupleSatisfiesHistoricMVCC(StorageTuple stup, Snapshot snapshot,
 		return true;
 }
 
+
+/*
+ *	heapam_fetch		- retrieve tuple with given tid
+ *
+ * On entry, tuple->t_self is the TID to fetch.  We pin the buffer holding
+ * the tuple, fill in the remaining fields of *tuple, and check the tuple
+ * against the specified snapshot.
+ *
+ * If successful (tuple found and passes snapshot time qual), then *userbuf
+ * is set to the buffer holding the tuple and TRUE is returned.  The caller
+ * must unpin the buffer when done with the tuple.
+ *
+ * If the tuple is not found (ie, item number references a deleted slot),
+ * then tuple->t_data is set to NULL and FALSE is returned.
+ *
+ * If the tuple is found but fails the time qual check, then FALSE is returned
+ * but tuple->t_data is left pointing to the tuple.
+ *
+ * keep_buf determines what is done with the buffer in the FALSE-result cases.
+ * When the caller specifies keep_buf = true, we retain the pin on the buffer
+ * and return it in *userbuf (so the caller must eventually unpin it); when
+ * keep_buf = false, the pin is released and *userbuf is set to InvalidBuffer.
+ *
+ * stats_relation is the relation to charge the heap_fetch operation against
+ * for statistical purposes.  (This could be the heap rel itself, an
+ * associated index, or NULL to not count the fetch at all.)
+ *
+ * heap_fetch does not follow HOT chains: only the exact TID requested will
+ * be fetched.
+ *
+ * It is somewhat inconsistent that we ereport() on invalid block number but
+ * return false on invalid item number.  There are a couple of reasons though.
+ * One is that the caller can relatively easily check the block number for
+ * validity, but cannot check the item number without reading the page
+ * himself.  Another is that when we are following a t_ctid link, we can be
+ * reasonably confident that the page number is valid (since VACUUM shouldn't
+ * truncate off the destination page without having killed the referencing
+ * tuple first), but the item number might well not be good.
+ */
+static bool
+heapam_fetch(Relation relation,
+		   ItemPointer tid,
+		   Snapshot snapshot,
+		   StorageTuple *stuple,
+		   Buffer *userbuf,
+		   bool keep_buf,
+		   Relation stats_relation)
+{
+	ItemId		lp;
+	Buffer		buffer;
+	Page		page;
+	OffsetNumber offnum;
+	bool		valid;
+	HeapTupleData tuple;
+
+	/*
+	 * Fetch and pin the appropriate page of the relation.
+	 */
+	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
+
+	/*
+	 * Need share lock on buffer to examine tuple commit status.
+	 */
+	LockBuffer(buffer, BUFFER_LOCK_SHARE);
+	page = BufferGetPage(buffer);
+	TestForOldSnapshot(snapshot, relation, page);
+
+	/*
+	 * We'd better check for out-of-range offnum in case of VACUUM since the
+	 * TID was obtained.
+	 */
+	offnum = ItemPointerGetOffsetNumber(tid);
+	if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
+	{
+		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+		if (keep_buf)
+			*userbuf = buffer;
+		else
+		{
+			ReleaseBuffer(buffer);
+			*userbuf = InvalidBuffer;
+		}
+		*stuple = NULL;
+		return false;
+	}
+
+	/*
+	 * get the item line pointer corresponding to the requested tid
+	 */
+	lp = PageGetItemId(page, offnum);
+
+	/*
+	 * Must check for deleted tuple.
+	 */
+	if (!ItemIdIsNormal(lp))
+	{
+		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+		if (keep_buf)
+			*userbuf = buffer;
+		else
+		{
+			ReleaseBuffer(buffer);
+			*userbuf = InvalidBuffer;
+		}
+		*stuple = NULL;
+		return false;
+	}
+
+	/*
+	 * 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);
+
+	/*
+	 * check time qualification of tuple, then release lock
+	 */
+	valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, &tuple, snapshot, buffer);
+
+	if (valid)
+		PredicateLockTuple(relation, &tuple, snapshot);
+
+	CheckForSerializableConflictOut(valid, relation, &tuple, buffer, snapshot);
+
+	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+
+	if (valid)
+	{
+		/*
+		 * All checks passed, so return the tuple as valid. Caller is now
+		 * responsible for releasing the buffer.
+		 */
+		*userbuf = buffer;
+
+		/* Count the successful fetch against appropriate rel, if any */
+		if (stats_relation != NULL)
+			pgstat_count_heap_fetch(stats_relation);
+
+		*stuple = heap_copytuple(&tuple);
+		return true;
+	}
+
+	/* Tuple failed time qual, but maybe caller wants to see it anyway. */
+	if (keep_buf)
+		*userbuf = buffer;
+	else
+	{
+		ReleaseBuffer(buffer);
+		*userbuf = InvalidBuffer;
+	}
+
+	return false;
+}
+
+
+
+
+
+/* ----------------------------------------------------------------
+ *				storage AM support routines for heapam
+ * ----------------------------------------------------------------
+ */
+
+/*
+ * 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)
+{
+	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);
+
+	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,
+				   CommandId cid, Snapshot crosscheck, bool wait,
+				   HeapUpdateFailureData *hufd, LockTupleMode *lockmode)
+{
+	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);
+
+	return result;
+}
+
+static void
+heapam_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);
+
+	offnum = ItemPointerGetOffsetNumber(&(tuple->t_self));
+	if (PageGetMaxOffsetNumber(page) >= offnum)
+		lp = PageGetItemId(page, offnum);
+
+	if (PageGetMaxOffsetNumber(page) < offnum || !ItemIdIsNormal(lp))
+		elog(ERROR, "invalid lp");
+
+	htup = (HeapTupleHeader) PageGetItem(page, lp);
+
+	/* SpecTokenOffsetNumber should be distinguishable from any real offset */
+	StaticAssertStmt(MaxOffsetNumber < SpecTokenOffsetNumber,
+					 "invalid speculative token constant");
+
+	/* NO EREPORT(ERROR) from here till changes are logged */
+	START_CRIT_SECTION();
+
+	Assert(HeapTupleHeaderIsSpeculative(tuple->t_data));
+
+	MarkBufferDirty(buffer);
+
+	/*
+	 * Replace the speculative insertion token with a real t_ctid, pointing to
+	 * itself like it does on regular tuples.
+	 */
+	htup->t_ctid = tuple->t_self;
+
+	/* XLOG stuff */
+	if (RelationNeedsWAL(relation))
+	{
+		xl_heap_confirm xlrec;
+		XLogRecPtr	recptr;
+
+		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
+
+		XLogBeginInsert();
+
+		/* We want the same filtering on this as on a plain insert */
+		XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
+
+		XLogRegisterData((char *) &xlrec, SizeOfHeapConfirm);
+		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
+
+		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_CONFIRM);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(buffer);
+	slot->tts_speculativeToken = 0;
+}
+
+static void
+heapam_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;
+	HeapTupleData tp;
+	Page		page;
+	BlockNumber block;
+	Buffer		buffer;
+
+	Assert(slot->tts_speculativeToken != 0);
+	Assert(ItemPointerIsValid(tid));
+
+	block = ItemPointerGetBlockNumber(tid);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
+
+	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+
+	/*
+	 * Page can't be all visible, we just inserted into it, and are still
+	 * running.
+	 */
+	Assert(!PageIsAllVisible(page));
+
+	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
+	Assert(ItemIdIsNormal(lp));
+
+	tp.t_tableOid = RelationGetRelid(relation);
+	tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	tp.t_len = ItemIdGetLength(lp);
+	tp.t_self = *tid;
+
+	/*
+	 * Sanity check that the tuple really is a speculatively inserted tuple,
+	 * inserted by us.
+	 */
+	if (tp.t_data->t_choice.t_heap.t_xmin != xid)
+		elog(ERROR, "attempted to kill a tuple inserted by another transaction");
+	if (!(IsToastRelation(relation) || HeapTupleHeaderIsSpeculative(tp.t_data)))
+		elog(ERROR, "attempted to kill a non-speculative tuple");
+	Assert(!HeapTupleHeaderIsHeapOnly(tp.t_data));
+
+	/*
+	 * No need to check for serializable conflicts here.  There is never a
+	 * need for a combocid, either.  No need to extract replica identity, or
+	 * do anything special with infomask bits.
+	 */
+
+	START_CRIT_SECTION();
+
+	/*
+	 * The tuple will become DEAD immediately.  Flag that this page
+	 * immediately is a candidate for pruning by setting xmin to
+	 * RecentGlobalXmin.  That's not pretty, but it doesn't seem worth
+	 * inventing a nicer API for this.
+	 */
+	Assert(TransactionIdIsValid(RecentGlobalXmin));
+	PageSetPrunable(page, RecentGlobalXmin);
+
+	/* store transaction information of xact deleting the tuple */
+	tp.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+	tp.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+
+	/*
+	 * Set the tuple header xmin to InvalidTransactionId.  This makes the
+	 * tuple immediately invisible everyone.  (In particular, to any
+	 * transactions waiting on the speculative token, woken up later.)
+	 */
+	HeapTupleHeaderSetXmin(tp.t_data, InvalidTransactionId);
+
+	/* Clear the speculative insertion token too */
+	tp.t_data->t_ctid = tp.t_self;
+
+	MarkBufferDirty(buffer);
+
+	/*
+	 * XLOG stuff
+	 *
+	 * The WAL records generated here match heap_delete().  The same recovery
+	 * routines are used.
+	 */
+	if (RelationNeedsWAL(relation))
+	{
+		xl_heap_delete xlrec;
+		XLogRecPtr	recptr;
+
+		xlrec.flags = XLH_DELETE_IS_SUPER;
+		xlrec.infobits_set = compute_infobits(tp.t_data->t_infomask,
+											  tp.t_data->t_infomask2);
+		xlrec.offnum = ItemPointerGetOffsetNumber(&tp.t_self);
+		xlrec.xmax = xid;
+
+		XLogBeginInsert();
+		XLogRegisterData((char *) &xlrec, SizeOfHeapDelete);
+		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
+
+		/* No replica identity & replication origin logged */
+
+		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_DELETE);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+
+	if (HeapTupleHasExternal(&tp))
+	{
+		Assert(!IsToastRelation(relation));
+		toast_delete(relation, &tp, true);
+	}
+
+	/*
+	 * Never need to mark tuple for invalidation, since catalogs don't support
+	 * speculative insertion
+	 */
+
+	/* Now we can release the buffer */
+	ReleaseBuffer(buffer);
+
+	/* count deletion, as we counted the insertion too */
+	pgstat_count_heap_delete(relation);
+	slot->tts_speculativeToken = 0;
+}
+
+/*
+ *	heapam_multi_insert	- insert multiple tuple into a heap
+ *
+ * This is like heap_insert(), but inserts multiple tuples in one operation.
+ * That's faster than calling heap_insert() in a loop, because when multiple
+ * tuples can be inserted on a single page, we can write just a single WAL
+ * record covering all of them, and only need to lock/unlock the page once.
+ *
+ * Note: this leaks memory into the current memory context. You can create a
+ * temporary context before calling this, if that's a problem.
+ */
+static void
+heapam_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
+				  CommandId cid, int options, BulkInsertState bistate)
+{
+	TransactionId xid = GetCurrentTransactionId();
+	HeapTuple  *heaptuples;
+	int			i;
+	int			ndone;
+	char	   *scratch = NULL;
+	Page		page;
+	bool		needwal;
+	Size		saveFreeSpace;
+	bool		need_tuple_data = RelationIsLogicallyLogged(relation);
+	bool		need_cids = RelationIsAccessibleInLogicalDecoding(relation);
+
+	needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation);
+	saveFreeSpace = RelationGetTargetPageFreeSpace(relation,
+												   HEAP_DEFAULT_FILLFACTOR);
+
+	/* Toast and set header data in all the tuples */
+	heaptuples = palloc(ntuples * sizeof(HeapTuple));
+	for (i = 0; i < ntuples; i++)
+		heaptuples[i] = heap_prepare_insert(relation, tuples[i],
+											xid, cid, options);
+
+	/*
+	 * Allocate some memory to use for constructing the WAL record. Using
+	 * palloc() within a critical section is not safe, so we allocate this
+	 * beforehand.
+	 */
+	if (needwal)
+		scratch = palloc(BLCKSZ);
+
+	/*
+	 * We're about to do the actual inserts -- but check for conflict first,
+	 * to minimize the possibility of having to roll back work we've just
+	 * done.
+	 *
+	 * A check here does not definitively prevent a serialization anomaly;
+	 * that check MUST be done at least past the point of acquiring an
+	 * exclusive buffer content lock on every buffer that will be affected,
+	 * and MAY be done after all inserts are reflected in the buffers and
+	 * those locks are released; otherwise there race condition.  Since
+	 * multiple buffers can be locked and unlocked in the loop below, and it
+	 * would not be feasible to identify and lock all of those buffers before
+	 * the loop, we must do a final check at the end.
+	 *
+	 * The check here could be omitted with no loss of correctness; it is
+	 * present strictly as an optimization.
+	 *
+	 * For heap inserts, we only need to check for table-level SSI locks. Our
+	 * new tuples can't possibly conflict with existing tuple locks, and heap
+	 * page locks are only consolidated versions of tuple locks; they do not
+	 * lock "gaps" as index page locks do.  So we don't need to specify a
+	 * buffer when making the call, which makes for a faster check.
+	 */
+	CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
+
+	ndone = 0;
+	while (ndone < ntuples)
+	{
+		Buffer		buffer;
+		Buffer		vmbuffer = InvalidBuffer;
+		bool		all_visible_cleared = false;
+		int			nthispage;
+
+		CHECK_FOR_INTERRUPTS();
+
+		/*
+		 * Find buffer where at least the next tuple will fit.  If the page is
+		 * all-visible, this will also pin the requisite visibility map page.
+		 */
+		buffer = RelationGetBufferForTuple(relation, heaptuples[ndone]->t_len,
+										   InvalidBuffer, options, bistate,
+										   &vmbuffer, NULL);
+		page = BufferGetPage(buffer);
+
+		/* NO EREPORT(ERROR) from here till changes are logged */
+		START_CRIT_SECTION();
+
+		/*
+		 * RelationGetBufferForTuple has ensured that the first tuple fits.
+		 * Put that on the page, and then as many other tuples as fit.
+		 */
+		RelationPutHeapTuple(relation, buffer, heaptuples[ndone], false);
+		for (nthispage = 1; ndone + nthispage < ntuples; nthispage++)
+		{
+			HeapTuple	heaptup = heaptuples[ndone + nthispage];
+
+			if (PageGetHeapFreeSpace(page) < MAXALIGN(heaptup->t_len) + saveFreeSpace)
+				break;
+
+			RelationPutHeapTuple(relation, buffer, heaptup, false);
+
+			/*
+			 * We don't use heap_multi_insert for catalog tuples yet, but
+			 * better be prepared...
+			 */
+			if (needwal && need_cids)
+				log_heap_new_cid(relation, heaptup);
+		}
+
+		if (PageIsAllVisible(page))
+		{
+			all_visible_cleared = true;
+			PageClearAllVisible(page);
+			visibilitymap_clear(relation,
+								BufferGetBlockNumber(buffer),
+								vmbuffer, VISIBILITYMAP_VALID_BITS);
+		}
+
+		/*
+		 * XXX Should we set PageSetPrunable on this page ? See heap_insert()
+		 */
+
+		MarkBufferDirty(buffer);
+
+		/* XLOG stuff */
+		if (needwal)
+		{
+			XLogRecPtr	recptr;
+			xl_heap_multi_insert *xlrec;
+			uint8		info = XLOG_HEAP2_MULTI_INSERT;
+			char	   *tupledata;
+			int			totaldatalen;
+			char	   *scratchptr = scratch;
+			bool		init;
+			int			bufflags = 0;
+
+			/*
+			 * If the page was previously empty, we can reinit the page
+			 * instead of restoring the whole thing.
+			 */
+			init = (ItemPointerGetOffsetNumber(&(heaptuples[ndone]->t_self)) == FirstOffsetNumber &&
+					PageGetMaxOffsetNumber(page) == FirstOffsetNumber + nthispage - 1);
+
+			/* allocate xl_heap_multi_insert struct from the scratch area */
+			xlrec = (xl_heap_multi_insert *) scratchptr;
+			scratchptr += SizeOfHeapMultiInsert;
+
+			/*
+			 * Allocate offsets array. Unless we're reinitializing the page,
+			 * in that case the tuples are stored in order starting at
+			 * FirstOffsetNumber and we don't need to store the offsets
+			 * explicitly.
+			 */
+			if (!init)
+				scratchptr += nthispage * sizeof(OffsetNumber);
+
+			/* the rest of the scratch space is used for tuple data */
+			tupledata = scratchptr;
+
+			xlrec->flags = all_visible_cleared ? XLH_INSERT_ALL_VISIBLE_CLEARED : 0;
+			xlrec->ntuples = nthispage;
+
+			/*
+			 * Write out an xl_multi_insert_tuple and the tuple data itself
+			 * for each tuple.
+			 */
+			for (i = 0; i < nthispage; i++)
+			{
+				HeapTuple	heaptup = heaptuples[ndone + i];
+				xl_multi_insert_tuple *tuphdr;
+				int			datalen;
+
+				if (!init)
+					xlrec->offsets[i] = ItemPointerGetOffsetNumber(&heaptup->t_self);
+				/* xl_multi_insert_tuple needs two-byte alignment. */
+				tuphdr = (xl_multi_insert_tuple *) SHORTALIGN(scratchptr);
+				scratchptr = ((char *) tuphdr) + SizeOfMultiInsertTuple;
+
+				tuphdr->t_infomask2 = heaptup->t_data->t_infomask2;
+				tuphdr->t_infomask = heaptup->t_data->t_infomask;
+				tuphdr->t_hoff = heaptup->t_data->t_hoff;
+
+				/* write bitmap [+ padding] [+ oid] + data */
+				datalen = heaptup->t_len - SizeofHeapTupleHeader;
+				memcpy(scratchptr,
+					   (char *) heaptup->t_data + SizeofHeapTupleHeader,
+					   datalen);
+				tuphdr->datalen = datalen;
+				scratchptr += datalen;
+			}
+			totaldatalen = scratchptr - tupledata;
+			Assert((scratchptr - scratch) < BLCKSZ);
+
+			if (need_tuple_data)
+				xlrec->flags |= XLH_INSERT_CONTAINS_NEW_TUPLE;
+
+			/*
+			 * Signal that this is the last xl_heap_multi_insert record
+			 * emitted by this call to heap_multi_insert(). Needed for logical
+			 * decoding so it knows when to cleanup temporary data.
+			 */
+			if (ndone + nthispage == ntuples)
+				xlrec->flags |= XLH_INSERT_LAST_IN_MULTI;
+
+			if (init)
+			{
+				info |= XLOG_HEAP_INIT_PAGE;
+				bufflags |= REGBUF_WILL_INIT;
+			}
+
+			/*
+			 * If we're doing logical decoding, include the new tuple data
+			 * even if we take a full-page image of the page.
+			 */
+			if (need_tuple_data)
+				bufflags |= REGBUF_KEEP_DATA;
+
+			XLogBeginInsert();
+			XLogRegisterData((char *) xlrec, tupledata - scratch);
+			XLogRegisterBuffer(0, buffer, REGBUF_STANDARD | bufflags);
+
+			XLogRegisterBufData(0, tupledata, totaldatalen);
+
+			/* filtering by origin on a row level is much more efficient */
+			XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
+
+			recptr = XLogInsert(RM_HEAP2_ID, info);
+
+			PageSetLSN(page, recptr);
+		}
+
+		END_CRIT_SECTION();
+
+		UnlockReleaseBuffer(buffer);
+		if (vmbuffer != InvalidBuffer)
+			ReleaseBuffer(vmbuffer);
+
+		ndone += nthispage;
+	}
+
+	/*
+	 * We're done with the actual inserts.  Check for conflicts again, to
+	 * ensure that all rw-conflicts in to these inserts are detected.  Without
+	 * this final check, a sequential scan of the heap may have locked the
+	 * table after the "before" check, missing one opportunity to detect the
+	 * conflict, and then scanned the table before the new tuples were there,
+	 * missing the other chance to detect the conflict.
+	 *
+	 * For heap inserts, we only need to check for table-level SSI locks. Our
+	 * new tuples can't possibly conflict with existing tuple locks, and heap
+	 * page locks are only consolidated versions of tuple locks; they do not
+	 * lock "gaps" as index page locks do.  So we don't need to specify a
+	 * buffer when making the call.
+	 */
+	CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
+
+	/*
+	 * If tuples are cachable, mark them for invalidation from the caches in
+	 * case we abort.  Note it is OK to do this after releasing the buffer,
+	 * because the heaptuples data structure is all in local memory, not in
+	 * the shared buffer.
+	 */
+	if (IsCatalogRelation(relation))
+	{
+		for (i = 0; i < ntuples; i++)
+			CacheInvalidateHeapTuple(relation, heaptuples[i], NULL);
+	}
+
+	/*
+	 * Copy t_self fields back to the caller's original tuples. This does
+	 * nothing for untoasted tuples (tuples[i] == heaptuples[i)], but it's
+	 * probably faster to always copy than check.
+	 */
+	for (i = 0; i < ntuples; i++)
+		tuples[i]->t_self = heaptuples[i]->t_self;
+
+	pgstat_count_heap_insert(relation, ntuples);
+}
+
+/*
+ * Subroutine for heap_lock_updated_tuple_rec.
+ *
+ * Given a hypothetical multixact status held by the transaction identified
+ * with the given xid, does the current transaction need to wait, fail, or can
+ * it continue if it wanted to acquire a lock of the given mode?  "needwait"
+ * is set to true if waiting is necessary; if it can continue, then
+ * HeapTupleMayBeUpdated is returned.  If the lock is already held by the
+ * current transaction, return HeapTupleSelfUpdated.  In case of a conflict
+ * with another transaction, a different HeapTupleSatisfiesUpdate return code
+ * is returned.
+ *
+ * The held status is said to be hypothetical because it might correspond to a
+ * lock held by a single Xid, i.e. not a real MultiXactId; we express it this
+ * way for simplicity of API.
+ */
+static HTSU_Result
+test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid,
+						   LockTupleMode mode, bool *needwait)
+{
+	MultiXactStatus wantedstatus;
+
+	*needwait = false;
+	wantedstatus = get_mxact_status_for_lock(mode, false);
+
+	/*
+	 * Note: we *must* check TransactionIdIsInProgress before
+	 * TransactionIdDidAbort/Commit; see comment at top of tqual.c for an
+	 * explanation.
+	 */
+	if (TransactionIdIsCurrentTransactionId(xid))
+	{
+		/*
+		 * The tuple has already been locked by our own transaction.  This is
+		 * very rare but can happen if multiple transactions are trying to
+		 * lock an ancient version of the same tuple.
+		 */
+		return HeapTupleSelfUpdated;
+	}
+	else if (TransactionIdIsInProgress(xid))
+	{
+		/*
+		 * If the locking transaction is running, what we do depends on
+		 * whether the lock modes conflict: if they do, then we must wait for
+		 * it to finish; otherwise we can fall through to lock this tuple
+		 * version without waiting.
+		 */
+		if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
+								LOCKMODE_from_mxstatus(wantedstatus)))
+		{
+			*needwait = true;
+		}
+
+		/*
+		 * If we set needwait above, then this value doesn't matter;
+		 * otherwise, this value signals to caller that it's okay to proceed.
+		 */
+		return HeapTupleMayBeUpdated;
+	}
+	else if (TransactionIdDidAbort(xid))
+		return HeapTupleMayBeUpdated;
+	else if (TransactionIdDidCommit(xid))
+	{
+		/*
+		 * The other transaction committed.  If it was only a locker, then the
+		 * lock is completely gone now and we can return success; but if it
+		 * was an update, then what we do depends on whether the two lock
+		 * modes conflict.  If they conflict, then we must report error to
+		 * caller. But if they don't, we can fall through to allow the current
+		 * transaction to lock the tuple.
+		 *
+		 * Note: the reason we worry about ISUPDATE here is because as soon as
+		 * a transaction ends, all its locks are gone and meaningless, and
+		 * thus we can ignore them; whereas its updates persist.  In the
+		 * TransactionIdIsInProgress case, above, we don't need to check
+		 * because we know the lock is still "alive" and thus a conflict needs
+		 * always be checked.
+		 */
+		if (!ISUPDATE_from_mxstatus(status))
+			return HeapTupleMayBeUpdated;
+
+		if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
+								LOCKMODE_from_mxstatus(wantedstatus)))
+			/* bummer */
+			return HeapTupleUpdated;
+
+		return HeapTupleMayBeUpdated;
+	}
+
+	/* Not in progress, not aborted, not committed -- must have crashed */
+	return HeapTupleMayBeUpdated;
+}
+
+
+/*
+ * Recursive part of heap_lock_updated_tuple
+ *
+ * Fetch the tuple pointed to by tid in rel, and mark it as locked by the given
+ * xid with the given mode; if this tuple is updated, recurse to lock the new
+ * version as well.
+ */
+static HTSU_Result
+heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid,
+							LockTupleMode mode)
+{
+	HTSU_Result result;
+	ItemPointerData tupid;
+	HeapTuple 	mytup;
+	Buffer		buf;
+	uint16		new_infomask,
+				new_infomask2,
+				old_infomask,
+				old_infomask2;
+	TransactionId xmax,
+				new_xmax;
+	TransactionId priorXmax = InvalidTransactionId;
+	bool		cleared_all_frozen = false;
+	Buffer		vmbuffer = InvalidBuffer;
+	BlockNumber block;
+
+	ItemPointerCopy(tid, &tupid);
+
+	for (;;)
+	{
+		new_infomask = 0;
+		new_xmax = InvalidTransactionId;
+		block = ItemPointerGetBlockNumber(&tupid);
+
+		if (!heapam_fetch(rel, &tupid, SnapshotAny, (StorageTuple *)&mytup, &buf, false, NULL))
+		{
+			/*
+			 * if we fail to find the updated version of the tuple, it's
+			 * because it was vacuumed/pruned away after its creator
+			 * transaction aborted.  So behave as if we got to the end of the
+			 * chain, and there's no further tuple to lock: return success to
+			 * caller.
+			 */
+			return HeapTupleMayBeUpdated;
+		}
+
+l4:
+		CHECK_FOR_INTERRUPTS();
+
+		/*
+		 * Before locking the buffer, pin the visibility map page if it
+		 * appears to be necessary.  Since we haven't got the lock yet,
+		 * someone else might be in the middle of changing this, so we'll need
+		 * to recheck after we have the lock.
+		 */
+		if (PageIsAllVisible(BufferGetPage(buf)))
+			visibilitymap_pin(rel, block, &vmbuffer);
+		else
+			vmbuffer = InvalidBuffer;
+
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+		/*
+		 * If we didn't pin the visibility map page and the page has become
+		 * all visible while we were busy locking the buffer, we'll have to
+		 * unlock and re-lock, to avoid holding the buffer lock across I/O.
+		 * That's a bit unfortunate, but hopefully shouldn't happen often.
+		 */
+		if (vmbuffer == InvalidBuffer && PageIsAllVisible(BufferGetPage(buf)))
+		{
+			LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+			visibilitymap_pin(rel, block, &vmbuffer);
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+		}
+
+		/*
+		 * Check the tuple XMIN against prior XMAX, if any.  If we reached the
+		 * end of the chain, we're done, so return success.
+		 */
+		if (TransactionIdIsValid(priorXmax) &&
+			!TransactionIdEquals(HeapTupleHeaderGetXmin(mytup->t_data),
+								 priorXmax))
+		{
+			result = HeapTupleMayBeUpdated;
+			goto out_locked;
+		}
+
+		/*
+		 * Also check Xmin: if this tuple was created by an aborted
+		 * (sub)transaction, then we already locked the last live one in the
+		 * chain, thus we're done, so return success.
+		 */
+		if (TransactionIdDidAbort(HeapTupleHeaderGetXmin(mytup->t_data)))
+		{
+			UnlockReleaseBuffer(buf);
+			return HeapTupleMayBeUpdated;
+		}
+
+		old_infomask = mytup->t_data->t_infomask;
+		old_infomask2 = mytup->t_data->t_infomask2;
+		xmax = HeapTupleHeaderGetRawXmax(mytup->t_data);
+
+		/*
+		 * If this tuple version has been updated or locked by some concurrent
+		 * transaction(s), what we do depends on whether our lock mode
+		 * conflicts with what those other transactions hold, and also on the
+		 * status of them.
+		 */
+		if (!(old_infomask & HEAP_XMAX_INVALID))
+		{
+			TransactionId rawxmax;
+			bool		needwait;
+
+			rawxmax = HeapTupleHeaderGetRawXmax(mytup->t_data);
+			if (old_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				int			nmembers;
+				int			i;
+				MultiXactMember *members;
+
+				/*
+				 * We don't need a test for pg_upgrade'd tuples: this is only
+				 * applied to tuples after the first in an update chain.  Said
+				 * first tuple in the chain may well be locked-in-9.2-and-
+				 * pg_upgraded, but that one was already locked by our caller,
+				 * not us; and any subsequent ones cannot be because our
+				 * caller must necessarily have obtained a snapshot later than
+				 * the pg_upgrade itself.
+				 */
+				Assert(!HEAP_LOCKED_UPGRADED(mytup->t_data->t_infomask));
+
+				nmembers = GetMultiXactIdMembers(rawxmax, &members, false,
+												 HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
+				for (i = 0; i < nmembers; i++)
+				{
+					result = test_lockmode_for_conflict(members[i].status,
+														members[i].xid,
+														mode, &needwait);
+
+					/*
+					 * If the tuple was already locked by ourselves in a
+					 * previous iteration of this (say heap_lock_tuple was
+					 * forced to restart the locking loop because of a change
+					 * in xmax), then we hold the lock already on this tuple
+					 * version and we don't need to do anything; and this is
+					 * not an error condition either.  We just need to skip
+					 * this tuple and continue locking the next version in the
+					 * update chain.
+					 */
+					if (result == HeapTupleSelfUpdated)
+					{
+						pfree(members);
+						goto next;
+					}
+
+					if (needwait)
+					{
+						LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+						XactLockTableWait(members[i].xid, rel,
+										  &mytup->t_self,
+										  XLTW_LockUpdated);
+						pfree(members);
+						goto l4;
+					}
+					if (result != HeapTupleMayBeUpdated)
+					{
+						pfree(members);
+						goto out_locked;
+					}
+				}
+				if (members)
+					pfree(members);
+			}
+			else
+			{
+				MultiXactStatus status;
+
+				/*
+				 * For a non-multi Xmax, we first need to compute the
+				 * corresponding MultiXactStatus by using the infomask bits.
+				 */
+				if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
+				{
+					if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
+						status = MultiXactStatusForKeyShare;
+					else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
+						status = MultiXactStatusForShare;
+					else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
+					{
+						if (old_infomask2 & HEAP_KEYS_UPDATED)
+							status = MultiXactStatusForUpdate;
+						else
+							status = MultiXactStatusForNoKeyUpdate;
+					}
+					else
+					{
+						/*
+						 * LOCK_ONLY present alone (a pg_upgraded tuple marked
+						 * as share-locked in the old cluster) shouldn't be
+						 * seen in the middle of an update chain.
+						 */
+						elog(ERROR, "invalid lock status in tuple");
+					}
+				}
+				else
+				{
+					/* it's an update, but which kind? */
+					if (old_infomask2 & HEAP_KEYS_UPDATED)
+						status = MultiXactStatusUpdate;
+					else
+						status = MultiXactStatusNoKeyUpdate;
+				}
+
+				result = test_lockmode_for_conflict(status, rawxmax, mode,
+													&needwait);
+
+				/*
+				 * If the tuple was already locked by ourselves in a previous
+				 * iteration of this (say heap_lock_tuple was forced to
+				 * restart the locking loop because of a change in xmax), then
+				 * we hold the lock already on this tuple version and we don't
+				 * need to do anything; and this is not an error condition
+				 * either.  We just need to skip this tuple and continue
+				 * locking the next version in the update chain.
+				 */
+				if (result == HeapTupleSelfUpdated)
+					goto next;
+
+				if (needwait)
+				{
+					LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+					XactLockTableWait(rawxmax, rel, &mytup->t_self,
+									  XLTW_LockUpdated);
+					goto l4;
+				}
+				if (result != HeapTupleMayBeUpdated)
+				{
+					goto out_locked;
+				}
+			}
+		}
+
+		/* compute the new Xmax and infomask values for the tuple ... */
+		compute_new_xmax_infomask(xmax, old_infomask, mytup->t_data->t_infomask2,
+								  xid, mode, false,
+								  &new_xmax, &new_infomask, &new_infomask2);
+
+		if (PageIsAllVisible(BufferGetPage(buf)) &&
+			visibilitymap_clear(rel, block, vmbuffer,
+								VISIBILITYMAP_ALL_FROZEN))
+			cleared_all_frozen = true;
+
+		START_CRIT_SECTION();
+
+		/* ... and set them */
+		HeapTupleHeaderSetXmax(mytup->t_data, new_xmax);
+		mytup->t_data->t_infomask &= ~HEAP_XMAX_BITS;
+		mytup->t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+		mytup->t_data->t_infomask |= new_infomask;
+		mytup->t_data->t_infomask2 |= new_infomask2;
+
+		MarkBufferDirty(buf);
+
+		/* XLOG stuff */
+		if (RelationNeedsWAL(rel))
+		{
+			xl_heap_lock_updated xlrec;
+			XLogRecPtr	recptr;
+			Page		page = BufferGetPage(buf);
+
+			XLogBeginInsert();
+			XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
+
+			xlrec.offnum = ItemPointerGetOffsetNumber(&mytup->t_self);
+			xlrec.xmax = new_xmax;
+			xlrec.infobits_set = compute_infobits(new_infomask, new_infomask2);
+			xlrec.flags =
+				cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
+
+			XLogRegisterData((char *) &xlrec, SizeOfHeapLockUpdated);
+
+			recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_LOCK_UPDATED);
+
+			PageSetLSN(page, recptr);
+		}
+
+		END_CRIT_SECTION();
+
+next:
+		/* if we find the end of update chain, we're done. */
+		if (mytup->t_data->t_infomask & HEAP_XMAX_INVALID ||
+			ItemPointerEquals(&mytup->t_self, &mytup->t_data->t_ctid) ||
+			HeapTupleHeaderIsOnlyLocked(mytup->t_data))
+		{
+			result = HeapTupleMayBeUpdated;
+			goto out_locked;
+		}
+
+		/* tail recursion */
+		priorXmax = HeapTupleHeaderGetUpdateXid(mytup->t_data);
+		ItemPointerCopy(&(mytup->t_data->t_ctid), &tupid);
+		UnlockReleaseBuffer(buf);
+		if (vmbuffer != InvalidBuffer)
+			ReleaseBuffer(vmbuffer);
+	}
+
+	result = HeapTupleMayBeUpdated;
+
+out_locked:
+	UnlockReleaseBuffer(buf);
+
+	if (vmbuffer != InvalidBuffer)
+		ReleaseBuffer(vmbuffer);
+
+	return result;
+
+}
+
+/*
+ * heap_lock_updated_tuple
+ *		Follow update chain when locking an updated tuple, acquiring locks (row
+ *		marks) on the updated versions.
+ *
+ * The initial tuple is assumed to be already locked.
+ *
+ * This function doesn't check visibility, it just unconditionally marks the
+ * tuple(s) as locked.  If any tuple in the updated chain is being deleted
+ * concurrently (or updated with the key being modified), sleep until the
+ * transaction doing it is finished.
+ *
+ * Note that we don't acquire heavyweight tuple locks on the tuples we walk
+ * when we have to wait for other transactions to release them, as opposed to
+ * what heap_lock_tuple does.  The reason is that having more than one
+ * transaction walking the chain is probably uncommon enough that risk of
+ * starvation is not likely: one of the preconditions for being here is that
+ * the snapshot in use predates the update that created this tuple (because we
+ * started at an earlier version of the tuple), but at the same time such a
+ * transaction cannot be using repeatable read or serializable isolation
+ * levels, because that would lead to a serializability failure.
+ */
+static HTSU_Result
+heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid,
+						TransactionId xid, LockTupleMode mode)
+{
+	if (!ItemPointerEquals(&tuple->t_self, ctid))
+	{
+		/*
+		 * If this is the first possibly-multixact-able operation in the
+		 * current transaction, set my per-backend OldestMemberMXactId
+		 * setting. We can be certain that the transaction will never become a
+		 * member of any older MultiXactIds than that.  (We have to do this
+		 * even if we end up just using our own TransactionId below, since
+		 * some other backend could incorporate our XID into a MultiXact
+		 * immediately afterwards.)
+		 */
+		MultiXactIdSetOldestMember();
+
+		return heap_lock_updated_tuple_rec(rel, ctid, xid, mode);
+	}
+
+	/* nothing to lock */
+	return HeapTupleMayBeUpdated;
+}
+
+
+/*
+ *	heapam_lock_tuple - lock a tuple in shared or exclusive mode
+ *
+ * Note that this acquires a buffer pin, which the caller must release.
+ *
+ * Input parameters:
+ *	relation: relation containing tuple (caller must hold suitable lock)
+ *	tuple->t_self: TID of tuple to lock (rest of struct need not be valid)
+ *	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
+ *	follow_updates: if true, follow the update chain to also lock descendant
+ *		tuples.
+ *
+ * Output parameters:
+ *	*tuple: all fields filled in
+ *	*buffer: set to buffer holding tuple (pinned but not locked at exit)
+ *	*hufd: filled in failure cases (see below)
+ *
+ * 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.
+ *
+ * See README.tuplock for a thorough explanation of this mechanism.
+ */
+static HTSU_Result
+heapam_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;
+	ItemId		lp;
+	Page		page;
+	Buffer		vmbuffer = InvalidBuffer;
+	BlockNumber block;
+	TransactionId xid,
+				xmax;
+	uint16		old_infomask,
+				new_infomask,
+				new_infomask2;
+	bool		first_time = true;
+	bool		have_tuple_lock = false;
+	bool		cleared_all_frozen = false;
+	HeapTupleData	tuple;
+	Buffer 		buf;
+
+	Assert(stuple != NULL);
+
+	buf = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
+	block = ItemPointerGetBlockNumber(tid);
+	*buffer = buf;
+
+	/*
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
+	 */
+	if (PageIsAllVisible(BufferGetPage(buf)))
+		visibilitymap_pin(relation, block, &vmbuffer);
+
+	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+	page = BufferGetPage(buf);
+	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);
+
+l3:
+	result = HeapTupleSatisfiesUpdate(&tuple, cid, buf);
+
+	if (result == HeapTupleInvisible)
+	{
+		/*
+		 * This is possible, but only when locking a tuple for ON CONFLICT
+		 * UPDATE.  We return this value here rather than throwing an error in
+		 * order to give that case the opportunity to throw a more specific
+		 * error.
+		 */
+		result = HeapTupleInvisible;
+		goto out_locked;
+	}
+	else if (result == HeapTupleBeingUpdated || result == HeapTupleUpdated)
+	{
+		TransactionId xwait;
+		uint16		infomask;
+		uint16		infomask2;
+		bool		require_sleep;
+		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);
+
+		LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+
+		/*
+		 * If any subtransaction of the current top transaction already holds
+		 * a lock as strong as or stronger than what we're requesting, we
+		 * effectively hold the desired lock already.  We *must* succeed
+		 * without trying to take the tuple lock, else we will deadlock
+		 * against anyone wanting to acquire a stronger lock.
+		 *
+		 * Note we only do this the first time we loop on the HTSU result;
+		 * there is no point in testing in subsequent passes, because
+		 * evidently our own transaction cannot have acquired a new lock after
+		 * the first time we checked.
+		 */
+		if (first_time)
+		{
+			first_time = false;
+
+			if (infomask & HEAP_XMAX_IS_MULTI)
+			{
+				int			i;
+				int			nmembers;
+				MultiXactMember *members;
+
+				/*
+				 * We don't need to allow old multixacts here; if that had
+				 * been the case, HeapTupleSatisfiesUpdate would have returned
+				 * MayBeUpdated and we wouldn't be here.
+				 */
+				nmembers =
+					GetMultiXactIdMembers(xwait, &members, false,
+										  HEAP_XMAX_IS_LOCKED_ONLY(infomask));
+
+				for (i = 0; i < nmembers; i++)
+				{
+					/* only consider members of our own transaction */
+					if (!TransactionIdIsCurrentTransactionId(members[i].xid))
+						continue;
+
+					if (TUPLOCK_from_mxstatus(members[i].status) >= mode)
+					{
+						pfree(members);
+						result = HeapTupleMayBeUpdated;
+						goto out_unlocked;
+					}
+				}
+
+				if (members)
+					pfree(members);
+			}
+			else if (TransactionIdIsCurrentTransactionId(xwait))
+			{
+				switch (mode)
+				{
+					case LockTupleKeyShare:
+						Assert(HEAP_XMAX_IS_KEYSHR_LOCKED(infomask) ||
+							   HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
+							   HEAP_XMAX_IS_EXCL_LOCKED(infomask));
+						result = HeapTupleMayBeUpdated;
+						goto out_unlocked;
+					case LockTupleShare:
+						if (HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
+							HEAP_XMAX_IS_EXCL_LOCKED(infomask))
+						{
+							result = HeapTupleMayBeUpdated;
+							goto out_unlocked;
+						}
+						break;
+					case LockTupleNoKeyExclusive:
+						if (HEAP_XMAX_IS_EXCL_LOCKED(infomask))
+						{
+							result = HeapTupleMayBeUpdated;
+							goto out_unlocked;
+						}
+						break;
+					case LockTupleExclusive:
+						if (HEAP_XMAX_IS_EXCL_LOCKED(infomask) &&
+							infomask2 & HEAP_KEYS_UPDATED)
+						{
+							result = HeapTupleMayBeUpdated;
+							goto out_unlocked;
+						}
+						break;
+				}
+			}
+		}
+
+		/*
+		 * Initially assume that we will have to wait for the locking
+		 * transaction(s) to finish.  We check various cases below in which
+		 * this can be turned off.
+		 */
+		require_sleep = true;
+		if (mode == LockTupleKeyShare)
+		{
+			/*
+			 * If we're requesting KeyShare, and there's no update present, we
+			 * don't need to wait.  Even if there is an update, we can still
+			 * continue if the key hasn't been modified.
+			 *
+			 * However, if there are updates, we need to walk the update chain
+			 * to mark future versions of the row as locked, too.  That way,
+			 * if somebody deletes that future version, we're protected
+			 * against the key going away.  This locking of future versions
+			 * could block momentarily, if a concurrent transaction is
+			 * deleting a key; or it could return a value to the effect that
+			 * the transaction deleting the key has already committed.  So we
+			 * do this before re-locking the buffer; otherwise this would be
+			 * prone to deadlocks.
+			 *
+			 * Note that the TID we're locking was grabbed before we unlocked
+			 * the buffer.  For it to change while we're not looking, the
+			 * other properties we're testing for below after re-locking the
+			 * buffer would also change, in which case we would restart this
+			 * loop above.
+			 */
+			if (!(infomask2 & HEAP_KEYS_UPDATED))
+			{
+				bool		updated;
+
+				updated = !HEAP_XMAX_IS_LOCKED_ONLY(infomask);
+
+				/*
+				 * If there are updates, follow the update chain; bail out if
+				 * that cannot be done.
+				 */
+				if (follow_updates && updated)
+				{
+					HTSU_Result res;
+
+					res = heap_lock_updated_tuple(relation, &tuple, &t_ctid,
+												  GetCurrentTransactionId(),
+												  mode);
+					if (res != HeapTupleMayBeUpdated)
+					{
+						result = res;
+						/* recovery code expects to have buffer lock held */
+						LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+						goto failed;
+					}
+				}
+
+				LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+				/*
+				 * Make sure it's still an appropriate lock, else start over.
+				 * Also, if it wasn't updated before we released the lock, but
+				 * is updated now, we start over too; the reason is that we
+				 * 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) ||
+					 !updated))
+					goto l3;
+
+				/* Things look okay, so we can skip sleeping */
+				require_sleep = false;
+
+				/*
+				 * Note we allow Xmax to change here; other updaters/lockers
+				 * could have modified it before we grabbed the buffer lock.
+				 * However, this is not a problem, because with the recheck we
+				 * just did we ensure that they still don't conflict with the
+				 * lock we want.
+				 */
+			}
+		}
+		else if (mode == LockTupleShare)
+		{
+			/*
+			 * If we're requesting Share, we can similarly avoid sleeping if
+			 * there's no update and no exclusive lock present.
+			 */
+			if (HEAP_XMAX_IS_LOCKED_ONLY(infomask) &&
+				!HEAP_XMAX_IS_EXCL_LOCKED(infomask))
+			{
+				LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+				/*
+				 * 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))
+					goto l3;
+				require_sleep = false;
+			}
+		}
+		else if (mode == LockTupleNoKeyExclusive)
+		{
+			/*
+			 * If we're requesting NoKeyExclusive, we might also be able to
+			 * avoid sleeping; just ensure that there no conflicting lock
+			 * already acquired.
+			 */
+			if (infomask & HEAP_XMAX_IS_MULTI)
+			{
+				if (!DoesMultiXactIdConflict((MultiXactId) xwait, infomask,
+											 mode))
+				{
+					/*
+					 * No conflict, but if the xmax changed under us in the
+					 * meantime, start over.
+					 */
+					LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+					if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
+						!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple.t_data),
+											 xwait))
+						goto l3;
+
+					/* otherwise, we're good */
+					require_sleep = false;
+				}
+			}
+			else if (HEAP_XMAX_IS_KEYSHR_LOCKED(infomask))
+			{
+				LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+				/* if the xmax changed in the meantime, start over */
+				if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
+					!TransactionIdEquals(
+										 HeapTupleHeaderGetRawXmax(tuple.t_data),
+										 xwait))
+					goto l3;
+				/* otherwise, we're good */
+				require_sleep = false;
+			}
+		}
+
+		/*
+		 * As a check independent from those above, we can also avoid sleeping
+		 * if the current transaction is the sole locker of the tuple.  Note
+		 * that the strength of the lock already held is irrelevant; this is
+		 * not about recording the lock in Xmax (which will be done regardless
+		 * of this optimization, below).  Also, note that the cases where we
+		 * hold a lock stronger than we are requesting are already handled
+		 * above by not doing anything.
+		 *
+		 * Note we only deal with the non-multixact case here; MultiXactIdWait
+		 * is well equipped to deal with this situation on its own.
+		 */
+		if (require_sleep && !(infomask & HEAP_XMAX_IS_MULTI) &&
+			TransactionIdIsCurrentTransactionId(xwait))
+		{
+			/* ... but if the xmax changed in the meantime, start over */
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+			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));
+			require_sleep = false;
+		}
+
+		/*
+		 * Time to sleep on the other transaction/multixact, if necessary.
+		 *
+		 * If the other transaction is an update that's already committed,
+		 * then sleeping cannot possibly do any good: if we're required to
+		 * sleep, get out to raise an error instead.
+		 *
+		 * By here, we either have already acquired the buffer exclusive lock,
+		 * 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)
+		{
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+			goto failed;
+		}
+		else if (require_sleep)
+		{
+			/*
+			 * Acquire tuple lock to establish our priority for the tuple, or
+			 * die trying.  LockTuple will release us when we are next-in-line
+			 * for the tuple.  We must do this even if we are share-locking.
+			 *
+			 * If we are forced to "start over" below, we keep the tuple lock;
+			 * this arranges that we stay at the head of the line while
+			 * rechecking tuple state.
+			 */
+			if (!heap_acquire_tuplock(relation, tid, mode, wait_policy,
+									  &have_tuple_lock))
+			{
+				/*
+				 * This can only happen if wait_policy is Skip and the lock
+				 * couldn't be obtained.
+				 */
+				result = HeapTupleWouldBlock;
+				/* recovery code expects to have buffer lock held */
+				LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+				goto failed;
+			}
+
+			if (infomask & HEAP_XMAX_IS_MULTI)
+			{
+				MultiXactStatus status = get_mxact_status_for_lock(mode, false);
+
+				/* We only ever lock tuples, never update them */
+				if (status >= MultiXactStatusNoKeyUpdate)
+					elog(ERROR, "invalid lock mode in heap_lock_tuple");
+
+				/* wait for multixact to end, or die trying  */
+				switch (wait_policy)
+				{
+					case LockWaitBlock:
+						MultiXactIdWait((MultiXactId) xwait, status, infomask,
+										relation, &tuple.t_self, XLTW_Lock, NULL);
+						break;
+					case LockWaitSkip:
+						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
+														status, infomask, relation,
+														NULL))
+						{
+							result = HeapTupleWouldBlock;
+							/* recovery code expects to have buffer lock held */
+							LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+							goto failed;
+						}
+						break;
+					case LockWaitError:
+						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
+														status, infomask, relation,
+														NULL))
+							ereport(ERROR,
+									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+									 errmsg("could not obtain lock on row in relation \"%s\"",
+											RelationGetRelationName(relation))));
+
+						break;
+				}
+
+				/*
+				 * Of course, the multixact might not be done here: if we're
+				 * requesting a light lock mode, other transactions with light
+				 * locks could still be alive, as well as locks owned by our
+				 * own xact or other subxacts of this backend.  We need to
+				 * preserve the surviving MultiXact members.  Note that it
+				 * isn't absolutely necessary in the latter case, but doing so
+				 * is simpler.
+				 */
+			}
+			else
+			{
+				/* wait for regular transaction to end, or die trying */
+				switch (wait_policy)
+				{
+					case LockWaitBlock:
+						XactLockTableWait(xwait, relation, &tuple.t_self,
+										  XLTW_Lock);
+						break;
+					case LockWaitSkip:
+						if (!ConditionalXactLockTableWait(xwait))
+						{
+							result = HeapTupleWouldBlock;
+							/* recovery code expects to have buffer lock held */
+							LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+							goto failed;
+						}
+						break;
+					case LockWaitError:
+						if (!ConditionalXactLockTableWait(xwait))
+							ereport(ERROR,
+									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+									 errmsg("could not obtain lock on row in relation \"%s\"",
+											RelationGetRelationName(relation))));
+						break;
+				}
+			}
+
+			/* if there are updates, follow the update chain */
+			if (follow_updates && !HEAP_XMAX_IS_LOCKED_ONLY(infomask))
+			{
+				HTSU_Result res;
+
+				res = heap_lock_updated_tuple(relation, &tuple, &t_ctid,
+											  GetCurrentTransactionId(),
+											  mode);
+				if (res != HeapTupleMayBeUpdated)
+				{
+					result = res;
+					/* recovery code expects to have buffer lock held */
+					LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+					goto failed;
+				}
+			}
+
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+			/*
+			 * xwait is done, but if xwait had just locked the tuple then some
+			 * 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),
+									 xwait))
+				goto l3;
+
+			if (!(infomask & HEAP_XMAX_IS_MULTI))
+			{
+				/*
+				 * Otherwise check if it committed or aborted.  Note we cannot
+				 * be here if the tuple was only locked by somebody who didn't
+				 * conflict with us; that would have been handled above.  So
+				 * that transaction must necessarily be gone by now.  But
+				 * don't check for this in the multixact case, because some
+				 * locker transactions might still be running.
+				 */
+				UpdateXmaxHintBits(tuple.t_data, buf, xwait);
+			}
+		}
+
+		/* By here, we're certain that we hold buffer exclusive lock again */
+
+		/*
+		 * We may lock if previous xmax aborted, or if it committed but only
+		 * locked the tuple without updating it; or if we didn't have to wait
+		 * 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))
+			result = HeapTupleMayBeUpdated;
+		else
+			result = HeapTupleUpdated;
+	}
+
+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);
+		if (result == HeapTupleSelfUpdated)
+			hufd->cmax = HeapTupleHeaderGetCmax(tuple.t_data);
+		else
+			hufd->cmax = InvalidCommandId;
+		goto out_locked;
+	}
+
+	/*
+	 * If we didn't pin the visibility map page and the page has become all
+	 * visible while we were busy locking the buffer, or during some
+	 * subsequent window during which we had it unlocked, we'll have to unlock
+	 * and re-lock, to avoid holding the buffer lock across I/O.  That's a bit
+	 * unfortunate, especially since we'll now have to recheck whether the
+	 * tuple has been locked or updated under us, but hopefully it won't
+	 * happen very often.
+	 */
+	if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
+	{
+		LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+		visibilitymap_pin(relation, block, &vmbuffer);
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+		goto l3;
+	}
+
+	xmax = HeapTupleHeaderGetRawXmax(tuple.t_data);
+	old_infomask = tuple.t_data->t_infomask;
+
+	/*
+	 * If this is the first possibly-multixact-able operation in the current
+	 * transaction, set my per-backend OldestMemberMXactId setting. We can be
+	 * certain that the transaction will never become a member of any older
+	 * MultiXactIds than that.  (We have to do this even if we end up just
+	 * using our own TransactionId below, since some other backend could
+	 * incorporate our XID into a MultiXact immediately afterwards.)
+	 */
+	MultiXactIdSetOldestMember();
+
+	/*
+	 * Compute the new xmax and infomask to store into the tuple.  Note we do
+	 * 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,
+							  GetCurrentTransactionId(), mode, false,
+							  &xid, &new_infomask, &new_infomask2);
+
+	START_CRIT_SECTION();
+
+	/*
+	 * Store transaction information of xact locking the tuple.
+	 *
+	 * Note: Cmax is meaningless in this context, so don't set it; this avoids
+	 * possibly generating a useless combo CID.  Moreover, if we're locking a
+	 * previously updated tuple, it's important to preserve the Cmax.
+	 *
+	 * 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;
+	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
+		HeapTupleHeaderClearHotUpdated(tuple.t_data);
+	HeapTupleHeaderSetXmax(tuple.t_data, xid);
+
+	/*
+	 * Make sure there is no forward chain link in t_ctid.  Note that in the
+	 * cases where the tuple has been updated, we must not overwrite t_ctid,
+	 * because it was set by the updater.  Moreover, if the tuple has been
+	 * updated, we need to follow the update chain to lock the new versions of
+	 * the tuple as well.
+	 */
+	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
+		tuple.t_data->t_ctid = *tid;
+
+	/* Clear only the all-frozen bit on visibility map if needed */
+	if (PageIsAllVisible(page) &&
+		visibilitymap_clear(relation, block, vmbuffer,
+							VISIBILITYMAP_ALL_FROZEN))
+		cleared_all_frozen = true;
+
+
+	MarkBufferDirty(buf);
+
+	/*
+	 * XLOG stuff.  You might think that we don't need an XLOG record because
+	 * there is no state change worth restoring after a crash.  You would be
+	 * wrong however: we have just written either a TransactionId or a
+	 * MultiXactId that may never have been seen on disk before, and we need
+	 * to make sure that there are XLOG entries covering those ID numbers.
+	 * Else the same IDs might be re-used after a crash, which would be
+	 * disastrous if this page made it to disk before the crash.  Essentially
+	 * we have to enforce the WAL log-before-data rule even in this case.
+	 * (Also, in a PITR log-shipping or 2PC environment, we have to have XLOG
+	 * entries for everything anyway.)
+	 */
+	if (RelationNeedsWAL(relation))
+	{
+		xl_heap_lock xlrec;
+		XLogRecPtr	recptr;
+
+		XLogBeginInsert();
+		XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
+
+		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple.t_self);
+		xlrec.locking_xid = xid;
+		xlrec.infobits_set = compute_infobits(new_infomask,
+											  tuple.t_data->t_infomask2);
+		xlrec.flags = cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
+		XLogRegisterData((char *) &xlrec, SizeOfHeapLock);
+
+		/* we don't decode row locks atm, so no need to log the origin */
+
+		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_LOCK);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	result = HeapTupleMayBeUpdated;
+
+out_locked:
+	LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+
+out_unlocked:
+	if (BufferIsValid(vmbuffer))
+		ReleaseBuffer(vmbuffer);
+
+	/*
+	 * Don't update the visibility map here. Locking a tuple doesn't change
+	 * visibility info.
+	 */
+
+	/*
+	 * Now that we have successfully marked the tuple as locked, we can
+	 * release the lmgr tuple lock, if we had it.
+	 */
+	if (have_tuple_lock)
+		UnlockTupleTuplock(relation, tid, mode);
+
+	*stuple = heap_copytuple(&tuple);
+	return result;
+}
+
+/*
+ *	heapam_get_latest_tid -  get the latest tid of a specified tuple
+ *
+ * Actually, this gets the latest version that is visible according to
+ * the passed snapshot.  You can pass SnapshotDirty to get the very latest,
+ * possibly uncommitted version.
+ *
+ * *tid is both an input and an output parameter: it is updated to
+ * show the latest version of the row.  Note that it will not be changed
+ * if no version of the row passes the snapshot test.
+ */
+static void
+heapam_get_latest_tid(Relation relation,
+					Snapshot snapshot,
+					ItemPointer tid)
+{
+	BlockNumber blk;
+	ItemPointerData ctid;
+	TransactionId priorXmax;
+
+	/* this is to avoid Assert failures on bad input */
+	if (!ItemPointerIsValid(tid))
+		return;
+
+	/*
+	 * Since this can be called with user-supplied TID, don't trust the input
+	 * too much.  (RelationGetNumberOfBlocks is an expensive check, so we
+	 * don't check t_ctid links again this way.  Note that it would not do to
+	 * call it just once and save the result, either.)
+	 */
+	blk = ItemPointerGetBlockNumber(tid);
+	if (blk >= RelationGetNumberOfBlocks(relation))
+		elog(ERROR, "block number %u is out of range for relation \"%s\"",
+			 blk, RelationGetRelationName(relation));
+
+	/*
+	 * Loop to chase down t_ctid links.  At top of loop, ctid is the tuple we
+	 * need to examine, and *tid is the TID we will return if ctid turns out
+	 * to be bogus.
+	 *
+	 * Note that we will loop until we reach the end of the t_ctid chain.
+	 * Depending on the snapshot passed, there might be at most one visible
+	 * version of the row, but we don't try to optimize for that.
+	 */
+	ctid = *tid;
+	priorXmax = InvalidTransactionId;	/* cannot check first XMIN */
+	for (;;)
+	{
+		Buffer		buffer;
+		Page		page;
+		OffsetNumber offnum;
+		ItemId		lp;
+		HeapTupleData tp;
+		bool		valid;
+
+		/*
+		 * Read, pin, and lock the page.
+		 */
+		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&ctid));
+		LockBuffer(buffer, BUFFER_LOCK_SHARE);
+		page = BufferGetPage(buffer);
+		TestForOldSnapshot(snapshot, relation, page);
+
+		/*
+		 * Check for bogus item number.  This is not treated as an error
+		 * condition because it can happen while following a t_ctid link. We
+		 * just assume that the prior tid is OK and return it unchanged.
+		 */
+		offnum = ItemPointerGetOffsetNumber(&ctid);
+		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+		lp = PageGetItemId(page, offnum);
+		if (!ItemIdIsNormal(lp))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+
+		/* OK to access the tuple */
+		tp.t_self = ctid;
+		tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+		tp.t_len = ItemIdGetLength(lp);
+		tp.t_tableOid = RelationGetRelid(relation);
+
+		/*
+		 * After following a t_ctid link, we might arrive at an unrelated
+		 * tuple.  Check for XMIN match.
+		 */
+		if (TransactionIdIsValid(priorXmax) &&
+			!TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(tp.t_data)))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+
+		/*
+		 * Check time qualification of tuple; if visible, set it as the new
+		 * result candidate.
+		 */
+		valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, &tp, snapshot, buffer);
+		CheckForSerializableConflictOut(valid, relation, &tp, buffer, snapshot);
+		if (valid)
+			*tid = ctid;
+
+		/*
+		 * If there's a valid t_ctid link, follow it, else we're done.
+		 */
+		if ((tp.t_data->t_infomask & HEAP_XMAX_INVALID) ||
+			HeapTupleHeaderIsOnlyLocked(tp.t_data) ||
+			ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+
+		ctid = tp.t_data->t_ctid;
+		priorXmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
+		UnlockReleaseBuffer(buffer);
+	}							/* end of loop */
+}
+
+
+/*
+ *	heapam_sync		- sync a heap, for use when no WAL has been written
+ *
+ * This forces the heap contents (including TOAST heap if any) down to disk.
+ * If we skipped using WAL, and WAL is otherwise needed, we must force the
+ * relation down to disk before it's safe to commit the transaction.  This
+ * requires writing out any dirty buffers and then doing a forced fsync.
+ *
+ * Indexes are not touched.  (Currently, index operations associated with
+ * the commands that use this are WAL-logged and so do not need fsync.
+ * That behavior might change someday, but in any case it's likely that
+ * any fsync decisions required would be per-index and hence not appropriate
+ * to be done here.)
+ */
+static void
+heapam_sync(Relation rel)
+{
+	/* non-WAL-logged tables never need fsync */
+	if (!RelationNeedsWAL(rel))
+		return;
+
+	/* main heap */
+	FlushRelationBuffers(rel);
+	/* FlushRelationBuffers will have opened rd_smgr */
+	smgrimmedsync(rel->rd_smgr, MAIN_FORKNUM);
+
+	/* FSM is not critical, don't bother syncing it */
+
+	/* toast heap, if any */
+	if (OidIsValid(rel->rd_rel->reltoastrelid))
+	{
+		Relation	toastrel;
+
+		toastrel = heap_open(rel->rd_rel->reltoastrelid, AccessShareLock);
+		FlushRelationBuffers(toastrel);
+		smgrimmedsync(toastrel->rd_smgr, MAIN_FORKNUM);
+		heap_close(toastrel, AccessShareLock);
+	}
+}
+
+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 bool
+heapam_tuple_is_heaopnly(StorageTuple tuple)
+{
+	return HeapTupleIsHeapOnly((HeapTuple)tuple);
+}
+
+static StorageTuple
+heapam_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;
+}
+
 Datum
 heapam_storage_handler(PG_FUNCTION_ARGS)
 {
 	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
 
+    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 = heapam_multi_insert;
+
+    amroutine->speculative_finish = heapam_finish_speculative;
+    amroutine->speculative_abort = heapam_abort_speculative;
+
+    amroutine->get_tuple_data = heapam_get_tuple_data;
+    amroutine->tuple_is_heaponly = heapam_tuple_is_heaopnly;
+    amroutine->tuple_from_datum = heapam_form_tuple_by_datum;
+    amroutine->tuple_get_latest_tid = heapam_get_latest_tid;
+
+    amroutine->relation_sync = heapam_sync;
+
 	amroutine->slot_storageam = heapam_storage_slot_handler;
 
 	amroutine->snapshot_satisfies[MVCC_VISIBILITY] = HeapTupleSatisfiesMVCC;
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 191f088..8fba61c 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -111,6 +111,7 @@
 #include "access/heapam_common.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"
@@ -127,13 +128,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
@@ -358,7 +359,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/storageam.c b/src/backend/access/heap/storageam.c
new file mode 100644
index 0000000..f8e7a4f
--- /dev/null
+++ b/src/backend/access/heap/storageam.c
@@ -0,0 +1,303 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/heap/storageam.c
+ *
+ *
+ * NOTES
+ *	  This file contains the storage_ routines which implement
+ *	  the POSTGRES storage access method used for all POSTGRES
+ *	  relations.
+ *
+ *-------------------------------------------------------------------------
+ */
+#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 "access/relscan.h"
+#include "access/storageam.h"
+#include "access/storageamapi.h"
+#include "access/tuptoaster.h"
+#include "access/valid.h"
+#include "access/visibilitymap.h"
+#include "access/xloginsert.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/bufmgr.h"
+#include "storage/lmgr.h"
+#include "storage/predicate.h"
+#include "storage/procarray.h"
+#include "storage/smgr.h"
+#include "storage/spin.h"
+#include "utils/builtins.h"
+#include "utils/inval.h"
+#include "utils/rel.h"
+#include "utils/tqual.h"
+
+
+/*
+ *	storage_fetch		- retrieve tuple with given tid
+ *
+ * On entry, tuple->t_self is the TID to fetch.  We pin the buffer holding
+ * the tuple, fill in the remaining fields of *tuple, and check the tuple
+ * against the specified snapshot.
+ *
+ * If successful (tuple found and passes snapshot time qual), then *userbuf
+ * is set to the buffer holding the tuple and TRUE is returned.  The caller
+ * must unpin the buffer when done with the tuple.
+ *
+ * If the tuple is not found (ie, item number references a deleted slot),
+ * then tuple->t_data is set to NULL and FALSE is returned.
+ *
+ * If the tuple is found but fails the time qual check, then FALSE is returned
+ * but tuple->t_data is left pointing to the tuple.
+ *
+ * keep_buf determines what is done with the buffer in the FALSE-result cases.
+ * When the caller specifies keep_buf = true, we retain the pin on the buffer
+ * and return it in *userbuf (so the caller must eventually unpin it); when
+ * keep_buf = false, the pin is released and *userbuf is set to InvalidBuffer.
+ *
+ * stats_relation is the relation to charge the heap_fetch operation against
+ * for statistical purposes.  (This could be the heap rel itself, an
+ * associated index, or NULL to not count the fetch at all.)
+ *
+ * heap_fetch does not follow HOT chains: only the exact TID requested will
+ * be fetched.
+ *
+ * It is somewhat inconsistent that we ereport() on invalid block number but
+ * return false on invalid item number.  There are a couple of reasons though.
+ * One is that the caller can relatively easily check the block number for
+ * validity, but cannot check the item number without reading the page
+ * himself.  Another is that when we are following a t_ctid link, we can be
+ * reasonably confident that the page number is valid (since VACUUM shouldn't
+ * truncate off the destination page without having killed the referencing
+ * tuple first), but the item number might well not be good.
+ */
+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
+ *
+ * Note that this acquires a buffer pin, which the caller must release.
+ *
+ * Input parameters:
+ *	relation: relation containing tuple (caller must hold suitable lock)
+ *	tuple->t_self: TID of tuple to lock (rest of struct need not be valid)
+ *	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
+ *	follow_updates: if true, follow the update chain to also lock descendant
+ *		tuples.
+ *
+ * Output parameters:
+ *	*tuple: all fields filled in
+ *	*buffer: set to buffer holding tuple (pinned but not locked at exit)
+ *	*hufd: filled in failure cases (see below)
+ *
+ * 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.
+ *
+ * See README.tuplock for a thorough explanation of this mechanism.
+ */
+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)
+{
+	return relation->rd_stamroutine->tuple_insert(relation, slot, cid,
+							options, bistate);
+}
+
+/*
+ * 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,
+				   CommandId cid, Snapshot crosscheck, bool wait,
+				   HeapUpdateFailureData *hufd, LockTupleMode *lockmode)
+{
+	return relation->rd_stamroutine->tuple_update(relation, otid, slot, cid,
+							crosscheck, wait, hufd, lockmode);
+}
+
+
+/*
+ *	storage_multi_insert	- insert multiple tuple into a storage
+ *
+ * This is like heap_insert(), but inserts multiple tuples in one operation.
+ * That's faster than calling heap_insert() in a loop, because when multiple
+ * tuples can be inserted on a single page, we can write just a single WAL
+ * record covering all of them, and only need to lock/unlock the page once.
+ *
+ * Note: this leaks memory into the current memory context. You can create a
+ * temporary context before calling this, if that's a problem.
+ */
+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_finish_speculative - mark speculative insertion as successful
+ *
+ * To successfully finish a speculative insertion we have to clear speculative
+ * token from tuple.  To do so the t_ctid field, which will contain a
+ * speculative token value, is modified in place to point to the tuple itself,
+ * which is characteristic of a newly inserted ordinary tuple.
+ *
+ * NB: It is not ok to commit without either finishing or aborting a
+ * speculative insertion.  We could treat speculative tuples of committed
+ * transactions implicitly as completed, but then we would have to be prepared
+ * to deal with speculative tokens on committed tuples.  That wouldn't be
+ * difficult - no-one looks at the ctid field of a tuple with invalid xmax -
+ * but clearing the token at completion isn't very expensive either.
+ * An explicit confirmation WAL record also makes logical decoding simpler.
+ */
+void
+storage_finish_speculative(Relation relation, TupleTableSlot *slot)
+{
+	relation->rd_stamroutine->speculative_finish(relation, slot);
+}
+
+/*
+ *	storage_abort_speculative - kill a speculatively inserted tuple
+ *
+ * Marks a tuple that was speculatively inserted in the same command as dead,
+ * by setting its xmin as invalid.  That makes it immediately appear as dead
+ * to all transactions, including our own.  In particular, it makes
+ * HeapTupleSatisfiesDirty() regard the tuple as dead, so that another backend
+ * inserting a duplicate key value won't unnecessarily wait for our whole
+ * transaction to finish (it'll just wait for our speculative insertion to
+ * finish).
+ *
+ * Killing the tuple prevents "unprincipled deadlocks", which are deadlocks
+ * that arise due to a mutual dependency that is not user visible.  By
+ * definition, unprincipled deadlocks cannot be prevented by the user
+ * reordering lock acquisition in client code, because the implementation level
+ * lock acquisitions are not under the user's direct control.  If speculative
+ * inserters did not take this precaution, then under high concurrency they
+ * could deadlock with each other, which would not be acceptable.
+ *
+ * This is somewhat redundant with heap_delete, but we prefer to have a
+ * dedicated routine with stripped down requirements.  Note that this is also
+ * used to delete the TOAST tuples created during speculative insertion.
+ *
+ * This routine does not affect logical decoding as it only looks at
+ * confirmation records.
+ */
+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);
+}
+
+bool
+storage_tuple_is_heaponly(Relation relation, StorageTuple tuple)
+{
+	return relation->rd_stamroutine->tuple_is_heaponly(tuple);
+}
+
+StorageTuple
+storage_tuple_by_datum(Relation relation, Datum data, Oid tableoid)
+{
+	return relation->rd_stamroutine->tuple_from_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
+ *
+ * This forces the heap contents (including TOAST heap if any) down to disk.
+ * If we skipped using WAL, and WAL is otherwise needed, we must force the
+ * relation down to disk before it's safe to commit the transaction.  This
+ * requires writing out any dirty buffers and then doing a forced fsync.
+ *
+ * Indexes are not touched.  (Currently, index operations associated with
+ * the commands that use this are WAL-logged and so do not need fsync.
+ * That behavior might change someday, but in any case it's likely that
+ * any fsync decisions required would be per-index and hence not appropriate
+ * to be done here.)
+ */
+void
+storage_sync(Relation rel)
+{
+	rel->rd_stamroutine->relation_sync(rel);
+}
diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index 5a8f1da..d766a6e 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,12 @@ 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/commands/copy.c b/src/backend/commands/copy.c
index 8456bfd..c81ddf5 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -22,6 +22,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"
@@ -2694,8 +2695,6 @@ CopyFrom(CopyState cstate)
 
 			if (slot == NULL)	/* "do nothing" */
 				skip_tuple = true;
-			else				/* trigger might have changed tuple */
-				tuple = ExecHeapifySlot(slot);
 		}
 
 		if (!skip_tuple)
@@ -2758,19 +2757,18 @@ 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);
+					storage_insert(resultRelInfo->ri_RelationDesc, slot, mycid, hi_options, bistate);
 
 					if (resultRelInfo->ri_NumIndices > 0)
 						recheckIndexes = ExecInsertIndexTuples(slot,
-															   &(tuple->t_self),
+															   &(slot->tts_tid),
 															   estate,
 															   false,
 															   NULL,
 															   NIL);
 
 					/* AFTER ROW INSERT Triggers */
-					ExecARInsertTriggers(estate, resultRelInfo, tuple,
+					ExecARInsertTriggers(estate, resultRelInfo, slot,
 										 recheckIndexes, cstate->transition_capture);
 
 					list_free(recheckIndexes);
@@ -2866,7 +2864,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;
 }
@@ -2899,12 +2897,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);
 
 	/*
@@ -2923,7 +2921,7 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 				ExecInsertIndexTuples(myslot, &(bufferedTuples[i]->t_self),
 									  estate, false, NULL, NIL);
 			ExecARInsertTriggers(estate, resultRelInfo,
-								 bufferedTuples[i],
+								 myslot,
 								 recheckIndexes, cstate->transition_capture);
 			list_free(recheckIndexes);
 		}
@@ -2940,8 +2938,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 a0ec444..d119149 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,24 @@ 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);
+		slot->tts_tupleOid = InvalidOid;
 
-	heap_insert(myState->rel,
-				tuple,
-				myState->output_cid,
-				myState->hi_options,
-				myState->bistate);
+	storage_insert(myState->rel,
+					 slot,
+					myState->output_cid,
+					myState->hi_options,
+					myState->bistate);
 
 	/* We know this is a newly created relation, so there are no indexes */
 
@@ -619,7 +619,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 b440740..6102481 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,16 +492,15 @@ 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);
+	ExecMaterializeSlot(slot);
 
-	heap_insert(myState->transientrel,
-				tuple,
+	storage_insert(myState->transientrel,
+						slot,
 				myState->output_cid,
 				myState->hi_options,
 				myState->bistate);
@@ -522,7 +522,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 0f08245..d46e2e6 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"
@@ -4601,7 +4602,7 @@ 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);
 
 			ResetExprContext(econtext);
 
@@ -4625,7 +4626,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 8634473..d2438ce 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2411,17 +2411,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 *
@@ -3061,9 +3065,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)
 	{
@@ -3079,11 +3084,11 @@ GetTupleForTrigger(EState *estate,
 		 * lock tuple for update
 		 */
 ltrmark:;
-		tuple.t_self = *tid;
-		test = heap_lock_tuple(relation, &tuple,
+		test = storage_lock_tuple(relation, tid, &tuple,
 							   estate->es_output_cid,
 							   lockmode, LockWaitBlock,
 							   false, &buffer, &hufd);
+		result = tuple;
 		switch (test)
 		{
 			case HeapTupleSelfUpdated:
@@ -3115,7 +3120,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;
@@ -3161,6 +3167,7 @@ ltrmark:;
 	{
 		Page		page;
 		ItemId		lp;
+		HeapTupleData tupledata;
 
 		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 
@@ -3179,17 +3186,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;
 }
 
@@ -3898,8 +3905,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;
@@ -3974,10 +3981,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
@@ -3991,10 +3997,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/execMain.c b/src/backend/executor/execMain.c
index 2946a0e..8bf0f61 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1887,7 +1887,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;
 
@@ -1967,7 +1967,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				 */
 				if (resultRelInfo->ri_PartitionRoot)
 				{
-					HeapTuple	tuple = ExecFetchSlotTuple(slot);
+					StorageTuple	tuple = ExecFetchSlotTuple(slot);
 					TupleConversionMap *map;
 
 					rel = resultRelInfo->ri_PartitionRoot;
@@ -2014,7 +2014,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;
 
@@ -2473,7 +2473,8 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
 			 ItemPointer tid, TransactionId priorXmax)
 {
 	TupleTableSlot *slot;
-	HeapTuple	copyTuple;
+	StorageTuple copyTuple;
+	tuple_data	t_data;
 
 	Assert(rti > 0);
 
@@ -2490,7 +2491,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.
@@ -2521,7 +2524,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
@@ -2554,14 +2557,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
@@ -2569,12 +2572,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;
@@ -2588,8 +2591,8 @@ 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),
-									 priorXmax))
+			t_data = storage_tuple_get_data(relation, tuple, XMIN);
+			if (!TransactionIdEquals(t_data.xid, priorXmax))
 			{
 				ReleaseBuffer(buffer);
 				return NULL;
@@ -2610,7 +2613,8 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 				{
 					case LockWaitBlock:
 						XactLockTableWait(SnapshotDirty.xmax,
-										  relation, &tuple.t_self,
+										  relation,
+										  tid,
 										  XLTW_FetchUpdated);
 						break;
 					case LockWaitSkip:
@@ -2639,8 +2643,9 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 			 * that priorXmax == xmin, so we can test that variable instead of
 			 * doing HeapTupleHeaderGetXmin again.
 			 */
+			t_data = storage_tuple_get_data(relation, tuple, CMIN);
 			if (TransactionIdIsCurrentTransactionId(priorXmax) &&
-				HeapTupleHeaderGetCmin(tuple.t_data) >= estate->es_output_cid)
+				t_data.cid >= estate->es_output_cid)
 			{
 				ReleaseBuffer(buffer);
 				return NULL;
@@ -2649,7 +2654,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 			/*
 			 * This is a live tuple, so now try to lock it.
 			 */
-			test = heap_lock_tuple(relation, &tuple,
+			test = storage_lock_tuple(relation, tid, tuple,
 								   estate->es_output_cid,
 								   lockmode, wait_policy,
 								   false, &buffer, &hufd);
@@ -2688,12 +2693,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;
@@ -2715,10 +2723,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;
 		}
@@ -2727,7 +2731,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;
@@ -2736,8 +2740,8 @@ 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),
-								 priorXmax))
+		t_data = storage_tuple_get_data(relation, tuple, XMIN);
+		if (!TransactionIdEquals(t_data.xid, priorXmax))
 		{
 			ReleaseBuffer(buffer);
 			return NULL;
@@ -2755,7 +2759,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);
@@ -2763,17 +2769,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;
 }
 
 /*
@@ -2819,7 +2827,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;
 
@@ -2838,7 +2846,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;
@@ -2866,7 +2874,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");
@@ -2897,8 +2905,6 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 
 		if (erm->markType == ROW_MARK_REFERENCE)
 		{
-			HeapTuple	copyTuple;
-
 			Assert(erm->relation != NULL);
 
 			/* fetch the tuple's ctid */
@@ -2922,11 +2928,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,
+				tuple = fdwroutine->RefetchForeignRow(epqstate->estate,
 														  erm,
 														  datum,
 														  &updated);
-				if (copyTuple == NULL)
+				if (tuple == NULL)
 					elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck");
 
 				/*
@@ -2940,23 +2946,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,
+				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 */
@@ -2966,19 +2967,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 = erm->relation->rd_stamroutine->tuple_from_datum(datum, erm->relid);
+
+			/* store tuple */
+			EvalPlanQualSetTuple(epqstate, erm->rti, tuple);
+
 		}
 	}
 }
@@ -3144,8 +3138,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 d424074..a1c2319 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),
+		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),
+		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,7 @@ void
 ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 {
 	bool		skip_tuple = false;
-	HeapTuple	tuple;
+	StorageTuple	tuple;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 
@@ -349,6 +351,7 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 	if (!skip_tuple)
 	{
 		List	   *recheckIndexes = NIL;
+		tuple_data	t_data;
 
 		/* Check the constraints of the tuple */
 		if (rel->rd_att->constr)
@@ -359,14 +362,15 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 
 		/* OK, store the tuple and create index entries for it */
 		simple_heap_insert(rel, tuple);
+		t_data = storage_tuple_get_data(rel, tuple, TID);
 
 		if (resultRelInfo->ri_NumIndices > 0)
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+			recheckIndexes = ExecInsertIndexTuples(slot, &(t_data.tid),
 												   estate, false, NULL,
 												   NIL);
 
 		/* AFTER ROW INSERT Triggers */
-		ExecARInsertTriggers(estate, resultRelInfo, tuple,
+		ExecARInsertTriggers(estate, resultRelInfo, slot,
 							 recheckIndexes, NULL);
 
 		/*
@@ -390,7 +394,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);
@@ -426,8 +430,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 		/* OK, update the tuple and index entries for it */
 		simple_heap_update(rel, tid, tuple);
 
-		if (resultRelInfo->ri_NumIndices > 0 &&
-			!HeapTupleIsHeapOnly(tuple))
+		if (resultRelInfo->ri_NumIndices > 0 && storage_tuple_is_heaponly(rel, tuple))
 			recheckIndexes = ExecInsertIndexTuples(slot, tid,
 												   estate, false, NULL,
 												   NIL);
diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c
index 9389560..f06f34a 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,
+		test = storage_lock_tuple(erm->relation, &tid, &tuple,
 							   estate->es_output_cid,
 							   lockmode, erm->waitPolicy, true,
 							   &buffer, &hufd);
-		ReleaseBuffer(buffer);
+		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,
+			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 f7051b3..a5f469a 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/executor.h"
 #include "executor/nodeModifyTable.h"
@@ -164,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;
 
@@ -191,7 +192,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
 static void
 ExecCheckHeapTupleVisible(EState *estate,
 						  Relation rel,
-						  HeapTuple tuple,
+						  StorageTuple tuple,
 						  Buffer buffer)
 {
 	if (!IsolationUsesXactSnapshot())
@@ -204,13 +205,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")));
@@ -226,19 +229,20 @@ ExecCheckTIDVisible(EState *estate,
 					ResultRelInfo *relinfo,
 					ItemPointer tid)
 {
+	Buffer buffer;
 	Relation	rel = relinfo->ri_RelationDesc;
-	Buffer		buffer;
-	HeapTupleData tuple;
+	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);
 }
 
 /* ----------------------------------------------------------------
@@ -259,7 +263,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,
 	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
 	 */
 	resultRelInfo = estate->es_result_relation_info;
@@ -284,6 +282,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.
@@ -371,19 +371,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.
@@ -401,9 +413,6 @@ ExecInsert(ModifyTableState *mtstate,
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
-
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -415,9 +424,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)
@@ -433,14 +439,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;
 	}
@@ -460,7 +464,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
@@ -551,24 +556,24 @@ 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,
+			newId = storage_insert(resultRelationDesc, slot,
 								estate->es_output_cid,
 								HEAP_INSERT_SPECULATIVE,
 								NULL);
 
 			/* insert index entries for tuple */
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+			recheckIndexes = ExecInsertIndexTuples(slot, &(slot->tts_tid),
 												   estate, true, &specConflict,
 												   arbiterIndexes);
 
 			/* adjust the tuple's state accordingly */
 			if (!specConflict)
-				heap_finish_speculative(resultRelationDesc, tuple);
+				storage_finish_speculative(resultRelationDesc, slot);
 			else
-				heap_abort_speculative(resultRelationDesc, tuple);
+				storage_abort_speculative(resultRelationDesc, slot);
 
 			/*
 			 * Wake up anyone waiting for our decision.  They will re-check
@@ -596,17 +601,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,
+			newId = storage_insert(resultRelationDesc, slot,
 								estate->es_output_cid,
 								0, NULL);
 
 			/* insert index entries for tuple */
 			if (resultRelInfo->ri_NumIndices > 0)
-				recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+				recheckIndexes = ExecInsertIndexTuples(slot, &(slot->tts_tid),
 													   estate, false, NULL,
 													   arbiterIndexes);
 		}
@@ -616,11 +618,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);
@@ -671,7 +673,7 @@ ExecInsert(ModifyTableState *mtstate,
 static TupleTableSlot *
 ExecDelete(ModifyTableState *mtstate,
 		   ItemPointer tupleid,
-		   HeapTuple oldtuple,
+		   StorageTuple oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate,
@@ -716,8 +718,6 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
-		HeapTuple	tuple;
-
 		/*
 		 * delete from foreign table: let the FDW do it
 		 *
@@ -743,8 +743,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
 	{
@@ -758,7 +760,7 @@ ExecDelete(ModifyTableState *mtstate,
 		 * mode transactions.
 		 */
 ldelete:;
-		result = heap_delete(resultRelationDesc, tupleid,
+		result = storage_delete(resultRelationDesc, tupleid,
 							 estate->es_output_cid,
 							 estate->es_crosscheck_snapshot,
 							 true /* wait for commit */ ,
@@ -858,7 +860,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)
@@ -872,20 +874,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);
@@ -894,7 +895,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))
@@ -931,14 +932,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;
@@ -1003,14 +1004,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
 	{
@@ -1020,7 +1021,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
@@ -1056,7 +1057,7 @@ lreplace:;
 		 * needed for referential integrity updates in transaction-snapshot
 		 * mode transactions.
 		 */
-		result = heap_update(resultRelationDesc, tupleid, tuple,
+		result = storage_update(resultRelationDesc, tupleid, slot,
 							 estate->es_output_cid,
 							 estate->es_crosscheck_snapshot,
 							 true /* wait for commit */ ,
@@ -1148,8 +1149,8 @@ lreplace:;
 		 *
 		 * 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),
+		if ((resultRelInfo->ri_NumIndices > 0) && !storage_tuple_is_heaponly(resultRelationDesc, tuple))
+			recheckIndexes = ExecInsertIndexTuples(slot, &(slot->tts_tid),
 												   estate, false, NULL, NIL);
 	}
 
@@ -1206,11 +1207,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);
@@ -1221,10 +1223,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:
@@ -1249,7 +1249,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"),
@@ -1280,7 +1281,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:
@@ -1308,10 +1311,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
@@ -1326,7 +1329,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 */
 	}
@@ -1366,12 +1371,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;
 }
 
@@ -1552,7 +1559,7 @@ ExecModifyTable(PlanState *pstate)
 	ItemPointer tupleid = NULL;
 	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 0ee76e7..8a6b217 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;
@@ -332,12 +333,6 @@ TidNext(TidScanState *node)
 	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.
 	 */
 	bBackward = ScanDirectionIsBackward(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 083f7d6..52779b7 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"
@@ -354,7 +355,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);
@@ -389,7 +390,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 b2132e7..b40d2d5 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -132,40 +132,19 @@ extern void heap_parallelscan_initialize(ParallelHeapScanDesc target,
 							 Relation relation, Snapshot snapshot);
 extern HeapScanDesc heap_beginscan_parallel(Relation, ParallelHeapScanDesc);
 
-extern bool heap_fetch(Relation relation, Snapshot snapshot,
-		   HeapTuple tuple, Buffer *userbuf, bool keep_buf,
-		   Relation stats_relation);
+extern Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid,
+			int options, BulkInsertState bistate);
 extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
 					   Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
 					   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);
 
 extern BulkInsertState GetBulkInsertState(void);
 extern void FreeBulkInsertState(BulkInsertState);
 extern void ReleaseBulkInsertStatePin(BulkInsertState bistate);
 
-extern Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid,
-			int options, BulkInsertState bistate);
-extern void heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
-				  CommandId cid, int options, BulkInsertState bistate);
-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 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,
-				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);
@@ -178,7 +157,6 @@ extern void simple_heap_delete(Relation relation, ItemPointer tid);
 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);
 
 /* in heap/pruneheap.c */
diff --git a/src/include/access/heapam_common.h b/src/include/access/heapam_common.h
index 1fe15ed..799b4ed 100644
--- a/src/include/access/heapam_common.h
+++ b/src/include/access/heapam_common.h
@@ -34,6 +34,111 @@
 #include "utils/relcache.h"
 #include "utils/snapshot.h"
 
+/*
+ * Each tuple lock mode has a corresponding heavyweight lock, and one or two
+ * corresponding MultiXactStatuses (one to merely lock tuples, another one to
+ * update them).  This table (and the macros below) helps us determine the
+ * heavyweight lock mode and MultiXactStatus values to use for any particular
+ * tuple lock strength.
+ *
+ * Don't look at lockstatus/updstatus directly!  Use get_mxact_status_for_lock
+ * instead.
+ */
+static const struct
+{
+	LOCKMODE	hwlock;
+	int			lockstatus;
+	int			updstatus;
+}
+
+			tupleLockExtraInfo[MaxLockTupleMode + 1] =
+{
+	{							/* LockTupleKeyShare */
+		AccessShareLock,
+		MultiXactStatusForKeyShare,
+		-1						/* KeyShare does not allow updating tuples */
+	},
+	{							/* LockTupleShare */
+		RowShareLock,
+		MultiXactStatusForShare,
+		-1						/* Share does not allow updating tuples */
+	},
+	{							/* LockTupleNoKeyExclusive */
+		ExclusiveLock,
+		MultiXactStatusForNoKeyUpdate,
+		MultiXactStatusNoKeyUpdate
+	},
+	{							/* LockTupleExclusive */
+		AccessExclusiveLock,
+		MultiXactStatusForUpdate,
+		MultiXactStatusUpdate
+	}
+};
+
+/*
+ * This table maps tuple lock strength values for each particular
+ * MultiXactStatus value.
+ */
+static const int MultiXactStatusLock[MaxMultiXactStatus + 1] =
+{
+	LockTupleKeyShare,			/* ForKeyShare */
+	LockTupleShare,				/* ForShare */
+	LockTupleNoKeyExclusive,	/* ForNoKeyUpdate */
+	LockTupleExclusive,			/* ForUpdate */
+	LockTupleNoKeyExclusive,	/* NoKeyUpdate */
+	LockTupleExclusive			/* Update */
+};
+
+/* Get the LockTupleMode for a given MultiXactStatus */
+#define TUPLOCK_from_mxstatus(status) \
+			(MultiXactStatusLock[(status)])
+
+/*
+ * Acquire heavyweight locks on tuples, using a LockTupleMode strength value.
+ * This is more readable than having every caller translate it to lock.h's
+ * LOCKMODE.
+ */
+#define LockTupleTuplock(rel, tup, mode) \
+	LockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
+#define UnlockTupleTuplock(rel, tup, mode) \
+	UnlockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
+#define ConditionalLockTupleTuplock(rel, tup, mode) \
+	ConditionalLockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
+/* Get the LOCKMODE for a given MultiXactStatus */
+#define LOCKMODE_from_mxstatus(status) \
+			(tupleLockExtraInfo[TUPLOCK_from_mxstatus((status))].hwlock)
+extern HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
+					TransactionId xid, CommandId cid, int options);
+
+extern HTSU_Result heap_delete(Relation relation, ItemPointer tid,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			HeapUpdateFailureData *hufd);
+extern HTSU_Result heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			HeapUpdateFailureData *hufd, LockTupleMode *lockmode);
+
+extern XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
+extern uint8 compute_infobits(uint16 infomask, uint16 infomask2);
+extern void compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
+						  uint16 old_infomask2, TransactionId add_to_xmax,
+						  LockTupleMode mode, bool is_update,
+						  TransactionId *result_xmax, uint16 *result_infomask,
+						  uint16 *result_infomask2);
+extern void UpdateXmaxHintBits(HeapTupleHeader tuple, Buffer buffer, TransactionId xid);
+extern bool DoesMultiXactIdConflict(MultiXactId multi, uint16 infomask,
+						LockTupleMode lockmode);
+extern bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
+						   uint16 infomask, Relation rel, int *remaining);
+
+extern void MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
+				Relation rel, ItemPointer ctid, XLTW_Oper oper,
+				int *remaining);
+extern MultiXactStatus get_mxact_status_for_lock(LockTupleMode mode, bool is_update);
+
+extern void heap_inplace_update(Relation relation, HeapTuple tuple);
+extern bool heap_acquire_tuplock(Relation relation, ItemPointer tid,
+					 LockTupleMode mode, LockWaitPolicy wait_policy,
+					 bool *have_tuple_lock);
 
 /* in heap/heapam_common.c */
 extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
@@ -43,6 +148,28 @@ extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin);
 typedef struct StorageSlotAmRoutine StorageSlotAmRoutine;
 extern StorageSlotAmRoutine* heapam_storage_slot_handler(void);
 
+
+/*
+ * Given two versions of the same t_infomask for a tuple, compare them and
+ * return whether the relevant status for a tuple Xmax has changed.  This is
+ * used after a buffer lock has been released and reacquired: we want to ensure
+ * that the tuple state continues to be the same it was when we previously
+ * examined it.
+ *
+ * Note the Xmax field itself must be compared separately.
+ */
+static inline bool
+xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask)
+{
+	const uint16 interesting =
+	HEAP_XMAX_IS_MULTI | HEAP_XMAX_LOCK_ONLY | HEAP_LOCK_MASK;
+
+	if ((new_infomask & interesting) != (old_infomask & interesting))
+		return true;
+
+	return false;
+}
+
 /*
  * SetHintBits()
  *
diff --git a/src/include/access/storageam.h b/src/include/access/storageam.h
new file mode 100644
index 0000000..9502c92
--- /dev/null
+++ b/src/include/access/storageam.h
@@ -0,0 +1,81 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "executor/tuptable.h"
+
+/* A physical tuple coming from a storage AM scan */
+typedef void *StorageTuple;
+
+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;
+
+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);
+
+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,
+				   CommandId cid, Snapshot crosscheck, bool wait,
+				   HeapUpdateFailureData *hufd, LockTupleMode *lockmode);
+
+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 void storage_finish_speculative(Relation relation, TupleTableSlot *slot);
+
+extern tuple_data storage_tuple_get_data(Relation relation, StorageTuple tuple, tuple_data_flags flags);
+
+extern bool storage_tuple_is_heaponly(Relation relation, StorageTuple tuple);
+
+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 95fe028..c2e6dc2 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -13,32 +13,13 @@
 
 #include "access/htup.h"
 #include "access/heapam.h"
+#include "access/storageam.h"
 #include "access/sdir.h"
 #include "access/skey.h"
 #include "executor/tuptable.h"
 #include "utils/relcache.h"
 #include "utils/snapshot.h"
 
-/* A physical tuple coming from a storage AM scan */
-typedef void *StorageTuple;
-
-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;
-
-
 typedef HeapScanDesc (*scan_begin_hook) (Relation relation,
 										Snapshot snapshot,
 										int nkeys, ScanKey key,
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index aeb363f..0da9ac9 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -189,7 +189,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 f48a603..b2913f2 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -196,7 +196,7 @@ 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,
+extern StorageTuple EvalPlanQualFetch(EState *estate, Relation relation,
 				  int lockmode, LockWaitPolicy wait_policy, ItemPointer tid,
 				  TransactionId priorXmax);
 extern void EvalPlanQualInit(EPQState *epqstate, EState *estate,
@@ -204,8 +204,8 @@ extern void EvalPlanQualInit(EPQState *epqstate, EState *estate,
 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);
 extern void ExecSetupPartitionTupleRouting(Relation rel,
 							   Index resultRTindex,
 							   EState *estate,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index d1565e7..d69414e 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -503,7 +503,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 */
 
@@ -2016,7 +2016,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;
 
-- 
1.8.3.1

#76Thomas Munro
thomas.munro@enterprisedb.com
In reply to: Haribabu Kommi (#75)
Re: Pluggable storage

On Fri, Sep 1, 2017 at 1:51 PM, Haribabu Kommi <kommi.haribabu@gmail.com> wrote:

Here I attached new set of patches that are rebased to the latest master.

Hi Haribabu,

Could you please post a new rebased version?
0007-Scan-functions-are-added-to-storage-AM.patch conflicts with
recent changes.

Thanks!

--
Thomas Munro
http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#77Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Thomas Munro (#76)
7 attachment(s)
Re: Pluggable storage

On Thu, Sep 7, 2017 at 11:53 AM, Thomas Munro <thomas.munro@enterprisedb.com

wrote:

On Fri, Sep 1, 2017 at 1:51 PM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:

Here I attached new set of patches that are rebased to the latest master.

Hi Haribabu,

Could you please post a new rebased version?
0007-Scan-functions-are-added-to-storage-AM.patch conflicts with
recent changes.

Thanks for checking the patch.

I rebased the patch to the latest master and also fixed the duplicate OID
and some slot fixes. Updated patches are attached.

Regards,
Hari Babu
Fujitsu Australia

Attachments:

0007-Scan-functions-are-added-to-storage-AM.patchapplication/octet-stream; name=0007-Scan-functions-are-added-to-storage-AM.patchDownload
From 9da4ba367a4eb8e7f0722ff7c647c1a8f491b34c Mon Sep 17 00:00:00 2001
From: Hari Babu Kommi <kommi.haribabu@gmail.com>
Date: Fri, 8 Sep 2017 15:45:04 +1000
Subject: [PATCH 7/7] 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           | 1504 ++--------------------------
 src/backend/access/heap/heapam_storage.c   | 1299 ++++++++++++++++++++++++
 src/backend/access/heap/rewriteheap.c      |    2 +-
 src/backend/access/heap/storageam.c        |  235 +++++
 src/backend/access/index/genam.c           |    7 +-
 src/backend/access/index/indexam.c         |    3 +-
 src/backend/access/nbtree/nbtinsert.c      |    5 +-
 src/backend/bootstrap/bootstrap.c          |   25 +-
 src/backend/catalog/aclchk.c               |   13 +-
 src/backend/catalog/index.c                |   27 +-
 src/backend/catalog/partition.c            |    6 +-
 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/trigger.c             |    3 +-
 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     |   16 +-
 src/backend/executor/execTuples.c          |    8 +-
 src/backend/executor/functions.c           |    4 +-
 src/backend/executor/nodeAgg.c             |    4 +-
 src/backend/executor/nodeBitmapHeapscan.c  |   11 +-
 src/backend/executor/nodeForeignscan.c     |    6 +-
 src/backend/executor/nodeGather.c          |    8 +-
 src/backend/executor/nodeGatherMerge.c     |   12 +-
 src/backend/executor/nodeIndexonlyscan.c   |    4 +-
 src/backend/executor/nodeIndexscan.c       |   16 +-
 src/backend/executor/nodeSamplescan.c      |   21 +-
 src/backend/executor/nodeSeqscan.c         |   39 +-
 src/backend/executor/nodeWindowAgg.c       |    4 +-
 src/backend/executor/spi.c                 |   20 +-
 src/backend/executor/tqueue.c              |   16 +-
 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                |   30 +-
 src/include/access/heapam_common.h         |    8 +
 src/include/access/storageam.h             |   42 +-
 src/include/executor/functions.h           |    2 +-
 src/include/executor/spi.h                 |   10 +-
 src/include/executor/tqueue.h              |    2 +-
 src/include/funcapi.h                      |    2 +-
 56 files changed, 1924 insertions(+), 1734 deletions(-)

diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index 5f076ef..063e079 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 f7b68a8..eb33b26 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 d20f211..b64fec8 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -81,19 +81,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 XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 				Buffer newbuf, HeapTuple oldtup,
 				HeapTuple newtup, HeapTuple old_key_tup,
@@ -112,139 +99,6 @@ static bool Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
 static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_modified,
 					   bool *copy);
 
-/* ----------------------------------------------------------------
- *						 heap support routines
- * ----------------------------------------------------------------
- */
-
-/* ----------------
- *		initscan - scan code common to heap_beginscan and heap_rescan
- * ----------------
- */
-static void
-initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
-{
-	bool		allow_strat;
-	bool		allow_sync;
-
-	/*
-	 * Determine the number of blocks we have to scan.
-	 *
-	 * It is sufficient to do this once at scan start, since any tuples added
-	 * while the scan is in progress will be invisible to my snapshot anyway.
-	 * (That is not true when using a non-MVCC snapshot.  However, we couldn't
-	 * guarantee to return tuples added after scan start anyway, since they
-	 * might go into pages we already scanned.  To guarantee consistent
-	 * results for a non-MVCC snapshot, the caller must hold some higher-level
-	 * lock that ensures the interesting tuple(s) won't change.)
-	 */
-	if (scan->rs_parallel != NULL)
-		scan->rs_nblocks = scan->rs_parallel->phs_nblocks;
-	else
-		scan->rs_nblocks = RelationGetNumberOfBlocks(scan->rs_rd);
-
-	/*
-	 * If the table is large relative to NBuffers, use a bulk-read access
-	 * strategy and enable synchronized scanning (see syncscan.c).  Although
-	 * the thresholds for these features could be different, we make them the
-	 * same so that there are only two behaviors to tune rather than four.
-	 * (However, some callers need to be able to disable one or both of these
-	 * behaviors, independently of the size of the table; also there is a GUC
-	 * variable that can disable synchronized scanning.)
-	 *
-	 * 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)
-	{
-		allow_strat = scan->rs_allow_strat;
-		allow_sync = scan->rs_allow_sync;
-	}
-	else
-		allow_strat = allow_sync = false;
-
-	if (allow_strat)
-	{
-		/* During a rescan, keep the previous strategy object. */
-		if (scan->rs_strategy == NULL)
-			scan->rs_strategy = GetAccessStrategy(BAS_BULKREAD);
-	}
-	else
-	{
-		if (scan->rs_strategy != NULL)
-			FreeAccessStrategy(scan->rs_strategy);
-		scan->rs_strategy = NULL;
-	}
-
-	if (scan->rs_parallel != NULL)
-	{
-		/* For parallel scan, believe whatever ParallelHeapScanDesc says. */
-		scan->rs_syncscan = scan->rs_parallel->phs_syncscan;
-	}
-	else if (keep_startblock)
-	{
-		/*
-		 * When rescanning, we want to keep the previous startblock setting,
-		 * so that rewinding a cursor doesn't generate surprising results.
-		 * Reset the active syncscan setting, though.
-		 */
-		scan->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);
-	}
-	else
-	{
-		scan->rs_syncscan = false;
-		scan->rs_startblock = 0;
-	}
-
-	scan->rs_numblocks = InvalidBlockNumber;
-	scan->rs_inited = false;
-	scan->rs_ctup.t_data = NULL;
-	ItemPointerSetInvalid(&scan->rs_ctup.t_self);
-	scan->rs_cbuf = InvalidBuffer;
-	scan->rs_cblock = InvalidBlockNumber;
-
-	/* page-at-a-time fields are always invalid when not rs_inited */
-
-	/*
-	 * copy the scan key, if appropriate
-	 */
-	if (key != NULL)
-		memcpy(scan->rs_key, key, scan->rs_nkeys * sizeof(ScanKeyData));
-
-	/*
-	 * Currently, we don't have a stats counter for bitmap heap scans (but the
-	 * underlying bitmap index scans will be counted) or sample scans (we only
-	 * update stats for tuple fetches there)
-	 */
-	if (!scan->rs_bitmapscan && !scan->rs_samplescan)
-		pgstat_count_heap_scan(scan->rs_rd);
-}
-
-/*
- * 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
-heap_setscanlimits(HeapScanDesc scan, BlockNumber startBlk, BlockNumber numBlks)
-{
-	Assert(!scan->rs_inited);	/* else too late to change */
-	Assert(!scan->rs_syncscan); /* else rs_startblock is significant */
-
-	/* Check startBlk is valid (but allow case of zero blocks...) */
-	Assert(startBlk == 0 || startBlk < scan->rs_nblocks);
-
-	scan->rs_startblock = startBlk;
-	scan->rs_numblocks = numBlks;
-}
-
 /*
  * heapgetpage - subroutine for heapgettup()
  *
@@ -363,603 +217,6 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 	scan->rs_ntuples = ntup;
 }
 
-/* ----------------
- *		heapgettup - fetch next heap tuple
- *
- *		Initialize the scan if not already done; then advance to the next
- *		tuple as indicated by "dir"; return the next tuple in scan->rs_ctup,
- *		or set scan->rs_ctup.t_data = NULL if no more tuples.
- *
- * dir == NoMovementScanDirection means "re-fetch the tuple indicated
- * by scan->rs_ctup".
- *
- * Note: the reason nkeys/key are passed separately, even though they are
- * kept in the scan descriptor, is that the caller may not want us to check
- * the scankeys.
- *
- * Note: when we fall off the end of the scan in either direction, we
- * reset rs_inited.  This means that a further request with the same
- * scan direction will restart the scan, which is a bit odd, but a
- * request with the opposite scan direction will start a fresh scan
- * in the proper direction.  The latter is required behavior for cursors,
- * while the former case is generally undefined behavior in Postgres
- * so we don't care too much.
- * ----------------
- */
-static void
-heapgettup(HeapScanDesc scan,
-		   ScanDirection dir,
-		   int nkeys,
-		   ScanKey key)
-{
-	HeapTuple	tuple = &(scan->rs_ctup);
-	Snapshot	snapshot = scan->rs_snapshot;
-	bool		backward = ScanDirectionIsBackward(dir);
-	BlockNumber page;
-	bool		finished;
-	Page		dp;
-	int			lines;
-	OffsetNumber lineoff;
-	int			linesleft;
-	ItemId		lpp;
-
-	/*
-	 * calculate next starting lineoff, given scan direction
-	 */
-	if (ScanDirectionIsForward(dir))
-	{
-		if (!scan->rs_inited)
-		{
-			/*
-			 * return null immediately if relation is empty
-			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
-			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
-				tuple->t_data = NULL;
-				return;
-			}
-			if (scan->rs_parallel != NULL)
-			{
-				heap_parallelscan_startblock_init(scan);
-
-				page = heap_parallelscan_nextpage(scan);
-
-				/* Other processes might have already finished the scan. */
-				if (page == InvalidBlockNumber)
-				{
-					Assert(!BufferIsValid(scan->rs_cbuf));
-					tuple->t_data = NULL;
-					return;
-				}
-			}
-			else
-				page = scan->rs_startblock; /* first page */
-			heapgetpage(scan, page);
-			lineoff = FirstOffsetNumber;	/* first offnum */
-			scan->rs_inited = true;
-		}
-		else
-		{
-			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-			lineoff =			/* next offnum */
-				OffsetNumberNext(ItemPointerGetOffsetNumber(&(tuple->t_self)));
-		}
-
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
-		lines = PageGetMaxOffsetNumber(dp);
-		/* page and lineoff now reference the physically next tid */
-
-		linesleft = lines - lineoff + 1;
-	}
-	else if (backward)
-	{
-		/* backward parallel scan not supported */
-		Assert(scan->rs_parallel == NULL);
-
-		if (!scan->rs_inited)
-		{
-			/*
-			 * return null immediately if relation is empty
-			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
-			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
-				tuple->t_data = NULL;
-				return;
-			}
-
-			/*
-			 * Disable reporting to syncscan logic in a backwards scan; it's
-			 * not very likely anyone else is doing the same thing at the same
-			 * time, and much more likely that we'll just bollix things for
-			 * forward scanners.
-			 */
-			scan->rs_syncscan = false;
-			/* start from last page of the scan */
-			if (scan->rs_startblock > 0)
-				page = scan->rs_startblock - 1;
-			else
-				page = scan->rs_nblocks - 1;
-			heapgetpage(scan, page);
-		}
-		else
-		{
-			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-		}
-
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
-		lines = PageGetMaxOffsetNumber(dp);
-
-		if (!scan->rs_inited)
-		{
-			lineoff = lines;	/* final offnum */
-			scan->rs_inited = true;
-		}
-		else
-		{
-			lineoff =			/* previous offnum */
-				OffsetNumberPrev(ItemPointerGetOffsetNumber(&(tuple->t_self)));
-		}
-		/* page and lineoff now reference the physically previous tid */
-
-		linesleft = lineoff;
-	}
-	else
-	{
-		/*
-		 * ``no movement'' scan direction: refetch prior tuple
-		 */
-		if (!scan->rs_inited)
-		{
-			Assert(!BufferIsValid(scan->rs_cbuf));
-			tuple->t_data = NULL;
-			return;
-		}
-
-		page = ItemPointerGetBlockNumber(&(tuple->t_self));
-		if (page != scan->rs_cblock)
-			heapgetpage(scan, page);
-
-		/* Since the tuple was previously fetched, needn't lock page here */
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
-		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
-		lpp = PageGetItemId(dp, lineoff);
-		Assert(ItemIdIsNormal(lpp));
-
-		tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
-		tuple->t_len = ItemIdGetLength(lpp);
-
-		return;
-	}
-
-	/*
-	 * advance the scan until we find a qualifying tuple or run out of stuff
-	 * to scan
-	 */
-	lpp = PageGetItemId(dp, lineoff);
-	for (;;)
-	{
-		while (linesleft > 0)
-		{
-			if (ItemIdIsNormal(lpp))
-			{
-				bool		valid;
-
-				tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
-				tuple->t_len = ItemIdGetLength(lpp);
-				ItemPointerSet(&(tuple->t_self), page, lineoff);
-
-				/*
-				 * if current tuple qualifies, return it.
-				 */
-				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine,
-													 tuple,
-													 snapshot,
-													 scan->rs_cbuf);
-
-				CheckForSerializableConflictOut(valid, scan->rs_rd, tuple,
-												scan->rs_cbuf, snapshot);
-
-				if (valid && key != NULL)
-					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
-								nkeys, key, valid);
-
-				if (valid)
-				{
-					LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
-					return;
-				}
-			}
-
-			/*
-			 * otherwise move to the next item on the page
-			 */
-			--linesleft;
-			if (backward)
-			{
-				--lpp;			/* move back in this page's ItemId array */
-				--lineoff;
-			}
-			else
-			{
-				++lpp;			/* move forward in this page's ItemId array */
-				++lineoff;
-			}
-		}
-
-		/*
-		 * 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);
-
-		/*
-		 * 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);
-			if (page == 0)
-				page = scan->rs_nblocks;
-			page--;
-		}
-		else if (scan->rs_parallel != NULL)
-		{
-			page = heap_parallelscan_nextpage(scan);
-			finished = (page == InvalidBlockNumber);
-		}
-		else
-		{
-			page++;
-			if (page >= scan->rs_nblocks)
-				page = 0;
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
-
-			/*
-			 * Report our new scan position for synchronization purposes. We
-			 * don't do that when moving backwards, however. That would just
-			 * mess up any other forward-moving scanners.
-			 *
-			 * Note: we do this before checking for end of scan so that the
-			 * final state of the position hint is back at the start of the
-			 * rel.  That's not strictly necessary, but otherwise when you run
-			 * the same query multiple times the starting position would shift
-			 * 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);
-		}
-
-		/*
-		 * return NULL if we've exhausted all the pages
-		 */
-		if (finished)
-		{
-			if (BufferIsValid(scan->rs_cbuf))
-				ReleaseBuffer(scan->rs_cbuf);
-			scan->rs_cbuf = InvalidBuffer;
-			scan->rs_cblock = InvalidBlockNumber;
-			tuple->t_data = NULL;
-			scan->rs_inited = false;
-			return;
-		}
-
-		heapgetpage(scan, page);
-
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
-		lines = PageGetMaxOffsetNumber((Page) dp);
-		linesleft = lines;
-		if (backward)
-		{
-			lineoff = lines;
-			lpp = PageGetItemId(dp, lines);
-		}
-		else
-		{
-			lineoff = FirstOffsetNumber;
-			lpp = PageGetItemId(dp, FirstOffsetNumber);
-		}
-	}
-}
-
-/* ----------------
- *		heapgettup_pagemode - fetch next heap tuple in page-at-a-time mode
- *
- *		Same API as heapgettup, but used in page-at-a-time mode
- *
- * The internal logic is much the same as heapgettup's too, but there are some
- * differences: we do not take the buffer content lock (that only needs to
- * happen inside heapgetpage), and we iterate through just the tuples listed
- * in rs_vistuples[] rather than all tuples on the page.  Notice that
- * lineindex is 0-based, where the corresponding loop variable lineoff in
- * heapgettup is 1-based.
- * ----------------
- */
-static void
-heapgettup_pagemode(HeapScanDesc scan,
-					ScanDirection dir,
-					int nkeys,
-					ScanKey key)
-{
-	HeapTuple	tuple = &(scan->rs_ctup);
-	bool		backward = ScanDirectionIsBackward(dir);
-	BlockNumber page;
-	bool		finished;
-	Page		dp;
-	int			lines;
-	int			lineindex;
-	OffsetNumber lineoff;
-	int			linesleft;
-	ItemId		lpp;
-
-	/*
-	 * calculate next starting lineindex, given scan direction
-	 */
-	if (ScanDirectionIsForward(dir))
-	{
-		if (!scan->rs_inited)
-		{
-			/*
-			 * return null immediately if relation is empty
-			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
-			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
-				tuple->t_data = NULL;
-				return;
-			}
-			if (scan->rs_parallel != NULL)
-			{
-				heap_parallelscan_startblock_init(scan);
-
-				page = heap_parallelscan_nextpage(scan);
-
-				/* Other processes might have already finished the scan. */
-				if (page == InvalidBlockNumber)
-				{
-					Assert(!BufferIsValid(scan->rs_cbuf));
-					tuple->t_data = NULL;
-					return;
-				}
-			}
-			else
-				page = scan->rs_startblock; /* first page */
-			heapgetpage(scan, page);
-			lineindex = 0;
-			scan->rs_inited = true;
-		}
-		else
-		{
-			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-			lineindex = scan->rs_cindex + 1;
-		}
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
-		/* page and lineindex now reference the next visible tid */
-
-		linesleft = lines - lineindex;
-	}
-	else if (backward)
-	{
-		/* backward parallel scan not supported */
-		Assert(scan->rs_parallel == NULL);
-
-		if (!scan->rs_inited)
-		{
-			/*
-			 * return null immediately if relation is empty
-			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
-			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
-				tuple->t_data = NULL;
-				return;
-			}
-
-			/*
-			 * Disable reporting to syncscan logic in a backwards scan; it's
-			 * not very likely anyone else is doing the same thing at the same
-			 * time, and much more likely that we'll just bollix things for
-			 * forward scanners.
-			 */
-			scan->rs_syncscan = false;
-			/* start from last page of the scan */
-			if (scan->rs_startblock > 0)
-				page = scan->rs_startblock - 1;
-			else
-				page = scan->rs_nblocks - 1;
-			heapgetpage(scan, page);
-		}
-		else
-		{
-			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-		}
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
-
-		if (!scan->rs_inited)
-		{
-			lineindex = lines - 1;
-			scan->rs_inited = true;
-		}
-		else
-		{
-			lineindex = scan->rs_cindex - 1;
-		}
-		/* page and lineindex now reference the previous visible tid */
-
-		linesleft = lineindex + 1;
-	}
-	else
-	{
-		/*
-		 * ``no movement'' scan direction: refetch prior tuple
-		 */
-		if (!scan->rs_inited)
-		{
-			Assert(!BufferIsValid(scan->rs_cbuf));
-			tuple->t_data = NULL;
-			return;
-		}
-
-		page = ItemPointerGetBlockNumber(&(tuple->t_self));
-		if (page != scan->rs_cblock)
-			heapgetpage(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);
-		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
-		lpp = PageGetItemId(dp, lineoff);
-		Assert(ItemIdIsNormal(lpp));
-
-		tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
-		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]);
-
-		return;
-	}
-
-	/*
-	 * advance the scan until we find a qualifying tuple or run out of stuff
-	 * to scan
-	 */
-	for (;;)
-	{
-		while (linesleft > 0)
-		{
-			lineoff = scan->rs_vistuples[lineindex];
-			lpp = PageGetItemId(dp, lineoff);
-			Assert(ItemIdIsNormal(lpp));
-
-			tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
-			tuple->t_len = ItemIdGetLength(lpp);
-			ItemPointerSet(&(tuple->t_self), page, lineoff);
-
-			/*
-			 * if current tuple qualifies, return it.
-			 */
-			if (key != NULL)
-			{
-				bool		valid;
-
-				HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
-							nkeys, key, valid);
-				if (valid)
-				{
-					scan->rs_cindex = lineindex;
-					return;
-				}
-			}
-			else
-			{
-				scan->rs_cindex = lineindex;
-				return;
-			}
-
-			/*
-			 * otherwise move to the next item on the page
-			 */
-			--linesleft;
-			if (backward)
-				--lineindex;
-			else
-				++lineindex;
-		}
-
-		/*
-		 * if we get here, it means we've exhausted the items on this page and
-		 * it's time to move to the next.
-		 */
-		if (backward)
-		{
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
-			if (page == 0)
-				page = scan->rs_nblocks;
-			page--;
-		}
-		else if (scan->rs_parallel != NULL)
-		{
-			page = heap_parallelscan_nextpage(scan);
-			finished = (page == InvalidBlockNumber);
-		}
-		else
-		{
-			page++;
-			if (page >= scan->rs_nblocks)
-				page = 0;
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
-
-			/*
-			 * Report our new scan position for synchronization purposes. We
-			 * don't do that when moving backwards, however. That would just
-			 * mess up any other forward-moving scanners.
-			 *
-			 * Note: we do this before checking for end of scan so that the
-			 * final state of the position hint is back at the start of the
-			 * rel.  That's not strictly necessary, but otherwise when you run
-			 * the same query multiple times the starting position would shift
-			 * 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);
-		}
-
-		/*
-		 * return NULL if we've exhausted all the pages
-		 */
-		if (finished)
-		{
-			if (BufferIsValid(scan->rs_cbuf))
-				ReleaseBuffer(scan->rs_cbuf);
-			scan->rs_cbuf = InvalidBuffer;
-			scan->rs_cblock = InvalidBlockNumber;
-			tuple->t_data = NULL;
-			scan->rs_inited = false;
-			return;
-		}
-
-		heapgetpage(scan, page);
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
-		linesleft = lines;
-		if (backward)
-			lineindex = lines - 1;
-		else
-			lineindex = 0;
-	}
-}
-
 
 #if defined(DISABLE_COMPLEX_MACRO)
 /*
@@ -1186,317 +443,96 @@ relation_close(Relation relation, LOCKMODE lockmode)
 		UnlockRelationId(&relid, lockmode);
 }
 
-
-/* ----------------
- *		heap_open - open a heap relation by relation OID
- *
- *		This is essentially relation_open plus check that the relation
- *		is not an index nor a composite type.  (The caller should also
- *		check that it's not a view or foreign table before assuming it has
- *		storage.)
- * ----------------
- */
-Relation
-heap_open(Oid relationId, LOCKMODE lockmode)
-{
-	Relation	r;
-
-	r = relation_open(relationId, lockmode);
-
-	if (r->rd_rel->relkind == RELKIND_INDEX)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is an index",
-						RelationGetRelationName(r))));
-	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is a composite type",
-						RelationGetRelationName(r))));
-
-	return r;
-}
-
-/* ----------------
- *		heap_openrv - open a heap relation specified
- *		by a RangeVar node
- *
- *		As above, but relation is specified by a RangeVar.
- * ----------------
- */
-Relation
-heap_openrv(const RangeVar *relation, LOCKMODE lockmode)
-{
-	Relation	r;
-
-	r = relation_openrv(relation, lockmode);
-
-	if (r->rd_rel->relkind == RELKIND_INDEX)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is an index",
-						RelationGetRelationName(r))));
-	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is a composite type",
-						RelationGetRelationName(r))));
-
-	return r;
-}
-
-/* ----------------
- *		heap_openrv_extended - open a heap relation specified
- *		by a RangeVar node
- *
- *		As above, but optionally return NULL instead of failing for
- *		relation-not-found.
- * ----------------
- */
-Relation
-heap_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
-					 bool missing_ok)
-{
-	Relation	r;
-
-	r = relation_openrv_extended(relation, lockmode, missing_ok);
-
-	if (r)
-	{
-		if (r->rd_rel->relkind == RELKIND_INDEX)
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("\"%s\" is an index",
-							RelationGetRelationName(r))));
-		else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("\"%s\" is a composite type",
-							RelationGetRelationName(r))));
-	}
-
-	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)
-{
-	HeapScanDesc scan;
-
-	/*
-	 * increment relation ref count while scanning relation
-	 *
-	 * This is just to make really sure the relcache entry won't go away while
-	 * the scan has a pointer to it.  Caller should be holding the rel open
-	 * anyway, so this is redundant in all normal scenarios...
-	 */
-	RelationIncrementReferenceCount(relation);
-
-	/*
-	 * allocate and initialize scan descriptor
-	 */
-	scan = (HeapScanDesc) palloc(sizeof(HeapScanDescData));
-
-	scan->rs_rd = relation;
-	scan->rs_snapshot = snapshot;
-	scan->rs_nkeys = nkeys;
-	scan->rs_bitmapscan = is_bitmapscan;
-	scan->rs_samplescan = is_samplescan;
-	scan->rs_strategy = NULL;	/* set in initscan */
-	scan->rs_allow_strat = allow_strat;
-	scan->rs_allow_sync = allow_sync;
-	scan->rs_temp_snap = temp_snap;
-	scan->rs_parallel = parallel_scan;
-
-	/*
-	 * we can use page-at-a-time mode if it's an MVCC-safe snapshot
-	 */
-	scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(snapshot);
-
-	/*
-	 * For a seqscan in a serializable transaction, acquire a predicate lock
-	 * on the entire relation. This is required not only to lock all the
-	 * matching tuples, but also to conflict with new insertions into the
-	 * table. In an indexscan, we take page locks on the index pages covering
-	 * the range specified in the scan qual, but in a heap scan there is
-	 * nothing more fine-grained to lock. A bitmap scan is a different story,
-	 * there we have already scanned the index and locked the index pages
-	 * covering the predicate. But in that case we still have to lock any
-	 * matching heap tuples.
-	 */
-	if (!is_bitmapscan)
-		PredicateLockRelation(relation, snapshot);
-
-	/* we only need to set this up once */
-	scan->rs_ctup.t_tableOid = RelationGetRelid(relation);
-
-	/*
-	 * we do this here instead of in initscan() because heap_rescan also calls
-	 * initscan() and we don't want to allocate memory again
-	 */
-	if (nkeys > 0)
-		scan->rs_key = (ScanKey) palloc(sizeof(ScanKeyData) * nkeys);
-	else
-		scan->rs_key = NULL;
-
-	initscan(scan, key, false);
-
-	return scan;
-}
-
+
 /* ----------------
- *		heap_rescan		- restart a relation scan
+ *		heap_open - open a heap relation by relation OID
+ *
+ *		This is essentially relation_open plus check that the relation
+ *		is not an index nor a composite type.  (The caller should also
+ *		check that it's not a view or foreign table before assuming it has
+ *		storage.)
  * ----------------
  */
-void
-heap_rescan(HeapScanDesc scan,
-			ScanKey key)
+Relation
+heap_open(Oid relationId, LOCKMODE lockmode)
 {
-	/*
-	 * unpin scan buffers
-	 */
-	if (BufferIsValid(scan->rs_cbuf))
-		ReleaseBuffer(scan->rs_cbuf);
+	Relation	r;
 
-	/*
-	 * reinitialize scan descriptor
-	 */
-	initscan(scan, key, true);
+	r = relation_open(relationId, lockmode);
+
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is an index",
+						RelationGetRelationName(r))));
+	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is a composite type",
+						RelationGetRelationName(r))));
+
+	return r;
 }
 
 /* ----------------
- *		heap_rescan_set_params	- restart a relation scan after changing params
+ *		heap_openrv - open a heap relation specified
+ *		by a RangeVar node
  *
- * 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.
+ *		As above, but relation is specified by a RangeVar.
  * ----------------
  */
-void
-heap_rescan_set_params(HeapScanDesc scan, ScanKey key,
-					   bool allow_strat, bool allow_sync, bool allow_pagemode)
+Relation
+heap_openrv(const RangeVar *relation, LOCKMODE lockmode)
 {
-	/* 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);
+	Relation	r;
+
+	r = relation_openrv(relation, lockmode);
+
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is an index",
+						RelationGetRelationName(r))));
+	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is a composite type",
+						RelationGetRelationName(r))));
+
+	return r;
 }
 
 /* ----------------
- *		heap_endscan	- end relation scan
+ *		heap_openrv_extended - open a heap relation specified
+ *		by a RangeVar node
  *
- *		See how to integrate with index scans.
- *		Check handling if reldesc caching.
+ *		As above, but optionally return NULL instead of failing for
+ *		relation-not-found.
  * ----------------
  */
-void
-heap_endscan(HeapScanDesc scan)
+Relation
+heap_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
+					 bool missing_ok)
 {
-	/* Note: no locking manipulations needed */
-
-	/*
-	 * unpin scan buffers
-	 */
-	if (BufferIsValid(scan->rs_cbuf))
-		ReleaseBuffer(scan->rs_cbuf);
-
-	/*
-	 * decrement relation reference count and free scan descriptor storage
-	 */
-	RelationDecrementReferenceCount(scan->rs_rd);
-
-	if (scan->rs_key)
-		pfree(scan->rs_key);
+	Relation	r;
 
-	if (scan->rs_strategy != NULL)
-		FreeAccessStrategy(scan->rs_strategy);
+	r = relation_openrv_extended(relation, lockmode, missing_ok);
 
-	if (scan->rs_temp_snap)
-		UnregisterSnapshot(scan->rs_snapshot);
+	if (r)
+	{
+		if (r->rd_rel->relkind == RELKIND_INDEX)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("\"%s\" is an index",
+							RelationGetRelationName(r))));
+		else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("\"%s\" is a composite type",
+							RelationGetRelationName(r))));
+	}
 
-	pfree(scan);
+	return r;
 }
 
 /* ----------------
@@ -1550,384 +586,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
- *
- *		Determine where the parallel seq scan should start.  This function may
- *		be called many times, once by each parallel worker.  We must be careful
- *		only to set the startblock once.
- * ----------------
- */
-static void
-heap_parallelscan_startblock_init(HeapScanDesc scan)
-{
-	BlockNumber sync_startpage = InvalidBlockNumber;
-	ParallelHeapScanDesc parallel_scan;
-
-	Assert(scan->rs_parallel);
-	parallel_scan = scan->rs_parallel;
-
-retry:
-	/* Grab the spinlock. */
-	SpinLockAcquire(&parallel_scan->phs_mutex);
-
-	/*
-	 * If the scan's startblock has not yet been initialized, we must do so
-	 * now.  If this is not a synchronized scan, we just start at block 0, but
-	 * if it is a synchronized scan, we must get the starting position from
-	 * the synchronized scan machinery.  We can't hold the spinlock while
-	 * doing that, though, so release the spinlock, get the information we
-	 * need, and retry.  If nobody else has initialized the scan in the
-	 * meantime, we'll fill in the value we fetched on the second time
-	 * through.
-	 */
-	if (parallel_scan->phs_startblock == InvalidBlockNumber)
-	{
-		if (!parallel_scan->phs_syncscan)
-			parallel_scan->phs_startblock = 0;
-		else if (sync_startpage != InvalidBlockNumber)
-			parallel_scan->phs_startblock = sync_startpage;
-		else
-		{
-			SpinLockRelease(&parallel_scan->phs_mutex);
-			sync_startpage = ss_get_location(scan->rs_rd, scan->rs_nblocks);
-			goto retry;
-		}
-	}
-	SpinLockRelease(&parallel_scan->phs_mutex);
-}
-
-/* ----------------
- *		heap_parallelscan_nextpage - get the next page to scan
- *
- *		Get the next page to scan.  Even if there are no pages left to scan,
- *		another backend could have grabbed a page to scan and not yet finished
- *		looking at it, so it doesn't follow that the scan is done when the
- *		first backend gets an InvalidBlockNumber return.
- * ----------------
- */
-static BlockNumber
-heap_parallelscan_nextpage(HeapScanDesc scan)
-{
-	BlockNumber page;
-	ParallelHeapScanDesc parallel_scan;
-	uint64		nallocated;
-
-	Assert(scan->rs_parallel);
-	parallel_scan = scan->rs_parallel;
-
-	/*
-	 * phs_nallocated tracks how many pages have been allocated to workers
-	 * already.  When phs_nallocated >= rs_nblocks, all blocks have been
-	 * allocated.
-	 *
-	 * Because we use an atomic fetch-and-add to fetch the current value, the
-	 * phs_nallocated counter will exceed rs_nblocks, because workers will
-	 * still increment the value, when they try to allocate the next block but
-	 * all blocks have been allocated already. The counter must be 64 bits
-	 * wide because of that, to avoid wrapping around when rs_nblocks is close
-	 * to 2^32.
-	 *
-	 * The actual page to return is calculated by adding the counter to the
-	 * starting block number, modulo nblocks.
-	 */
-	nallocated = pg_atomic_fetch_add_u64(&parallel_scan->phs_nallocated, 1);
-	if (nallocated >= scan->rs_nblocks)
-		page = InvalidBlockNumber;	/* all blocks have been allocated */
-	else
-		page = (nallocated + parallel_scan->phs_startblock) % scan->rs_nblocks;
-
-	/*
-	 * Report scan location.  Normally, we report the current page number.
-	 * When we reach the end of the scan, though, we report the starting page,
-	 * not the ending page, just so the starting positions for later scans
-	 * 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 (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);
-	}
-
-	return page;
-}
-
-/* ----------------
- *		heap_update_snapshot
- *
- *		Update snapshot info in heap scan descriptor.
- * ----------------
- */
-void
-heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
-{
-	Assert(IsMVCCSnapshot(snapshot));
-
-	RegisterSnapshot(snapshot);
-	scan->rs_snapshot = snapshot;
-	scan->rs_temp_snap = true;
-}
-
-/* ----------------
- *		heap_getnext	- retrieve next tuple in scan
- *
- *		Fix to work with index relations.
- *		We don't return the buffer anymore, but you can get it from the
- *		returned HeapTuple.
- * ----------------
- */
-
-#ifdef HEAPDEBUGALL
-#define HEAPDEBUG_1 \
-	elog(DEBUG2, "heap_getnext([%s,nkeys=%d],dir=%d) called", \
-		 RelationGetRelationName(scan->rs_rd), scan->rs_nkeys, (int) direction)
-#define HEAPDEBUG_2 \
-	elog(DEBUG2, "heap_getnext returning EOS")
-#define HEAPDEBUG_3 \
-	elog(DEBUG2, "heap_getnext returning tuple")
-#else
-#define HEAPDEBUG_1
-#define HEAPDEBUG_2
-#define HEAPDEBUG_3
-#endif							/* !defined(HEAPDEBUGALL) */
-
-
-HeapTuple
-heap_getnext(HeapScanDesc scan, ScanDirection direction)
-{
-	/* Note: no locking manipulations needed */
-
-	HEAPDEBUG_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)
-	{
-		HEAPDEBUG_2;			/* heap_getnext returning EOS */
-		return NULL;
-	}
-
-	/*
-	 * if we get here it means we have a new current scan tuple, so point to
-	 * the proper return buffer and return the tuple.
-	 */
-	HEAPDEBUG_3;				/* heap_getnext returning tuple */
-
-	pgstat_count_heap_getnext(scan->rs_rd);
-
-	return &(scan->rs_ctup);
-}
-
-/*
- *	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
-heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
-					   Snapshot snapshot, HeapTuple heapTuple,
-					   bool *all_dead, bool first_call)
-{
-	Page		dp = (Page) BufferGetPage(buffer);
-	TransactionId prev_xmax = InvalidTransactionId;
-	OffsetNumber offnum;
-	bool		at_chain_start;
-	bool		valid;
-	bool		skip;
-
-	/* If this is not the first call, previous call returned a (live!) tuple */
-	if (all_dead)
-		*all_dead = first_call;
-
-	Assert(TransactionIdIsValid(RecentGlobalXmin));
-
-	Assert(ItemPointerGetBlockNumber(tid) == BufferGetBlockNumber(buffer));
-	offnum = ItemPointerGetOffsetNumber(tid);
-	at_chain_start = first_call;
-	skip = !first_call;
-
-	heapTuple->t_self = *tid;
-
-	/* Scan through possible multiple members of HOT-chain */
-	for (;;)
-	{
-		ItemId		lp;
-
-		/* check for bogus TID */
-		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(dp))
-			break;
-
-		lp = PageGetItemId(dp, offnum);
-
-		/* check for unused, dead, or redirected items */
-		if (!ItemIdIsNormal(lp))
-		{
-			/* We should only see a redirect at start of chain */
-			if (ItemIdIsRedirected(lp) && at_chain_start)
-			{
-				/* Follow the redirect */
-				offnum = ItemIdGetRedirect(lp);
-				at_chain_start = false;
-				continue;
-			}
-			/* else must be end of chain */
-			break;
-		}
-
-		heapTuple->t_data = (HeapTupleHeader) PageGetItem(dp, lp);
-		heapTuple->t_len = ItemIdGetLength(lp);
-		heapTuple->t_tableOid = RelationGetRelid(relation);
-		ItemPointerSetOffsetNumber(&heapTuple->t_self, offnum);
-
-		/*
-		 * Shouldn't see a HEAP_ONLY tuple at chain start.
-		 */
-		if (at_chain_start && HeapTupleIsHeapOnly(heapTuple))
-			break;
-
-		/*
-		 * The xmin should match the previous xmax value, else chain is
-		 * broken.
-		 */
-		if (TransactionIdIsValid(prev_xmax) &&
-			!TransactionIdEquals(prev_xmax,
-								 HeapTupleHeaderGetXmin(heapTuple->t_data)))
-			break;
-
-		/*
-		 * When first_call is true (and thus, skip is initially false) we'll
-		 * return the first tuple we find.  But on later passes, heapTuple
-		 * will initially be pointing to the tuple we returned last time.
-		 * Returning it again would be incorrect (and would loop forever), so
-		 * we skip it and return the next match we find.
-		 */
-		if (!skip)
-		{
-			/*
-			 * For the benefit of logical decoding, have t_self point at the
-			 * element of the HOT chain we're currently investigating instead
-			 * of the root tuple of the HOT chain. This is important because
-			 * the *Satisfies routine for historical mvcc snapshots needs the
-			 * correct tid to decide about the visibility in some cases.
-			 */
-			ItemPointerSet(&(heapTuple->t_self), BufferGetBlockNumber(buffer), offnum);
-
-			/* If it's visible per the snapshot, we must return it */
-			valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, heapTuple, snapshot, buffer);
-			CheckForSerializableConflictOut(valid, relation, heapTuple,
-											buffer, snapshot);
-			/* reset to original, non-redirected, tid */
-			heapTuple->t_self = *tid;
-
-			if (valid)
-			{
-				ItemPointerSetOffsetNumber(tid, offnum);
-				PredicateLockTuple(relation, heapTuple, snapshot);
-				if (all_dead)
-					*all_dead = false;
-				return true;
-			}
-		}
-		skip = false;
-
-		/*
-		 * If we can't see it, maybe no one else can either.  At caller
-		 * request, check whether all chain members are dead to all
-		 * transactions.
-		 *
-		 * Note: if you change the criterion here for what is "dead", fix the
-		 * planner's get_actual_variable_range() function to match.
-		 */
-		if (all_dead && *all_dead &&
-			!HeapTupleIsSurelyDead(heapTuple, RecentGlobalXmin))
-			*all_dead = false;
-
-		/*
-		 * Check to see if HOT chain continues past this tuple; if so fetch
-		 * the next offnum and loop around.
-		 */
-		if (HeapTupleIsHotUpdated(heapTuple))
-		{
-			Assert(ItemPointerGetBlockNumber(&heapTuple->t_data->t_ctid) ==
-				   ItemPointerGetBlockNumber(tid));
-			offnum = ItemPointerGetOffsetNumber(&heapTuple->t_data->t_ctid);
-			at_chain_start = false;
-			prev_xmax = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
-		}
-		else
-			break;				/* end of chain */
-	}
-
-	return false;
-}
-
-/*
- *	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
-heap_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 = heap_hot_search_buffer(tid, relation, buffer, snapshot,
-									&heapTuple, all_dead, true);
-	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-	ReleaseBuffer(buffer);
-	return result;
-}
-
-
 /*
  * UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends
  *
@@ -4762,32 +3420,6 @@ heap_execute_freeze_tuple(HeapTupleHeader tuple, xl_heap_freeze_tuple *frz)
 	tuple->t_infomask2 = frz->t_infomask2;
 }
 
-/*
- * heap_freeze_tuple
- *		Freeze tuple in place, without WAL logging.
- *
- * Useful for callers like CLUSTER that perform their own WAL logging.
- */
-bool
-heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
-				  TransactionId cutoff_multi)
-{
-	xl_heap_freeze_tuple frz;
-	bool		do_freeze;
-	bool		tuple_totally_frozen;
-
-	do_freeze = heap_prepare_freeze_tuple(tuple, cutoff_xid, cutoff_multi,
-										  &frz, &tuple_totally_frozen);
-
-	/*
-	 * Note that because this is not a WAL-logged operation, we don't need to
-	 * fill in the offset in the freeze record.
-	 */
-
-	if (do_freeze)
-		heap_execute_freeze_tuple(tuple, &frz);
-	return do_freeze;
-}
 
 /*
  * For a given MultiXactId, return the hint bits that should be set in the
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index a0e3272..12a8f56 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -1666,6 +1666,1094 @@ HeapTupleSatisfiesHistoricMVCC(StorageTuple stup, Snapshot snapshot,
 		return true;
 }
 
+/* ----------------------------------------------------------------
+ *						 heap support routines
+ * ----------------------------------------------------------------
+ */
+
+/* ----------------
+ *		heap_parallelscan_startblock_init - find and set the scan's startblock
+ *
+ *		Determine where the parallel seq scan should start.  This function may
+ *		be called many times, once by each parallel worker.  We must be careful
+ *		only to set the startblock once.
+ * ----------------
+ */
+static void
+heap_parallelscan_startblock_init(HeapScanDesc scan)
+{
+	BlockNumber sync_startpage = InvalidBlockNumber;
+	ParallelHeapScanDesc parallel_scan;
+
+	Assert(scan->rs_parallel);
+	parallel_scan = scan->rs_parallel;
+
+retry:
+	/* Grab the spinlock. */
+	SpinLockAcquire(&parallel_scan->phs_mutex);
+
+	/*
+	 * If the scan's startblock has not yet been initialized, we must do so
+	 * now.  If this is not a synchronized scan, we just start at block 0, but
+	 * if it is a synchronized scan, we must get the starting position from
+	 * the synchronized scan machinery.  We can't hold the spinlock while
+	 * doing that, though, so release the spinlock, get the information we
+	 * need, and retry.  If nobody else has initialized the scan in the
+	 * meantime, we'll fill in the value we fetched on the second time
+	 * through.
+	 */
+	if (parallel_scan->phs_startblock == InvalidBlockNumber)
+	{
+		if (!parallel_scan->phs_syncscan)
+			parallel_scan->phs_startblock = 0;
+		else if (sync_startpage != InvalidBlockNumber)
+			parallel_scan->phs_startblock = sync_startpage;
+		else
+		{
+			SpinLockRelease(&parallel_scan->phs_mutex);
+			sync_startpage = ss_get_location(scan->rs_rd, scan->rs_nblocks);
+			goto retry;
+		}
+	}
+	SpinLockRelease(&parallel_scan->phs_mutex);
+}
+
+/* ----------------
+ *		heap_parallelscan_nextpage - get the next page to scan
+ *
+ *		Get the next page to scan.  Even if there are no pages left to scan,
+ *		another backend could have grabbed a page to scan and not yet finished
+ *		looking at it, so it doesn't follow that the scan is done when the
+ *		first backend gets an InvalidBlockNumber return.
+ * ----------------
+ */
+static BlockNumber
+heap_parallelscan_nextpage(HeapScanDesc scan)
+{
+	BlockNumber page;
+	ParallelHeapScanDesc parallel_scan;
+	uint64		nallocated;
+
+	Assert(scan->rs_parallel);
+	parallel_scan = scan->rs_parallel;
+
+	/*
+	 * phs_nallocated tracks how many pages have been allocated to workers
+	 * already.  When phs_nallocated >= rs_nblocks, all blocks have been
+	 * allocated.
+	 *
+	 * Because we use an atomic fetch-and-add to fetch the current value, the
+	 * phs_nallocated counter will exceed rs_nblocks, because workers will
+	 * still increment the value, when they try to allocate the next block but
+	 * all blocks have been allocated already. The counter must be 64 bits
+	 * wide because of that, to avoid wrapping around when rs_nblocks is close
+	 * to 2^32.
+	 *
+	 * The actual page to return is calculated by adding the counter to the
+	 * starting block number, modulo nblocks.
+	 */
+	nallocated = pg_atomic_fetch_add_u64(&parallel_scan->phs_nallocated, 1);
+	if (nallocated >= scan->rs_nblocks)
+		page = InvalidBlockNumber;	/* all blocks have been allocated */
+	else
+		page = (nallocated + parallel_scan->phs_startblock) % scan->rs_nblocks;
+
+	/*
+	 * Report scan location.  Normally, we report the current page number.
+	 * When we reach the end of the scan, though, we report the starting page,
+	 * not the ending page, just so the starting positions for later scans
+	 * 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 (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);
+	}
+
+	return page;
+}
+
+
+/* ----------------
+ *		initscan - scan code common to heap_beginscan and heap_rescan
+ * ----------------
+ */
+static void
+initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
+{
+	bool		allow_strat;
+	bool		allow_sync;
+
+	/*
+	 * Determine the number of blocks we have to scan.
+	 *
+	 * It is sufficient to do this once at scan start, since any tuples added
+	 * while the scan is in progress will be invisible to my snapshot anyway.
+	 * (That is not true when using a non-MVCC snapshot.  However, we couldn't
+	 * guarantee to return tuples added after scan start anyway, since they
+	 * might go into pages we already scanned.  To guarantee consistent
+	 * results for a non-MVCC snapshot, the caller must hold some higher-level
+	 * lock that ensures the interesting tuple(s) won't change.)
+	 */
+	if (scan->rs_parallel != NULL)
+		scan->rs_nblocks = scan->rs_parallel->phs_nblocks;
+	else
+		scan->rs_nblocks = RelationGetNumberOfBlocks(scan->rs_rd);
+
+	/*
+	 * If the table is large relative to NBuffers, use a bulk-read access
+	 * strategy and enable synchronized scanning (see syncscan.c).  Although
+	 * the thresholds for these features could be different, we make them the
+	 * same so that there are only two behaviors to tune rather than four.
+	 * (However, some callers need to be able to disable one or both of these
+	 * behaviors, independently of the size of the table; also there is a GUC
+	 * variable that can disable synchronized scanning.)
+	 *
+	 * 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)
+	{
+		allow_strat = scan->rs_allow_strat;
+		allow_sync = scan->rs_allow_sync;
+	}
+	else
+		allow_strat = allow_sync = false;
+
+	if (allow_strat)
+	{
+		/* During a rescan, keep the previous strategy object. */
+		if (scan->rs_strategy == NULL)
+			scan->rs_strategy = GetAccessStrategy(BAS_BULKREAD);
+	}
+	else
+	{
+		if (scan->rs_strategy != NULL)
+			FreeAccessStrategy(scan->rs_strategy);
+		scan->rs_strategy = NULL;
+	}
+
+	if (scan->rs_parallel != NULL)
+	{
+		/* For parallel scan, believe whatever ParallelHeapScanDesc says. */
+		scan->rs_syncscan = scan->rs_parallel->phs_syncscan;
+	}
+	else if (keep_startblock)
+	{
+		/*
+		 * When rescanning, we want to keep the previous startblock setting,
+		 * so that rewinding a cursor doesn't generate surprising results.
+		 * Reset the active syncscan setting, though.
+		 */
+		scan->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);
+	}
+	else
+	{
+		scan->rs_syncscan = false;
+		scan->rs_startblock = 0;
+	}
+
+	scan->rs_numblocks = InvalidBlockNumber;
+	scan->rs_inited = false;
+	scan->rs_ctup.t_data = NULL;
+	ItemPointerSetInvalid(&scan->rs_ctup.t_self);
+	scan->rs_cbuf = InvalidBuffer;
+	scan->rs_cblock = InvalidBlockNumber;
+
+	/* page-at-a-time fields are always invalid when not rs_inited */
+
+	/*
+	 * copy the scan key, if appropriate
+	 */
+	if (key != NULL)
+		memcpy(scan->rs_key, key, scan->rs_nkeys * sizeof(ScanKeyData));
+
+	/*
+	 * Currently, we don't have a stats counter for bitmap heap scans (but the
+	 * underlying bitmap index scans will be counted) or sample scans (we only
+	 * update stats for tuple fetches there)
+	 */
+	if (!scan->rs_bitmapscan && !scan->rs_samplescan)
+		pgstat_count_heap_scan(scan->rs_rd);
+}
+
+
+/* ----------------
+ *		heapgettup - fetch next heap tuple
+ *
+ *		Initialize the scan if not already done; then advance to the next
+ *		tuple as indicated by "dir"; return the next tuple in scan->rs_ctup,
+ *		or set scan->rs_ctup.t_data = NULL if no more tuples.
+ *
+ * dir == NoMovementScanDirection means "re-fetch the tuple indicated
+ * by scan->rs_ctup".
+ *
+ * Note: the reason nkeys/key are passed separately, even though they are
+ * kept in the scan descriptor, is that the caller may not want us to check
+ * the scankeys.
+ *
+ * Note: when we fall off the end of the scan in either direction, we
+ * reset rs_inited.  This means that a further request with the same
+ * scan direction will restart the scan, which is a bit odd, but a
+ * request with the opposite scan direction will start a fresh scan
+ * in the proper direction.  The latter is required behavior for cursors,
+ * while the former case is generally undefined behavior in Postgres
+ * so we don't care too much.
+ * ----------------
+ */
+static void
+heapgettup(HeapScanDesc scan,
+		   ScanDirection dir,
+		   int nkeys,
+		   ScanKey key)
+{
+	HeapTuple	tuple = &(scan->rs_ctup);
+	Snapshot	snapshot = scan->rs_snapshot;
+	bool		backward = ScanDirectionIsBackward(dir);
+	BlockNumber page;
+	bool		finished;
+	Page		dp;
+	int			lines;
+	OffsetNumber lineoff;
+	int			linesleft;
+	ItemId		lpp;
+
+	/*
+	 * calculate next starting lineoff, given scan direction
+	 */
+	if (ScanDirectionIsForward(dir))
+	{
+		if (!scan->rs_inited)
+		{
+			/*
+			 * return null immediately if relation is empty
+			 */
+			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			{
+				Assert(!BufferIsValid(scan->rs_cbuf));
+				tuple->t_data = NULL;
+				return;
+			}
+			if (scan->rs_parallel != NULL)
+			{
+				heap_parallelscan_startblock_init(scan);
+
+				page = heap_parallelscan_nextpage(scan);
+
+				/* Other processes might have already finished the scan. */
+				if (page == InvalidBlockNumber)
+				{
+					Assert(!BufferIsValid(scan->rs_cbuf));
+					tuple->t_data = NULL;
+					return;
+				}
+			}
+			else
+				page = scan->rs_startblock; /* first page */
+			heapgetpage(scan, page);
+			lineoff = FirstOffsetNumber;	/* first offnum */
+			scan->rs_inited = true;
+		}
+		else
+		{
+			/* continue from previously returned page/tuple */
+			page = scan->rs_cblock; /* current page */
+			lineoff =			/* next offnum */
+				OffsetNumberNext(ItemPointerGetOffsetNumber(&(tuple->t_self)));
+		}
+
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		lines = PageGetMaxOffsetNumber(dp);
+		/* page and lineoff now reference the physically next tid */
+
+		linesleft = lines - lineoff + 1;
+	}
+	else if (backward)
+	{
+		/* backward parallel scan not supported */
+		Assert(scan->rs_parallel == NULL);
+
+		if (!scan->rs_inited)
+		{
+			/*
+			 * return null immediately if relation is empty
+			 */
+			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			{
+				Assert(!BufferIsValid(scan->rs_cbuf));
+				tuple->t_data = NULL;
+				return;
+			}
+
+			/*
+			 * Disable reporting to syncscan logic in a backwards scan; it's
+			 * not very likely anyone else is doing the same thing at the same
+			 * time, and much more likely that we'll just bollix things for
+			 * forward scanners.
+			 */
+			scan->rs_syncscan = false;
+			/* start from last page of the scan */
+			if (scan->rs_startblock > 0)
+				page = scan->rs_startblock - 1;
+			else
+				page = scan->rs_nblocks - 1;
+			heapgetpage(scan, page);
+		}
+		else
+		{
+			/* continue from previously returned page/tuple */
+			page = scan->rs_cblock; /* current page */
+		}
+
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		lines = PageGetMaxOffsetNumber(dp);
+
+		if (!scan->rs_inited)
+		{
+			lineoff = lines;	/* final offnum */
+			scan->rs_inited = true;
+		}
+		else
+		{
+			lineoff =			/* previous offnum */
+				OffsetNumberPrev(ItemPointerGetOffsetNumber(&(tuple->t_self)));
+		}
+		/* page and lineoff now reference the physically previous tid */
+
+		linesleft = lineoff;
+	}
+	else
+	{
+		/*
+		 * ``no movement'' scan direction: refetch prior tuple
+		 */
+		if (!scan->rs_inited)
+		{
+			Assert(!BufferIsValid(scan->rs_cbuf));
+			tuple->t_data = NULL;
+			return;
+		}
+
+		page = ItemPointerGetBlockNumber(&(tuple->t_self));
+		if (page != scan->rs_cblock)
+			heapgetpage(scan, page);
+
+		/* Since the tuple was previously fetched, needn't lock page here */
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
+		lpp = PageGetItemId(dp, lineoff);
+		Assert(ItemIdIsNormal(lpp));
+
+		tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+		tuple->t_len = ItemIdGetLength(lpp);
+
+		return;
+	}
+
+	/*
+	 * advance the scan until we find a qualifying tuple or run out of stuff
+	 * to scan
+	 */
+	lpp = PageGetItemId(dp, lineoff);
+	for (;;)
+	{
+		while (linesleft > 0)
+		{
+			if (ItemIdIsNormal(lpp))
+			{
+				bool		valid;
+
+				tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+				tuple->t_len = ItemIdGetLength(lpp);
+				ItemPointerSet(&(tuple->t_self), page, lineoff);
+
+				/*
+				 * if current tuple qualifies, return it.
+				 */
+				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine,
+													 tuple,
+													 snapshot,
+													 scan->rs_cbuf);
+
+				CheckForSerializableConflictOut(valid, scan->rs_rd, tuple,
+												scan->rs_cbuf, snapshot);
+
+				if (valid && key != NULL)
+					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
+								nkeys, key, valid);
+
+				if (valid)
+				{
+					LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+					return;
+				}
+			}
+
+			/*
+			 * otherwise move to the next item on the page
+			 */
+			--linesleft;
+			if (backward)
+			{
+				--lpp;			/* move back in this page's ItemId array */
+				--lineoff;
+			}
+			else
+			{
+				++lpp;			/* move forward in this page's ItemId array */
+				++lineoff;
+			}
+		}
+
+		/*
+		 * 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);
+
+		/*
+		 * 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);
+			if (page == 0)
+				page = scan->rs_nblocks;
+			page--;
+		}
+		else if (scan->rs_parallel != NULL)
+		{
+			page = heap_parallelscan_nextpage(scan);
+			finished = (page == InvalidBlockNumber);
+		}
+		else
+		{
+			page++;
+			if (page >= scan->rs_nblocks)
+				page = 0;
+			finished = (page == scan->rs_startblock) ||
+				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+
+			/*
+			 * Report our new scan position for synchronization purposes. We
+			 * don't do that when moving backwards, however. That would just
+			 * mess up any other forward-moving scanners.
+			 *
+			 * Note: we do this before checking for end of scan so that the
+			 * final state of the position hint is back at the start of the
+			 * rel.  That's not strictly necessary, but otherwise when you run
+			 * the same query multiple times the starting position would shift
+			 * 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);
+		}
+
+		/*
+		 * return NULL if we've exhausted all the pages
+		 */
+		if (finished)
+		{
+			if (BufferIsValid(scan->rs_cbuf))
+				ReleaseBuffer(scan->rs_cbuf);
+			scan->rs_cbuf = InvalidBuffer;
+			scan->rs_cblock = InvalidBlockNumber;
+			tuple->t_data = NULL;
+			scan->rs_inited = false;
+			return;
+		}
+
+		heapgetpage(scan, page);
+
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		lines = PageGetMaxOffsetNumber((Page) dp);
+		linesleft = lines;
+		if (backward)
+		{
+			lineoff = lines;
+			lpp = PageGetItemId(dp, lines);
+		}
+		else
+		{
+			lineoff = FirstOffsetNumber;
+			lpp = PageGetItemId(dp, FirstOffsetNumber);
+		}
+	}
+}
+
+/* ----------------
+ *		heapgettup_pagemode - fetch next heap tuple in page-at-a-time mode
+ *
+ *		Same API as heapgettup, but used in page-at-a-time mode
+ *
+ * The internal logic is much the same as heapgettup's too, but there are some
+ * differences: we do not take the buffer content lock (that only needs to
+ * happen inside heapgetpage), and we iterate through just the tuples listed
+ * in rs_vistuples[] rather than all tuples on the page.  Notice that
+ * lineindex is 0-based, where the corresponding loop variable lineoff in
+ * heapgettup is 1-based.
+ * ----------------
+ */
+static void
+heapgettup_pagemode(HeapScanDesc scan,
+					ScanDirection dir,
+					int nkeys,
+					ScanKey key)
+{
+	HeapTuple	tuple = &(scan->rs_ctup);
+	bool		backward = ScanDirectionIsBackward(dir);
+	BlockNumber page;
+	bool		finished;
+	Page		dp;
+	int			lines;
+	int			lineindex;
+	OffsetNumber lineoff;
+	int			linesleft;
+	ItemId		lpp;
+
+	/*
+	 * calculate next starting lineindex, given scan direction
+	 */
+	if (ScanDirectionIsForward(dir))
+	{
+		if (!scan->rs_inited)
+		{
+			/*
+			 * return null immediately if relation is empty
+			 */
+			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			{
+				Assert(!BufferIsValid(scan->rs_cbuf));
+				tuple->t_data = NULL;
+				return;
+			}
+			if (scan->rs_parallel != NULL)
+			{
+				heap_parallelscan_startblock_init(scan);
+
+				page = heap_parallelscan_nextpage(scan);
+
+				/* Other processes might have already finished the scan. */
+				if (page == InvalidBlockNumber)
+				{
+					Assert(!BufferIsValid(scan->rs_cbuf));
+					tuple->t_data = NULL;
+					return;
+				}
+			}
+			else
+				page = scan->rs_startblock; /* first page */
+			heapgetpage(scan, page);
+			lineindex = 0;
+			scan->rs_inited = true;
+		}
+		else
+		{
+			/* continue from previously returned page/tuple */
+			page = scan->rs_cblock; /* current page */
+			lineindex = scan->rs_cindex + 1;
+		}
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
+		lines = scan->rs_ntuples;
+		/* page and lineindex now reference the next visible tid */
+
+		linesleft = lines - lineindex;
+	}
+	else if (backward)
+	{
+		/* backward parallel scan not supported */
+		Assert(scan->rs_parallel == NULL);
+
+		if (!scan->rs_inited)
+		{
+			/*
+			 * return null immediately if relation is empty
+			 */
+			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			{
+				Assert(!BufferIsValid(scan->rs_cbuf));
+				tuple->t_data = NULL;
+				return;
+			}
+
+			/*
+			 * Disable reporting to syncscan logic in a backwards scan; it's
+			 * not very likely anyone else is doing the same thing at the same
+			 * time, and much more likely that we'll just bollix things for
+			 * forward scanners.
+			 */
+			scan->rs_syncscan = false;
+			/* start from last page of the scan */
+			if (scan->rs_startblock > 0)
+				page = scan->rs_startblock - 1;
+			else
+				page = scan->rs_nblocks - 1;
+			heapgetpage(scan, page);
+		}
+		else
+		{
+			/* continue from previously returned page/tuple */
+			page = scan->rs_cblock; /* current page */
+		}
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
+		lines = scan->rs_ntuples;
+
+		if (!scan->rs_inited)
+		{
+			lineindex = lines - 1;
+			scan->rs_inited = true;
+		}
+		else
+		{
+			lineindex = scan->rs_cindex - 1;
+		}
+		/* page and lineindex now reference the previous visible tid */
+
+		linesleft = lineindex + 1;
+	}
+	else
+	{
+		/*
+		 * ``no movement'' scan direction: refetch prior tuple
+		 */
+		if (!scan->rs_inited)
+		{
+			Assert(!BufferIsValid(scan->rs_cbuf));
+			tuple->t_data = NULL;
+			return;
+		}
+
+		page = ItemPointerGetBlockNumber(&(tuple->t_self));
+		if (page != scan->rs_cblock)
+			heapgetpage(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);
+		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
+		lpp = PageGetItemId(dp, lineoff);
+		Assert(ItemIdIsNormal(lpp));
+
+		tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+		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]);
+
+		return;
+	}
+
+	/*
+	 * advance the scan until we find a qualifying tuple or run out of stuff
+	 * to scan
+	 */
+	for (;;)
+	{
+		while (linesleft > 0)
+		{
+			lineoff = scan->rs_vistuples[lineindex];
+			lpp = PageGetItemId(dp, lineoff);
+			Assert(ItemIdIsNormal(lpp));
+
+			tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+			tuple->t_len = ItemIdGetLength(lpp);
+			ItemPointerSet(&(tuple->t_self), page, lineoff);
+
+			/*
+			 * if current tuple qualifies, return it.
+			 */
+			if (key != NULL)
+			{
+				bool		valid;
+
+				HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
+							nkeys, key, valid);
+				if (valid)
+				{
+					scan->rs_cindex = lineindex;
+					return;
+				}
+			}
+			else
+			{
+				scan->rs_cindex = lineindex;
+				return;
+			}
+
+			/*
+			 * otherwise move to the next item on the page
+			 */
+			--linesleft;
+			if (backward)
+				--lineindex;
+			else
+				++lineindex;
+		}
+
+		/*
+		 * if we get here, it means we've exhausted the items on this page and
+		 * it's time to move to the next.
+		 */
+		if (backward)
+		{
+			finished = (page == scan->rs_startblock) ||
+				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+			if (page == 0)
+				page = scan->rs_nblocks;
+			page--;
+		}
+		else if (scan->rs_parallel != NULL)
+		{
+			page = heap_parallelscan_nextpage(scan);
+			finished = (page == InvalidBlockNumber);
+		}
+		else
+		{
+			page++;
+			if (page >= scan->rs_nblocks)
+				page = 0;
+			finished = (page == scan->rs_startblock) ||
+				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+
+			/*
+			 * Report our new scan position for synchronization purposes. We
+			 * don't do that when moving backwards, however. That would just
+			 * mess up any other forward-moving scanners.
+			 *
+			 * Note: we do this before checking for end of scan so that the
+			 * final state of the position hint is back at the start of the
+			 * rel.  That's not strictly necessary, but otherwise when you run
+			 * the same query multiple times the starting position would shift
+			 * 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);
+		}
+
+		/*
+		 * return NULL if we've exhausted all the pages
+		 */
+		if (finished)
+		{
+			if (BufferIsValid(scan->rs_cbuf))
+				ReleaseBuffer(scan->rs_cbuf);
+			scan->rs_cbuf = InvalidBuffer;
+			scan->rs_cblock = InvalidBlockNumber;
+			tuple->t_data = NULL;
+			scan->rs_inited = false;
+			return;
+		}
+
+		heapgetpage(scan, page);
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
+		lines = scan->rs_ntuples;
+		linesleft = lines;
+		if (backward)
+			lineindex = lines - 1;
+		else
+			lineindex = 0;
+	}
+}
+
+
+static HeapScanDesc
+heapam_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)
+{
+	HeapScanDesc scan;
+
+	/*
+	 * increment relation ref count while scanning relation
+	 *
+	 * This is just to make really sure the relcache entry won't go away while
+	 * the scan has a pointer to it.  Caller should be holding the rel open
+	 * anyway, so this is redundant in all normal scenarios...
+	 */
+	RelationIncrementReferenceCount(relation);
+
+	/*
+	 * allocate and initialize scan descriptor
+	 */
+	scan = (HeapScanDesc) palloc(sizeof(HeapScanDescData));
+
+	scan->rs_rd = relation;
+	scan->rs_snapshot = snapshot;
+	scan->rs_nkeys = nkeys;
+	scan->rs_bitmapscan = is_bitmapscan;
+	scan->rs_samplescan = is_samplescan;
+	scan->rs_strategy = NULL;	/* set in initscan */
+	scan->rs_allow_strat = allow_strat;
+	scan->rs_allow_sync = allow_sync;
+	scan->rs_temp_snap = temp_snap;
+	scan->rs_parallel = parallel_scan;
+
+	/*
+	 * we can use page-at-a-time mode if it's an MVCC-safe snapshot
+	 */
+	scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(snapshot);
+
+	/*
+	 * For a seqscan in a serializable transaction, acquire a predicate lock
+	 * on the entire relation. This is required not only to lock all the
+	 * matching tuples, but also to conflict with new insertions into the
+	 * table. In an indexscan, we take page locks on the index pages covering
+	 * the range specified in the scan qual, but in a heap scan there is
+	 * nothing more fine-grained to lock. A bitmap scan is a different story,
+	 * there we have already scanned the index and locked the index pages
+	 * covering the predicate. But in that case we still have to lock any
+	 * matching heap tuples.
+	 */
+	if (!is_bitmapscan)
+		PredicateLockRelation(relation, snapshot);
+
+	/* we only need to set this up once */
+	scan->rs_ctup.t_tableOid = RelationGetRelid(relation);
+
+	/*
+	 * we do this here instead of in initscan() because heap_rescan also calls
+	 * initscan() and we don't want to allocate memory again
+	 */
+	if (nkeys > 0)
+		scan->rs_key = (ScanKey) palloc(sizeof(ScanKeyData) * nkeys);
+	else
+		scan->rs_key = NULL;
+
+	initscan(scan, key, false);
+
+	return scan;
+}
+
+/* ----------------
+ *		heapam_rescan		- restart a relation scan
+ * ----------------
+ */
+static void
+heapam_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
+	 */
+	if (BufferIsValid(scan->rs_cbuf))
+		ReleaseBuffer(scan->rs_cbuf);
+
+	/*
+	 * reinitialize scan descriptor
+	 */
+	initscan(scan, key, true);
+
+	/*
+	 * 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);
+	}
+}
+
+/* ----------------
+ *		heapam_endscan	- end relation scan
+ *
+ *		See how to integrate with index scans.
+ *		Check handling if reldesc caching.
+ * ----------------
+ */
+static void
+heapam_endscan(HeapScanDesc scan)
+{
+	/* Note: no locking manipulations needed */
+
+	/*
+	 * unpin scan buffers
+	 */
+	if (BufferIsValid(scan->rs_cbuf))
+		ReleaseBuffer(scan->rs_cbuf);
+
+	/*
+	 * decrement relation reference count and free scan descriptor storage
+	 */
+	RelationDecrementReferenceCount(scan->rs_rd);
+
+	if (scan->rs_key)
+		pfree(scan->rs_key);
+
+	if (scan->rs_strategy != NULL)
+		FreeAccessStrategy(scan->rs_strategy);
+
+	if (scan->rs_temp_snap)
+		UnregisterSnapshot(scan->rs_snapshot);
+
+	pfree(scan);
+}
+
+/* ----------------
+ *		heapam_scan_update_snapshot
+ *
+ *		Update snapshot info in heap scan descriptor.
+ * ----------------
+ */
+static void
+heapam_scan_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
+{
+	Assert(IsMVCCSnapshot(snapshot));
+
+	RegisterSnapshot(snapshot);
+	scan->rs_snapshot = snapshot;
+	scan->rs_temp_snap = true;
+}
+
+/* ----------------
+ *		heapam_getnext	- retrieve next tuple in scan
+ *
+ *		Fix to work with index relations.
+ *		We don't return the buffer anymore, but you can get it from the
+ *		returned HeapTuple.
+ * ----------------
+ */
+
+#ifdef HEAPAMDEBUGALL
+#define HEAPAMDEBUG_1 \
+	elog(DEBUG2, "heapam_getnext([%s,nkeys=%d],dir=%d) called", \
+		 RelationGetRelationName(scan->rs_rd), scan->rs_nkeys, (int) direction)
+#define HEAPAMDEBUG_2 \
+	elog(DEBUG2, "heapam_getnext returning EOS")
+#define HEAPAMDEBUG_3 \
+	elog(DEBUG2, "heapam_getnext returning tuple")
+#else
+#define HEAPAMDEBUG_1
+#define HEAPAMDEBUG_2
+#define HEAPAMDEBUG_3
+#endif							/* !defined(HEAPDEBUGALL) */
+
+
+static StorageTuple
+heapam_getnext(HeapScanDesc sscan, ScanDirection direction)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	/* Note: no locking manipulations needed */
+
+	HEAPAMDEBUG_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)
+	{
+		HEAPAMDEBUG_2;			/* heap_getnext returning EOS */
+		return NULL;
+	}
+
+	/*
+	 * if we get here it means we have a new current scan tuple, so point to
+	 * the proper return buffer and return the tuple.
+	 */
+	HEAPAMDEBUG_3;				/* heap_getnext returning tuple */
+
+	pgstat_count_heap_getnext(scan->rs_rd);
+
+	return &(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
+
+static TupleTableSlot *
+heapam_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
@@ -1818,9 +2906,210 @@ heap_fetch(Relation relation,
 	return false;
 }
 
+/*
+ *	heapam_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.
+ */
+static bool
+heapam_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
+					   Snapshot snapshot, HeapTuple heapTuple,
+					   bool *all_dead, bool first_call)
+{
+	Page		dp = (Page) BufferGetPage(buffer);
+	TransactionId prev_xmax = InvalidTransactionId;
+	OffsetNumber offnum;
+	bool		at_chain_start;
+	bool		valid;
+	bool		skip;
+
+	/* If this is not the first call, previous call returned a (live!) tuple */
+	if (all_dead)
+		*all_dead = first_call;
+
+	Assert(TransactionIdIsValid(RecentGlobalXmin));
+
+	Assert(ItemPointerGetBlockNumber(tid) == BufferGetBlockNumber(buffer));
+	offnum = ItemPointerGetOffsetNumber(tid);
+	at_chain_start = first_call;
+	skip = !first_call;
+
+	heapTuple->t_self = *tid;
 
+	/* Scan through possible multiple members of HOT-chain */
+	for (;;)
+	{
+		ItemId		lp;
 
+		/* check for bogus TID */
+		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(dp))
+			break;
 
+		lp = PageGetItemId(dp, offnum);
+
+		/* check for unused, dead, or redirected items */
+		if (!ItemIdIsNormal(lp))
+		{
+			/* We should only see a redirect at start of chain */
+			if (ItemIdIsRedirected(lp) && at_chain_start)
+			{
+				/* Follow the redirect */
+				offnum = ItemIdGetRedirect(lp);
+				at_chain_start = false;
+				continue;
+			}
+			/* else must be end of chain */
+			break;
+		}
+
+		heapTuple->t_data = (HeapTupleHeader) PageGetItem(dp, lp);
+		heapTuple->t_len = ItemIdGetLength(lp);
+		heapTuple->t_tableOid = RelationGetRelid(relation);
+		ItemPointerSetOffsetNumber(&heapTuple->t_self, offnum);
+
+		/*
+		 * Shouldn't see a HEAP_ONLY tuple at chain start.
+		 */
+		if (at_chain_start && HeapTupleIsHeapOnly(heapTuple))
+			break;
+
+		/*
+		 * The xmin should match the previous xmax value, else chain is
+		 * broken.
+		 */
+		if (TransactionIdIsValid(prev_xmax) &&
+			!TransactionIdEquals(prev_xmax,
+								 HeapTupleHeaderGetXmin(heapTuple->t_data)))
+			break;
+
+		/*
+		 * When first_call is true (and thus, skip is initially false) we'll
+		 * return the first tuple we find.  But on later passes, heapTuple
+		 * will initially be pointing to the tuple we returned last time.
+		 * Returning it again would be incorrect (and would loop forever), so
+		 * we skip it and return the next match we find.
+		 */
+		if (!skip)
+		{
+			/*
+			 * For the benefit of logical decoding, have t_self point at the
+			 * element of the HOT chain we're currently investigating instead
+			 * of the root tuple of the HOT chain. This is important because
+			 * the *Satisfies routine for historical mvcc snapshots needs the
+			 * correct tid to decide about the visibility in some cases.
+			 */
+			ItemPointerSet(&(heapTuple->t_self), BufferGetBlockNumber(buffer), offnum);
+
+			/* If it's visible per the snapshot, we must return it */
+			valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, heapTuple, snapshot, buffer);
+			CheckForSerializableConflictOut(valid, relation, heapTuple,
+											buffer, snapshot);
+			/* reset to original, non-redirected, tid */
+			heapTuple->t_self = *tid;
+
+			if (valid)
+			{
+				ItemPointerSetOffsetNumber(tid, offnum);
+				PredicateLockTuple(relation, heapTuple, snapshot);
+				if (all_dead)
+					*all_dead = false;
+				return true;
+			}
+		}
+		skip = false;
+
+		/*
+		 * If we can't see it, maybe no one else can either.  At caller
+		 * request, check whether all chain members are dead to all
+		 * transactions.
+		 */
+		if (all_dead && *all_dead &&
+			!HeapTupleIsSurelyDead(heapTuple, RecentGlobalXmin))
+			*all_dead = false;
+
+		/*
+		 * Check to see if HOT chain continues past this tuple; if so fetch
+		 * the next offnum and loop around.
+		 */
+		if (HeapTupleIsHotUpdated(heapTuple))
+		{
+			Assert(ItemPointerGetBlockNumber(&heapTuple->t_data->t_ctid) ==
+				   ItemPointerGetBlockNumber(tid));
+			offnum = ItemPointerGetOffsetNumber(&heapTuple->t_data->t_ctid);
+			at_chain_start = false;
+			prev_xmax = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
+		}
+		else
+			break;				/* end of chain */
+	}
+
+	return false;
+}
+
+/*
+ * heapam_setscanlimits - restrict range of a heapscan
+ *
+ * startBlk is the page to start at
+ * numBlks is number of pages to scan (InvalidBlockNumber means "all")
+ */
+static void
+heapam_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	Assert(!scan->rs_inited);	/* else too late to change */
+	Assert(!scan->rs_syncscan); /* else rs_startblock is significant */
+
+	/* Check startBlk is valid (but allow case of zero blocks...) */
+	Assert(startBlk == 0 || startBlk < scan->rs_nblocks);
+
+	scan->rs_startblock = startBlk;
+	scan->rs_numblocks = numBlks;
+}
+
+
+/*
+ * heapam_freeze_tuple
+ *		Freeze tuple in place, without WAL logging.
+ *
+ * Useful for callers like CLUSTER that perform their own WAL logging.
+ */
+static bool
+heapam_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
+				  TransactionId cutoff_multi)
+{
+	xl_heap_freeze_tuple frz;
+	bool		do_freeze;
+	bool		tuple_totally_frozen;
+
+	do_freeze = heap_prepare_freeze_tuple(tuple, cutoff_xid, cutoff_multi,
+										  &frz, &tuple_totally_frozen);
+
+	/*
+	 * Note that because this is not a WAL-logged operation, we don't need to
+	 * fill in the offset in the freeze record.
+	 */
+
+	if (do_freeze)
+		heap_execute_freeze_tuple(tuple, &frz);
+	return do_freeze;
+}
 
 /* ----------------------------------------------------------------
  *				storage AM support routines for heapam
@@ -3793,6 +5082,16 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 {
 	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
 
+	amroutine->scan_begin = heapam_beginscan;
+	amroutine->scansetlimits = heapam_setscanlimits;
+	amroutine->scan_getnext = heapam_getnext;
+	amroutine->scan_getnextslot = heapam_getnextslot;
+	amroutine->scan_end = heapam_endscan;
+	amroutine->scan_rescan = heapam_rescan;
+	amroutine->scan_update_snapshot = heapam_scan_update_snapshot;
+	amroutine->tuple_freeze = heapam_freeze_tuple;
+	amroutine->hot_search_buffer = heapam_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/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 8fba61c..a475a85 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -409,7 +409,7 @@ rewrite_heap_tuple(RewriteState state,
 	 * While we have our hands on the tuple, we may as well freeze any
 	 * eligible xmin or xmax, so that future VACUUM effort can be saved.
 	 */
-	heap_freeze_tuple(new_tuple->t_data, state->rs_freeze_xid,
+	storage_freeze_tuple(state->rs_new_rel, new_tuple->t_data, state->rs_freeze_xid,
 					  state->rs_cutoff_multi);
 
 	/*
diff --git a/src/backend/access/heap/storageam.c b/src/backend/access/heap/storageam.c
index d1d7364..76b94dc 100644
--- a/src/backend/access/heap/storageam.c
+++ b/src/backend/access/heap/storageam.c
@@ -48,6 +48,174 @@
 #include "utils/tqual.h"
 
 
+/* ----------------
+ *		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);
+}
+
 /*
  *	storage_fetch		- retrieve tuple with given tid
  *
@@ -99,6 +267,73 @@ storage_fetch(Relation relation,
 							userbuf, keep_buf, stats_relation);
 }
 
+/*
+ *	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;
+}
+
+/*
+ * heap_freeze_tuple
+ *		Freeze tuple in place, without WAL logging.
+ *
+ * Useful for callers like CLUSTER that perform their own WAL logging.
+ */
+bool
+storage_freeze_tuple(Relation rel, HeapTupleHeader tuple, TransactionId cutoff_xid,
+				  TransactionId cutoff_multi)
+{
+	return rel->rd_stamroutine->tuple_freeze(tuple, cutoff_xid, cutoff_multi);
+}
+
 
 /*
  *	storage_lock_tuple - lock a tuple in shared or exclusive mode
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 01321a2..db5c93b 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,7 +395,7 @@ 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,
+		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 bef4255..349a127 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,7 +606,7 @@ 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,
+	got_heap_tuple = storage_hot_search_buffer(tid, scan->heapRelation,
 											scan->xs_cbuf,
 											scan->xs_snapshot,
 											&scan->xs_ctup,
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index bf963fc..0e25e9a 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,7 +326,7 @@ _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,
+				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/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 0453fd4..975cd5b 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 "bootstrap/bootstrap.h"
 #include "catalog/index.h"
@@ -573,18 +574,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,
@@ -592,7 +593,7 @@ boot_openrel(char *relname)
 				   sizeof((*app)->am_typ));
 			app++;
 		}
-		heap_endscan(scan);
+		storage_endscan(scan);
 		heap_close(rel, NoLock);
 	}
 
@@ -903,25 +904,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 ccde66a..d2a8a06 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"
@@ -797,14 +798,14 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 								ObjectIdGetDatum(namespaceId));
 
 					rel = heap_open(ProcedureRelationId, AccessShareLock);
-					scan = heap_beginscan_catalog(rel, 1, key);
+					scan = storage_beginscan_catalog(rel, 1, 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;
@@ -842,14 +843,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 0240df7..68c46f7 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"
@@ -1904,10 +1905,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
 	{
@@ -2279,7 +2280,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	}
 
     method = heapRelation->rd_stamroutine;
-	scan = heap_beginscan_strat(heapRelation,	/* relation */
+	scan = storage_beginscan_strat(heapRelation,	/* relation */
 								snapshot,	/* snapshot */
 								0,	/* number of keys */
 								NULL,	/* scan key */
@@ -2288,7 +2289,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 
 	/* 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 */
@@ -2301,7 +2302,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;
 
@@ -2613,7 +2614,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		}
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	/* we can now forget our snapshot, if set */
 	if (IsBootstrapProcessingMode() || indexInfo->ii_Concurrent)
@@ -2684,14 +2685,14 @@ IndexCheckExclusion(Relation heapRelation,
 	 * Scan all live tuples in the base relation.
 	 */
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = heap_beginscan_strat(heapRelation,	/* relation */
+	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 = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((heapTuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		CHECK_FOR_INTERRUPTS();
 
@@ -2727,7 +2728,7 @@ IndexCheckExclusion(Relation heapRelation,
 								   estate, true);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 
 	ExecDropSingleTupleTableSlot(slot);
@@ -3004,7 +3005,7 @@ 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 */
+	scan = storage_beginscan_strat(heapRelation,	/* relation */
 								snapshot,	/* snapshot */
 								0,	/* number of keys */
 								NULL,	/* scan key */
@@ -3014,7 +3015,7 @@ validate_index_heapscan(Relation heapRelation,
 	/*
 	 * 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;
@@ -3171,7 +3172,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 73eff17..b8137f2 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -986,7 +986,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);
 
 		/*
@@ -995,7 +995,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;
@@ -1011,7 +1011,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 5746dc3..1d048e6 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 323471b..517e310 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 3ef7ba8..145e3c1 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 fb53d71..a51f2e4 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 dbcc5bc..e0f6973 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 e2544e5..6727d15 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 c81ddf5..00e71e3 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2028,10 +2028,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();
 
@@ -2043,7 +2043,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 e138539..39850b1 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 b61aaac..46bc3da 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"
@@ -1948,8 +1949,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);
@@ -1989,7 +1990,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 bca2a4c..c0320ec 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4540,7 +4540,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
@@ -4548,7 +4548,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)
 			{
@@ -4661,7 +4661,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 
 		MemoryContextSwitchTo(oldCxt);
-		heap_endscan(scan);
+		storage_endscan(scan);
 		UnregisterSnapshot(snapshot);
 
 		ExecDropSingleTupleTableSlot(oldslot);
@@ -5064,9 +5064,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,
@@ -5078,7 +5078,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;
@@ -8243,7 +8243,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
@@ -8251,7 +8251,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);
 
@@ -8266,7 +8266,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	}
 
 	MemoryContextSwitchTo(oldcxt);
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 	ExecDropSingleTupleTableSlot(slot);
 	FreeExecutorState(estate);
@@ -8321,9 +8321,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;
@@ -8352,7 +8352,7 @@ validateForeignKeyConstraint(char *conname,
 		RI_FKey_check_ins(&fcinfo);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 }
 
@@ -10802,8 +10802,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;
@@ -10862,7 +10862,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 8559c3b..cdfa8ff 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/trigger.c b/src/backend/commands/trigger.c
index d2438ce..fbbdbc2 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -15,8 +15,9 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
-#include "access/sysattr.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
+#include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 7ed16ae..c07f508 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"
@@ -2315,8 +2316,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;
 
@@ -2345,7 +2346,7 @@ AlterDomainNotNull(List *names, bool notNull)
 					}
 				}
 			}
-			heap_endscan(scan);
+			storage_endscan(scan);
 			UnregisterSnapshot(snapshot);
 
 			/* Close each rel after processing, but keep lock */
@@ -2711,8 +2712,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;
 
@@ -2755,7 +2756,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 faa1812..e24ac9f 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"
@@ -447,9 +448,9 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
 
 		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);
 
@@ -469,7 +470,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
 			MemoryContextSwitchTo(oldcontext);
 		}
 
-		heap_endscan(scan);
+		storage_endscan(scan);
 		heap_close(pgclass, AccessShareLock);
 	}
 
@@ -1121,9 +1122,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;
@@ -1160,7 +1161,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 f1636a5..6ade9df 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 89e189f..5e9daea 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 e2bcf90..6ec17bd 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,20 @@ RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 
 	/* Start an index 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 +313,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 b7a2cbc..0a5098d 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -681,7 +681,7 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 Datum
 ExecFetchSlotTupleDatum(TupleTableSlot *slot)
 {
-	HeapTuple	tup;
+	StorageTuple	tup;
 	TupleDesc	tupdesc;
 
 	/* Fetch slot's contents in regular-physical-tuple form */
@@ -765,7 +765,7 @@ ExecHeapifySlot(TupleTableSlot *slot)
 TupleTableSlot *
 ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
 {
-	HeapTuple	newTuple;
+	StorageTuple	newTuple;
 	MemoryContext oldContext;
 
 	/*
@@ -1085,7 +1085,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;
@@ -1093,7 +1093,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 42a4ca9..79b74ee 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)
 {
@@ -597,7 +597,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 0ae5873..d94169c 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -3097,7 +3097,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,
@@ -3212,7 +3212,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 60a6cb0..7921025 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 "executor/execdebug.h"
 #include "executor/nodeBitmapHeapscan.h"
@@ -400,7 +401,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 			HeapTupleData heapTuple;
 
 			ItemPointerSet(&tid, page, offnum);
-			if (heap_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
+			if (storage_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
 									   &heapTuple, NULL, true))
 				scan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
 		}
@@ -685,7 +686,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);
 
 	if (node->tbmiterator)
 		tbm_end_iterate(node->tbmiterator);
@@ -764,7 +765,7 @@ ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 	/*
 	 * close heap scan
 	 */
-	heap_endscan(scanDesc);
+	storage_endscan(scanDesc);
 
 	/*
 	 * close the heap relation.
@@ -865,7 +866,7 @@ 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,
+	scanstate->ss.ss_currentScanDesc = storage_beginscan_bm(currentRelation,
 														 estate->es_snapshot,
 														 0,
 														 NULL);
@@ -1023,5 +1024,5 @@ ExecBitmapHeapInitializeWorker(BitmapHeapScanState *node, shm_toc *toc)
 	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 02f6c81..abec3a9 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 022d75b..5f4a294 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -45,7 +45,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);
 
 
@@ -252,7 +252,7 @@ gather_getnext(GatherState *gatherstate)
 	TupleTableSlot *outerTupleSlot;
 	TupleTableSlot *fslot = gatherstate->funnel_slot;
 	MemoryContext tupleContext = gatherstate->ps.ps_ExprContext->ecxt_per_tuple_memory;
-	HeapTuple	tup;
+	StorageTuple	tup;
 
 	while (gatherstate->nreaders > 0 || gatherstate->need_to_scan_locally)
 	{
@@ -295,7 +295,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;
@@ -303,7 +303,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 d20d466..ef9d9f1 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -44,7 +44,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 */
@@ -53,7 +53,7 @@ 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,
+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);
@@ -399,7 +399,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);
@@ -617,7 +617,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
@@ -689,12 +689,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;
 	MemoryContext oldContext;
 	MemoryContext tupleContext;
 
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index 5351cb8..f770bc4 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,7 +186,7 @@ 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
+		 * 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.
 		 */
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 638b17b..7330ff9 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 6a118d1..04f85e5 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -29,9 +29,9 @@
 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,
-				   HeapScanDesc scan);
+static StorageTuple tablesample_getnext(SampleScanState *scanstate);
+static bool SampleTupleVisible(StorageTuple tuple, OffsetNumber tupoffset,
+		HeapScanDesc scan); //hari
 
 /* ----------------------------------------------------------------
  *						Scan Support
@@ -47,7 +47,7 @@ static bool SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset,
 static TupleTableSlot *
 SampleNext(SampleScanState *node)
 {
-	HeapTuple	tuple;
+	StorageTuple	tuple;
 	TupleTableSlot *slot;
 
 	/*
@@ -66,7 +66,8 @@ SampleNext(SampleScanState *node)
 	if (tuple)
 		ExecStoreTuple(tuple,	/* tuple to store */
 					   slot,	/* slot to store in */
-					   node->ss.ss_currentScanDesc->rs_cbuf,	/* tuple's buffer */
+					   //harinode->ss.ss_currentScanDesc->rs_cbuf,	/* tuple's buffer */
+					   InvalidBuffer,
 					   false);	/* don't pfree this pointer */
 	else
 		ExecClearTuple(slot);
@@ -244,7 +245,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,7 +350,7 @@ tablesample_init(SampleScanState *scanstate)
 	if (scanstate->ss.ss_currentScanDesc == NULL)
 	{
 		scanstate->ss.ss_currentScanDesc =
-			heap_beginscan_sampling(scanstate->ss.ss_currentRelation,
+			storage_beginscan_sampling(scanstate->ss.ss_currentRelation,
 									scanstate->ss.ps.state->es_snapshot,
 									0, NULL,
 									scanstate->use_bulkread,
@@ -358,7 +359,7 @@ tablesample_init(SampleScanState *scanstate)
 	}
 	else
 	{
-		heap_rescan_set_params(scanstate->ss.ss_currentScanDesc, NULL,
+		storage_rescan_set_params(scanstate->ss.ss_currentScanDesc, NULL,
 							   scanstate->use_bulkread,
 							   allow_sync,
 							   scanstate->use_pagemode);
@@ -376,7 +377,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 +555,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 d4ac939..839d3a6 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,7 +69,7 @@ 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,
+		scandesc = storage_beginscan(node->ss.ss_currentRelation,
 								  estate->es_snapshot,
 								  0, NULL);
 		node->ss.ss_currentScanDesc = scandesc;
@@ -78,26 +78,7 @@ SeqNext(SeqScanState *node)
 	/*
 	 * 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,12 +251,12 @@ ExecEndSeqScan(SeqScanState *node)
 void
 ExecReScanSeqScan(SeqScanState *node)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 
 	scan = node->ss.ss_currentScanDesc;
 
 	if (scan != NULL)
-		heap_rescan(scan,		/* scan desc */
+		storage_rescan(scan,		/* scan desc */
 					NULL);		/* new scan keys */
 
 	ExecScanReScan((ScanState *) node);
@@ -322,7 +303,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);
 }
 
 /* ----------------------------------------------------------------
@@ -353,5 +334,5 @@ ExecSeqScanInitializeWorker(SeqScanState *node, shm_toc *toc)
 
 	pscan = shm_toc_lookup(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 80be460..d55a752 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -2091,7 +2091,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;
@@ -2159,7 +2159,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 afe231f..418c2a6 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -627,11 +627,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)
 	{
@@ -655,7 +655,7 @@ SPI_copytuple(HeapTuple tuple)
 }
 
 HeapTupleHeader
-SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc)
+SPI_returntuple(StorageTuple tuple, TupleDesc tupdesc)
 {
 	MemoryContext oldcxt;
 	HeapTupleHeader dtup;
@@ -686,7 +686,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)
 {
@@ -854,7 +854,7 @@ char *
 SPI_gettype(TupleDesc tupdesc, int fnumber)
 {
 	Oid			typoid;
-	HeapTuple	typeTuple;
+	StorageTuple	typeTuple;
 	char	   *result;
 
 	SPI_result = 0;
@@ -962,7 +962,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);
@@ -1683,7 +1683,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);
@@ -1714,8 +1714,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 81964d7..81d0adc 100644
--- a/src/backend/executor/tqueue.c
+++ b/src/backend/executor/tqueue.c
@@ -192,12 +192,12 @@ static void TQSendRecordInfo(TQueueDestReceiver *tqueue, int32 typmod,
 				 TupleDesc tupledesc);
 static void TupleQueueHandleControlMessage(TupleQueueReader *reader,
 							   Size nbytes, char *data);
-static HeapTuple TupleQueueHandleDataMessage(TupleQueueReader *reader,
+static StorageTuple TupleQueueHandleDataMessage(TupleQueueReader *reader,
 							Size nbytes, HeapTupleHeader data);
-static HeapTuple TQRemapTuple(TupleQueueReader *reader,
+static StorageTuple TQRemapTuple(TupleQueueReader *reader,
 			 TupleDesc tupledesc,
 			 TupleRemapInfo **field_remapinfo,
-			 HeapTuple tuple);
+			 StorageTuple tuple);
 static Datum TQRemap(TupleQueueReader *reader, TupleRemapInfo *remapinfo,
 		Datum value, bool *changed);
 static Datum TQRemapArray(TupleQueueReader *reader, ArrayRemapInfo *remapinfo,
@@ -682,7 +682,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)
 {
 	shm_mq_result result;
@@ -737,7 +737,7 @@ TupleQueueReaderNext(TupleQueueReader *reader, bool nowait, bool *done)
 /*
  * Handle a data message - that is, a tuple - from the remote side.
  */
-static HeapTuple
+static StorageTuple
 TupleQueueHandleDataMessage(TupleQueueReader *reader,
 							Size nbytes,
 							HeapTupleHeader data)
@@ -766,11 +766,11 @@ TupleQueueHandleDataMessage(TupleQueueReader *reader,
 /*
  * Copy the given tuple, remapping any transient typmods contained in it.
  */
-static HeapTuple
+static StorageTuple
 TQRemapTuple(TupleQueueReader *reader,
 			 TupleDesc tupledesc,
 			 TupleRemapInfo **field_remapinfo,
-			 HeapTuple tuple)
+			 StorageTuple tuple)
 {
 	Datum	   *values;
 	bool	   *isnull;
@@ -1128,7 +1128,7 @@ TupleQueueHandleControlMessage(TupleQueueReader *reader, Size nbytes,
 static TupleRemapInfo *
 BuildTupleRemapInfo(Oid typid, MemoryContext mycontext)
 {
-	HeapTuple	tup;
+	StorageTuple	tup;
 	Form_pg_type typ;
 
 	/* This is recursive, so it could be driven to stack overflow. */
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 776b1c0..fec203d 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1882,9 +1882,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;
@@ -1911,7 +1911,7 @@ get_database_list(void)
 		MemoryContextSwitchTo(oldcxt);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	CommitTransactionCommand();
@@ -2042,13 +2042,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;
@@ -2134,7 +2134,7 @@ do_autovacuum(void)
 		}
 	}
 
-	heap_endscan(relScan);
+	storage_endscan(relScan);
 
 	/* second pass: check TOAST tables */
 	ScanKeyInit(&key,
@@ -2142,8 +2142,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;
@@ -2189,7 +2189,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 accf302..74113a7 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 6c89442..fca56d4 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 d03984a..d71d673 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(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 eb6960d..82c042a 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -21,6 +21,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"
@@ -1208,10 +1209,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 cdd45ef..4cddd73 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -106,40 +106,16 @@ 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);
-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_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 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 Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 			int options, BulkInsertState bistate);
-extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
-					   Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
-					   bool *all_dead, bool first_call);
-extern bool heap_hot_search(ItemPointer tid, Relation relation,
-				Snapshot snapshot, bool *all_dead);
+
 extern void setLastTid(const ItemPointer tid);
 
 extern BulkInsertState GetBulkInsertState(void);
@@ -147,8 +123,6 @@ extern void FreeBulkInsertState(BulkInsertState);
 extern void ReleaseBulkInsertStatePin(BulkInsertState bistate);
 
 extern void heap_inplace_update(Relation relation, HeapTuple tuple);
-extern bool heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
-				  TransactionId cutoff_multi);
 extern bool heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
 						MultiXactId cutoff_multi, Buffer buf);
 extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
@@ -158,8 +132,6 @@ extern void simple_heap_delete(Relation relation, ItemPointer tid);
 extern void simple_heap_update(Relation relation, ItemPointer otid,
 				   HeapTuple tup);
 
-extern void heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
-
 /* 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/heapam_common.h b/src/include/access/heapam_common.h
index 799b4ed..66a96d7 100644
--- a/src/include/access/heapam_common.h
+++ b/src/include/access/heapam_common.h
@@ -107,6 +107,9 @@ static const int MultiXactStatusLock[MaxMultiXactStatus + 1] =
 /* Get the LOCKMODE for a given MultiXactStatus */
 #define LOCKMODE_from_mxstatus(status) \
 			(tupleLockExtraInfo[TUPLOCK_from_mxstatus((status))].hwlock)
+
+extern bool	synchronize_seqscans;
+
 extern HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
 					TransactionId xid, CommandId cid, int options);
 
@@ -136,6 +139,11 @@ extern void MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 in
 extern MultiXactStatus get_mxact_status_for_lock(LockTupleMode mode, bool is_update);
 
 extern void heap_inplace_update(Relation relation, HeapTuple tuple);
+
+extern bool heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
+						MultiXactId cutoff_multi, Buffer buf);
+extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
+
 extern bool heap_acquire_tuplock(Relation relation, ItemPointer tid,
 					 LockTupleMode mode, LockWaitPolicy wait_policy,
 					 bool *have_tuple_lock);
diff --git a/src/include/access/storageam.h b/src/include/access/storageam.h
index 9502c92..507af71 100644
--- a/src/include/access/storageam.h
+++ b/src/include/access/storageam.h
@@ -19,6 +19,7 @@
 
 /* A physical tuple coming from a storage AM scan */
 typedef void *StorageTuple;
+typedef void *StorageScanDesc;
 
 typedef union tuple_data
 {
@@ -36,6 +37,34 @@ typedef enum tuple_data_flags
 	CTID
 } tuple_data_flags;
 
+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,
@@ -44,6 +73,15 @@ 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 bool storage_freeze_tuple(Relation rel, HeapTupleHeader tuple, TransactionId cutoff_xid,
+				  TransactionId cutoff_multi);
+
 extern HTSU_Result storage_lock_tuple(Relation relation, ItemPointer tid, StorageTuple *stuple,
 				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 				bool follow_updates,
@@ -72,10 +110,6 @@ extern bool storage_tuple_is_heaponly(Relation relation, StorageTuple tuple);
 
 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/executor/functions.h b/src/include/executor/functions.h
index 718d894..7f9bef1 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 acade7e..d466c99 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,9 +117,9 @@ 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,
+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);
@@ -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 a717ac6..4156767 100644
--- a/src/include/executor/tqueue.h
+++ b/src/include/executor/tqueue.h
@@ -27,7 +27,7 @@ extern DestReceiver *CreateTupleQueueDestReceiver(shm_mq_handle *handle);
 extern TupleQueueReader *CreateTupleQueueReader(shm_mq_handle *handle,
 					   TupleDesc tupledesc);
 extern void DestroyTupleQueueReader(TupleQueueReader *reader);
-extern HeapTuple TupleQueueReaderNext(TupleQueueReader *reader,
+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 951af2a..ab0e091 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -229,7 +229,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.7.4.windows.1

0001-Change-Create-Access-method-to-include-storage-handl.patchapplication/octet-stream; name=0001-Change-Create-Access-method-to-include-storage-handl.patchDownload
From 1a7186e8d05b2efd73d317d7db8f3091bd48f9bf Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Tue, 29 Aug 2017 19:45:30 +1000
Subject: [PATCH 1/7] 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              |  4 ++++
 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, 63 insertions(+), 11 deletions(-)

diff --git a/src/backend/commands/amcmds.c b/src/backend/commands/amcmds.c
index 7e0a9aa..33079c1 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 c303818..d4ac340 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
@@ -5172,16 +5174,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 be79353..0a7e0a3 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 e021f5b..2c3e33c 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
@@ -83,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 d820b56..e3c9c19 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3870,6 +3870,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 = 336  (  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 = 337  (  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 ffdb452..ea352fa 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 fcf8bd7..3bc9607 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1711,11 +1711,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 2945966..f1f58a3 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1153,15 +1153,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.7.4.windows.1

0002-Storage-AM-API-hooks-and-related-functions.patchapplication/octet-stream; name=0002-Storage-AM-API-hooks-and-related-functions.patchDownload
From 1a9c4a4a66e75fdddbc88782d4abbf81fbea3a28 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Wed, 30 Aug 2017 12:41:15 +1000
Subject: [PATCH 2/7] Storage AM API hooks and related functions

---
 src/backend/access/heap/Makefile         |   3 +-
 src/backend/access/heap/heapam_storage.c |  58 ++++++++
 src/backend/access/heap/storageamapi.c   | 103 ++++++++++++++
 src/include/access/htup.h                |  21 +++
 src/include/access/storageamapi.h        | 237 +++++++++++++++++++++++++++++++
 src/include/catalog/pg_proc.h            |   5 +
 src/include/nodes/nodes.h                |   1 +
 src/include/utils/tqual.h                |   9 --
 8 files changed, 427 insertions(+), 10 deletions(-)
 create mode 100644 src/backend/access/heap/heapam_storage.c
 create mode 100644 src/backend/access/heap/storageamapi.c
 create mode 100644 src/include/access/storageamapi.h

diff --git a/src/backend/access/heap/Makefile b/src/backend/access/heap/Makefile
index b83d496..02a3909 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 storageamapi.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 0000000..88827e7
--- /dev/null
+++ b/src/backend/access/heap/heapam_storage.c
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/heapam.h"
+#include "access/heapam_xlog.h"
+#include "access/hio.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/relscan.h"
+#include "access/storageamapi.h"
+#include "access/subtrans.h"
+#include "access/tuptoaster.h"
+#include "access/valid.h"
+#include "access/visibilitymap.h"
+#include "access/xloginsert.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/bufmgr.h"
+#include "storage/lmgr.h"
+#include "storage/predicate.h"
+#include "storage/procarray.h"
+#include "storage/smgr.h"
+#include "storage/spin.h"
+#include "utils/builtins.h"
+#include "utils/inval.h"
+#include "utils/rel.h"
+#include "utils/tqual.h"
+
+
+Datum
+heapam_storage_handler(PG_FUNCTION_ARGS)
+{
+	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
+
+
+	PG_RETURN_POINTER(amroutine);
+}
diff --git a/src/backend/access/heap/storageamapi.c b/src/backend/access/heap/storageamapi.c
new file mode 100644
index 0000000..def2029
--- /dev/null
+++ b/src/backend/access/heap/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/htup.h b/src/include/access/htup.h
index 61b3e68..6459435 100644
--- a/src/include/access/htup.h
+++ b/src/include/access/htup.h
@@ -26,6 +26,27 @@ typedef struct MinimalTupleData MinimalTupleData;
 
 typedef MinimalTupleData *MinimalTuple;
 
+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 */
+
+	END_OF_VISIBILITY
+} tuple_visibility_type;
+
+/* 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;
 
 /*
  * HeapTupleData is an in-memory data structure that points to a tuple.
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
new file mode 100644
index 0000000..95fe028
--- /dev/null
+++ b/src/include/access/storageamapi.h
@@ -0,0 +1,237 @@
+/*---------------------------------------------------------------------
+ *
+ * storageamapi.h
+ *		API for Postgres storage access methods
+ *
+ * Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * src/include/access/storageamapi.h
+ *---------------------------------------------------------------------
+ */
+#ifndef STORAGEAMAPI_H
+#define STORAGEAMAPI_H
+
+#include "access/htup.h"
+#include "access/heapam.h"
+#include "access/sdir.h"
+#include "access/skey.h"
+#include "executor/tuptable.h"
+#include "utils/relcache.h"
+#include "utils/snapshot.h"
+
+/* A physical tuple coming from a storage AM scan */
+typedef void *StorageTuple;
+
+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;
+
+
+typedef HeapScanDesc (*scan_begin_hook) (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 (*scan_setlimits_hook) (HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
+
+/* must return a TupleTableSlot? */
+typedef StorageTuple (*scan_getnext_hook) (HeapScanDesc scan,
+											ScanDirection direction);
+
+typedef TupleTableSlot* (*scan_getnext_slot_hook) (HeapScanDesc scan,
+										ScanDirection direction, TupleTableSlot *slot);
+
+typedef void (*scan_end_hook) (HeapScanDesc scan);
+
+
+typedef void (*scan_getpage_hook) (HeapScanDesc scan, BlockNumber page);
+typedef void (*scan_rescan_hook) (HeapScanDesc scan, ScanKey key, bool set_params,
+					bool allow_strat, bool allow_sync, bool allow_pagemode);
+typedef void (*scan_update_snapshot_hook) (HeapScanDesc scan, Snapshot snapshot);
+
+typedef bool (*hot_search_buffer_hook) (ItemPointer tid, Relation relation,
+					   Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
+					   bool *all_dead, bool first_call);
+
+typedef Oid (*tuple_insert_hook) (Relation relation,
+									  TupleTableSlot *tupslot,
+									  CommandId cid,
+									  int options,
+									  BulkInsertState bistate);
+
+typedef HTSU_Result (*tuple_delete_hook) (Relation relation,
+											  ItemPointer tid,
+											  CommandId cid,
+											  Snapshot crosscheck,
+											  bool wait,
+											  HeapUpdateFailureData *hufd);
+
+typedef HTSU_Result (*tuple_update_hook) (Relation relation,
+											  ItemPointer otid,
+											  TupleTableSlot *slot,
+											  CommandId cid,
+											  Snapshot crosscheck,
+											  bool wait,
+											  HeapUpdateFailureData *hufd,
+											  LockTupleMode *lockmode);
+
+typedef bool (*tuple_fetch_hook) (Relation relation,
+									ItemPointer tid,
+									Snapshot snapshot,
+									StorageTuple *tuple,
+									Buffer *userbuf,
+									bool keep_buf,
+									Relation stats_relation);
+
+typedef HTSU_Result (*tuple_lock_hook) (Relation relation,
+											ItemPointer tid,
+											StorageTuple *tuple,
+											CommandId cid,
+											LockTupleMode mode,
+											LockWaitPolicy wait_policy,
+											bool follow_update,
+											Buffer *buffer,
+											HeapUpdateFailureData *hufd);
+
+typedef void (*multi_insert_hook) (Relation relation, HeapTuple *tuples, int ntuples,
+				  CommandId cid, int options, BulkInsertState bistate);
+
+typedef bool (*tuple_freeze_hook) (HeapTupleHeader tuple, TransactionId cutoff_xid,
+				  TransactionId cutoff_multi);
+
+typedef void (*tuple_get_latest_tid_hook) (Relation relation,
+											Snapshot snapshot,
+											ItemPointer tid);
+
+typedef tuple_data (*get_tuple_data_hook) (StorageTuple tuple, tuple_data_flags flags);
+
+typedef StorageTuple (*tuple_from_datum_hook) (Datum data, Oid tableoid);
+
+typedef bool (*tuple_is_heaponly_hook) (StorageTuple tuple);
+
+typedef void (*slot_store_tuple_hook) (TupleTableSlot *slot,
+										   StorageTuple tuple,
+										   bool shouldFree,
+										   bool minumumtuple);
+typedef void (*slot_clear_tuple_hook) (TupleTableSlot *slot);
+typedef Datum (*slot_getattr_hook) (TupleTableSlot *slot,
+										int attnum, bool *isnull);
+typedef void (*slot_virtualize_tuple_hook) (TupleTableSlot *slot, int16 upto);
+
+typedef HeapTuple (*slot_tuple_hook) (TupleTableSlot *slot, bool palloc_copy);
+typedef MinimalTuple (*slot_min_tuple_hook) (TupleTableSlot *slot, bool palloc_copy);
+
+typedef void (*slot_update_tableoid_hook) (TupleTableSlot *slot, Oid tableoid);
+
+typedef void (*speculative_finish_hook) (Relation rel,
+											 TupleTableSlot *slot);
+typedef void (*speculative_abort_hook) (Relation rel,
+											TupleTableSlot *slot);
+
+typedef void (*relation_sync_hook) (Relation relation);
+
+typedef bool (*snapshot_satisfies_hook) (StorageTuple htup, Snapshot snapshot, Buffer buffer);
+typedef HTSU_Result (*snapshot_satisfies_update_hook) (StorageTuple htup, CommandId curcid, Buffer buffer);
+typedef HTSV_Result (*snapshot_satisfies_vacuum_hook) (StorageTuple htup, TransactionId OldestXmin, Buffer buffer);
+
+typedef struct StorageSlotAmRoutine
+{
+	/* Operations on TupleTableSlot */
+	slot_store_tuple_hook	slot_store_tuple;
+	slot_virtualize_tuple_hook	slot_virtualize_tuple;
+	slot_clear_tuple_hook	slot_clear_tuple;
+	slot_getattr_hook	slot_getattr;
+	slot_tuple_hook     slot_tuple;
+	slot_min_tuple_hook      slot_min_tuple;
+	slot_update_tableoid_hook slot_update_tableoid;
+} StorageSlotAmRoutine;
+
+typedef StorageSlotAmRoutine* (*slot_storageam_hook) (void);
+
+/*
+ * 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;
+
+	/* Operations on relation scans */
+	scan_begin_hook scan_begin;
+	scan_setlimits_hook scansetlimits;
+	scan_getnext_hook scan_getnext;
+	scan_getnext_slot_hook scan_getnextslot;
+	scan_end_hook scan_end;
+	scan_getpage_hook scan_getpage;
+	scan_rescan_hook scan_rescan;
+	scan_update_snapshot_hook scan_update_snapshot;
+	hot_search_buffer_hook hot_search_buffer; /* heap_hot_search_buffer */
+
+	// heap_sync_function		heap_sync;		/* heap_sync */
+	/* not implemented */
+	//	parallelscan_estimate_function	parallelscan_estimate;	/* heap_parallelscan_estimate */
+	//	parallelscan_initialize_function parallelscan_initialize;	/* heap_parallelscan_initialize */
+	//	parallelscan_begin_function	parallelscan_begin;	/* heap_beginscan_parallel */
+
+	/* Operations on physical tuples */
+	tuple_insert_hook		tuple_insert;	/* heap_insert */
+	tuple_update_hook		tuple_update;	/* heap_update */
+	tuple_delete_hook		tuple_delete;	/* heap_delete */
+	tuple_fetch_hook		tuple_fetch;	/* heap_fetch */
+	tuple_lock_hook			tuple_lock;		/* heap_lock_tuple */
+	multi_insert_hook		multi_insert;	/* heap_multi_insert */
+	tuple_freeze_hook       tuple_freeze;	/* heap_freeze_tuple */
+	tuple_get_latest_tid_hook tuple_get_latest_tid; /* heap_get_latest_tid */
+
+	get_tuple_data_hook		get_tuple_data;
+	tuple_is_heaponly_hook	tuple_is_heaponly;
+	tuple_from_datum_hook	tuple_from_datum;
+
+
+	slot_storageam_hook	slot_storageam;
+
+	/*
+	 * 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:
+	 */
+	speculative_finish_hook	speculative_finish;
+	speculative_abort_hook	speculative_abort;
+
+
+	relation_sync_hook	relation_sync;	/* heap_sync */
+
+	snapshot_satisfies_hook snapshot_satisfies[END_OF_VISIBILITY];
+	snapshot_satisfies_update_hook snapshot_satisfiesUpdate; /* HeapTupleSatisfiesUpdate */
+	snapshot_satisfies_vacuum_hook snapshot_satisfiesVacuum; /* HeapTupleSatisfiesVacuum */
+} 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_proc.h b/src/include/catalog/pg_proc.h
index e3c9c19..03cb703 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -549,6 +549,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 27bd4f3..108df9b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -496,6 +496,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;
diff --git a/src/include/utils/tqual.h b/src/include/utils/tqual.h
index 9a3b56e..de75e01 100644
--- a/src/include/utils/tqual.h
+++ b/src/include/utils/tqual.h
@@ -45,15 +45,6 @@ extern PGDLLIMPORT SnapshotData CatalogSnapshotData;
 #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,
-- 
2.7.4.windows.1

0003-Adding-storageam-hanlder-to-relation-structure.patchapplication/octet-stream; name=0003-Adding-storageam-hanlder-to-relation-structure.patchDownload
From 1079b9ffe466449b917996117157979972c2bccd Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Wed, 30 Aug 2017 12:49:46 +1000
Subject: [PATCH 3/7] Adding storageam hanlder to relation structure

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

diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index b8e3780..40cbc67 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -34,6 +34,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"
@@ -1368,10 +1369,26 @@ 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);
@@ -1868,6 +1885,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
@@ -2027,6 +2109,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
 	 */
 	if (IsBootstrapProcessingMode())
@@ -2354,6 +2441,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)
@@ -3368,6 +3457,13 @@ 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.
 	 *
@@ -3889,6 +3985,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);
 
@@ -5607,6 +5715,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 4bc61e5..82d8a53 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -161,6 +161,12 @@ typedef struct RelationData
 	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)
 	 *
 	 * Note: only default support procs for each opclass are cached, namely
@@ -428,6 +434,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 3c53cef..03d996f 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -71,6 +71,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.7.4.windows.1

0004-Adding-tuple-visibility-function-to-storage-AM.patchapplication/octet-stream; name=0004-Adding-tuple-visibility-function-to-storage-AM.patchDownload
From 4f0abf567c359ce0003410e246ba098fff09d3fe Mon Sep 17 00:00:00 2001
From: Hari Babu Kommi <kommi.haribabu@gmail.com>
Date: Fri, 8 Sep 2017 15:38:28 +1000
Subject: [PATCH 4/7] 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 heapam storage is moved into heapam_common.c
and heapam_common.h files.
---
 contrib/pg_visibility/pg_visibility.c       |   10 +-
 contrib/pgrowlocks/pgrowlocks.c             |    3 +-
 contrib/pgstattuple/pgstatapprox.c          |    7 +-
 contrib/pgstattuple/pgstattuple.c           |    3 +-
 src/backend/access/heap/Makefile            |    3 +-
 src/backend/access/heap/heapam.c            |   23 +-
 src/backend/access/heap/heapam_common.c     |  162 +++
 src/backend/access/heap/heapam_storage.c    | 1622 ++++++++++++++++++++++++
 src/backend/access/heap/pruneheap.c         |    4 +-
 src/backend/access/heap/rewriteheap.c       |    1 +
 src/backend/access/index/genam.c            |    4 +-
 src/backend/catalog/index.c                 |    4 +-
 src/backend/commands/analyze.c              |    2 +-
 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/backend/utils/time/tqual.c              | 1809 ---------------------------
 src/include/access/heapam_common.h          |   96 ++
 src/include/access/htup.h                   |    3 +-
 src/include/storage/bufmgr.h                |    6 +-
 src/include/utils/snapshot.h                |    2 +-
 src/include/utils/tqual.h                   |   66 +-
 29 files changed, 1964 insertions(+), 1907 deletions(-)
 create mode 100644 src/backend/access/heap/heapam_common.c
 delete mode 100644 src/backend/utils/time/tqual.c
 create mode 100644 src/include/access/heapam_common.h

diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c
index 2cc9575..10d47cd 100644
--- a/contrib/pg_visibility/pg_visibility.c
+++ b/contrib/pg_visibility/pg_visibility.c
@@ -51,7 +51,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 +656,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 +681,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 +739,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 eabca65..5f076ef 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,7 +150,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
 		/* must hold a buffer lock to call HeapTupleSatisfiesUpdate */
 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 
-		htsu = HeapTupleSatisfiesUpdate(tuple,
+		htsu = rel->rd_stamroutine->snapshot_satisfiesUpdate(tuple,
 										GetCurrentCommandId(false),
 										scan->rs_cbuf);
 		xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
diff --git a/contrib/pgstattuple/pgstatapprox.c b/contrib/pgstattuple/pgstatapprox.c
index 5bf0613..284eabc 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 7a91cc3..f7b68a8 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 02a3909..e6bc18e 100644
--- a/src/backend/access/heap/Makefile
+++ b/src/backend/access/heap/Makefile
@@ -12,7 +12,8 @@ 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 storageamapi.o \
+OBJS = heapam.o heapam_common.o heapam_storage.o hio.o \
+	pruneheap.o rewriteheap.o storageamapi.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 d20f038..c21d6f8 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -40,11 +40,13 @@
 
 #include "access/bufmask.h"
 #include "access/heapam.h"
+#include "access/heapam_common.h"
 #include "access/heapam_xlog.h"
 #include "access/hio.h"
 #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 +440,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 +655,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);
 
@@ -1950,7 +1953,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 +2100,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 +2274,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;
@@ -3095,7 +3098,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)
 	{
@@ -3206,7 +3209,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;
 	}
 
@@ -3666,7 +3669,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);
@@ -3847,7 +3850,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;
 	}
 
@@ -4598,7 +4601,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_common.c b/src/backend/access/heap/heapam_common.c
new file mode 100644
index 0000000..502f6db
--- /dev/null
+++ b/src/backend/access/heap/heapam_common.c
@@ -0,0 +1,162 @@
+/*-------------------------------------------------------------------------
+ *
+ * heapam_common.c
+ *	  heapam 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/heap/heapam_common.c
+ *
+ *
+ * NOTES
+ *	  This file contains the storage_ routines which implement
+ *	  the POSTGRES storage access method used for all POSTGRES
+ *	  relations.
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/heapam_common.h"
+#include "access/htup_details.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);
+}
+
+
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 88827e7..1bd4bfa 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -21,6 +21,7 @@
 #include "postgres.h"
 
 #include "access/heapam.h"
+#include "access/heapam_common.h"
 #include "access/heapam_xlog.h"
 #include "access/hio.h"
 #include "access/htup_details.h"
@@ -47,6 +48,1617 @@
 #include "utils/rel.h"
 #include "utils/tqual.h"
 
+/* local functions */
+static bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
+
+/*-------------------------------------------------------------------------
+ *
+ * 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
+ * has now committed or aborted (and it is safe to set the hint bits).
+ * If the hint bits are changed, MarkBufferDirtyHint is called on
+ * the passed-in buffer.  The caller must hold not only a pin, but at least
+ * shared buffer content lock on the buffer containing the tuple.
+ *
+ * NOTE: When using a non-MVCC snapshot, we must check
+ * TransactionIdIsInProgress (which looks in the PGXACT array)
+ * before TransactionIdDidCommit/TransactionIdDidAbort (which look in
+ * pg_xact).  Otherwise we have a race condition: we might decide that a
+ * just-committed transaction crashed, because none of the tests succeed.
+ * xact.c is careful to record commit/abort in pg_xact before it unsets
+ * MyPgXact->xid in the PGXACT array.  That fixes that problem, but it
+ * also means there is a window where TransactionIdIsInProgress and
+ * TransactionIdDidCommit will both return true.  If we check only
+ * TransactionIdDidCommit, we could consider a tuple committed when a
+ * later GetSnapshotData call will still think the originating transaction
+ * is in progress, which leads to application-level inconsistency.  The
+ * upshot is that we gotta check TransactionIdIsInProgress first in all
+ * code paths, except for a few cases where we are looking at
+ * subtransactions of our own main transaction and so there can't be any
+ * race condition.
+ *
+ * When using an MVCC snapshot, we rely on XidInMVCCSnapshot rather than
+ * TransactionIdIsInProgress, but the logic is otherwise the same: do not
+ * check pg_xact until after deciding that the xact is no longer in progress.
+ *
+ *
+ * Summary of visibility functions:
+ *
+ *	 HeapTupleSatisfiesMVCC()
+ *		  visible to supplied snapshot, excludes current command
+ *	 HeapTupleSatisfiesUpdate()
+ *		  visible to instant snapshot, with user-supplied command
+ *		  counter and more complex result
+ *	 HeapTupleSatisfiesSelf()
+ *		  visible to instant snapshot and current command
+ *	 HeapTupleSatisfiesDirty()
+ *		  like HeapTupleSatisfiesSelf(), but includes open transactions
+ *	 HeapTupleSatisfiesVacuum()
+ *		  visible to any running transaction, used by VACUUM
+ *   HeapTupleSatisfiesNonVacuumable()
+ *        Snapshot-style API for HeapTupleSatisfiesVacuum
+ *	 HeapTupleSatisfiesToast()
+ *		  visible unless part of interrupted vacuum, used for TOAST
+ *	 HeapTupleSatisfiesAny()
+ *		  all tuples are visible
+ *
+ * -------------------------------------------------------------------------
+ */
+
+/*
+ * HeapTupleSatisfiesSelf
+ *		True iff heap tuple is valid "for itself".
+ *
+ *	Here, we consider the effects of:
+ *		all committed transactions (as of the current instant)
+ *		previous commands of this transaction
+ *		changes made by the current command
+ *
+ * Note:
+ *		Assumes heap tuple is valid.
+ *
+ * The satisfaction of "itself" requires the following:
+ *
+ * ((Xmin == my-transaction &&				the row was updated by the current transaction, and
+ *		(Xmax is null						it was not deleted
+ *		 [|| Xmax != my-transaction)])			[or it was deleted by another transaction]
+ * ||
+ *
+ * (Xmin is committed &&					the row was modified by a committed transaction, and
+ *		(Xmax is null ||					the row has not been deleted, or
+ *			(Xmax != my-transaction &&			the row was deleted by another transaction
+ *			 Xmax is not committed)))			that has not been committed
+ */
+static bool
+HeapTupleSatisfiesSelf(StorageTuple stup, Snapshot snapshot, Buffer buffer)
+{
+	HeapTuple htup = (HeapTuple)stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return false;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return false;
+			if (!TransactionIdIsInProgress(xvac))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (TransactionIdIsInProgress(xvac))
+					return false;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return true;
+
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
+				return true;
+
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleGetUpdateXid(tuple);
+
+				/* not LOCKED_ONLY, so it has to have an xmax */
+				Assert(TransactionIdIsValid(xmax));
+
+				/* updating subtransaction must have aborted */
+				if (!TransactionIdIsCurrentTransactionId(xmax))
+					return true;
+				else
+					return false;
+			}
+
+			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+			{
+				/* deleting subtransaction must have aborted */
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+				return true;
+			}
+
+			return false;
+		}
+		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+			return false;
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return false;
+		}
+	}
+
+	/* by here, the inserting transaction has committed */
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
+		return true;
+
+	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+		return false;			/* updated by other */
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+
+		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 true;
+		if (TransactionIdDidCommit(xmax))
+			return false;
+		/* it must have aborted or crashed */
+		return true;
+	}
+
+	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+		return false;
+	}
+
+	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+		return true;
+
+	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		/* it must have aborted or crashed */
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return true;
+	}
+
+	/* xmax transaction committed */
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	{
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return true;
+	}
+
+	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+				HeapTupleHeaderGetRawXmax(tuple));
+	return false;
+}
+
+/*
+ * HeapTupleSatisfiesAny
+ *		Dummy "satisfies" routine: any tuple satisfies SnapshotAny.
+ */
+static bool
+HeapTupleSatisfiesAny(StorageTuple stup, Snapshot snapshot, Buffer buffer)
+{
+	return true;
+}
+
+/*
+ * HeapTupleSatisfiesToast
+ *		True iff heap tuple is valid as a TOAST row.
+ *
+ * This is a simplified version that only checks for VACUUM moving conditions.
+ * It's appropriate for TOAST usage because TOAST really doesn't want to do
+ * its own time qual checks; if you can see the main table row that contains
+ * a TOAST reference, you should be able to see the TOASTed value.  However,
+ * vacuuming a TOAST table is independent of the main table, and in case such
+ * a vacuum fails partway through, we'd better do this much checking.
+ *
+ * Among other things, this means you can't do UPDATEs of rows in a TOAST
+ * table.
+ */
+static bool
+HeapTupleSatisfiesToast(StorageTuple stup, Snapshot snapshot,
+						Buffer buffer)
+{
+	HeapTuple htup = (HeapTuple)stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return false;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return false;
+			if (!TransactionIdIsInProgress(xvac))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (TransactionIdIsInProgress(xvac))
+					return false;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+			}
+		}
+
+		/*
+		 * An invalid Xmin can be left behind by a speculative insertion that
+		 * is canceled by super-deleting the tuple.  This also applies to
+		 * TOAST tuples created during speculative insertion.
+		 */
+		else if (!TransactionIdIsValid(HeapTupleHeaderGetXmin(tuple)))
+			return false;
+	}
+
+	/* otherwise assume the tuple is valid for TOAST. */
+	return true;
+}
+
+/*
+ * HeapTupleSatisfiesUpdate
+ *
+ *	This function returns a more detailed result code than most of the
+ *	functions in this file, since UPDATE needs to know more than "is it
+ *	visible?".  It also allows for user-supplied CommandId rather than
+ *	relying on CurrentCommandId.
+ *
+ *	The possible return codes are:
+ *
+ *	HeapTupleInvisible: the tuple didn't exist at all when the scan started,
+ *	e.g. it was created by a later CommandId.
+ *
+ *	HeapTupleMayBeUpdated: The tuple is valid and visible, so it may be
+ *	updated.
+ *
+ *	HeapTupleSelfUpdated: The tuple was updated by the current transaction,
+ *	after the current scan started.
+ *
+ *	HeapTupleUpdated: The tuple was updated by a committed transaction.
+ *
+ *	HeapTupleBeingUpdated: The tuple is being updated by an in-progress
+ *	transaction other than the current transaction.  (Note: this includes
+ *	the case where the tuple is share-locked by a MultiXact, even if the
+ *	MultiXact includes the current transaction.  Callers that want to
+ *	distinguish that case must test for it themselves.)
+ */
+static HTSU_Result
+HeapTupleSatisfiesUpdate(StorageTuple stup, CommandId curcid,
+						 Buffer buffer)
+{
+	HeapTuple htup = (HeapTuple)stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return HeapTupleInvisible;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return HeapTupleInvisible;
+			if (!TransactionIdIsInProgress(xvac))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return HeapTupleInvisible;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (TransactionIdIsInProgress(xvac))
+					return HeapTupleInvisible;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return HeapTupleInvisible;
+				}
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (HeapTupleHeaderGetCmin(tuple) >= curcid)
+				return HeapTupleInvisible;	/* inserted after scan started */
+
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return HeapTupleMayBeUpdated;
+
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleHeaderGetRawXmax(tuple);
+
+				/*
+				 * Careful here: even though this tuple was created by our own
+				 * transaction, it might be locked by other transactions, if
+				 * the original version was key-share locked when we updated
+				 * it.
+				 */
+
+				if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+				{
+					if (MultiXactIdIsRunning(xmax, true))
+						return HeapTupleBeingUpdated;
+					else
+						return HeapTupleMayBeUpdated;
+				}
+
+				/*
+				 * If the locker is gone, then there is nothing of interest
+				 * left in this Xmax; otherwise, report the tuple as
+				 * locked/updated.
+				 */
+				if (!TransactionIdIsInProgress(xmax))
+					return HeapTupleMayBeUpdated;
+				return HeapTupleBeingUpdated;
+			}
+
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleGetUpdateXid(tuple);
+
+				/* not LOCKED_ONLY, so it has to have an xmax */
+				Assert(TransactionIdIsValid(xmax));
+
+				/* deleting subtransaction must have aborted */
+				if (!TransactionIdIsCurrentTransactionId(xmax))
+				{
+					if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
+											 false))
+						return HeapTupleBeingUpdated;
+					return HeapTupleMayBeUpdated;
+				}
+				else
+				{
+					if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+						return HeapTupleSelfUpdated;	/* updated after scan
+														 * started */
+					else
+						return HeapTupleInvisible;	/* updated before scan
+													 * started */
+				}
+			}
+
+			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+			{
+				/* deleting subtransaction must have aborted */
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+				return HeapTupleMayBeUpdated;
+			}
+
+			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+				return HeapTupleSelfUpdated;	/* updated after scan started */
+			else
+				return HeapTupleInvisible;	/* updated before scan started */
+		}
+		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+			return HeapTupleInvisible;
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return HeapTupleInvisible;
+		}
+	}
+
+	/* by here, the inserting transaction has committed */
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
+		return HeapTupleMayBeUpdated;
+
+	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return HeapTupleMayBeUpdated;
+		return HeapTupleUpdated;	/* updated by other */
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		if (HEAP_LOCKED_UPGRADED(tuple->t_infomask))
+			return HeapTupleMayBeUpdated;
+
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+		{
+			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), true))
+				return HeapTupleBeingUpdated;
+
+			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
+			return HeapTupleMayBeUpdated;
+		}
+
+		xmax = HeapTupleGetUpdateXid(tuple);
+		if (!TransactionIdIsValid(xmax))
+		{
+			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+				return HeapTupleBeingUpdated;
+		}
+
+		/* not LOCKED_ONLY, so it has to have an xmax */
+		Assert(TransactionIdIsValid(xmax));
+
+		if (TransactionIdIsCurrentTransactionId(xmax))
+		{
+			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+				return HeapTupleSelfUpdated;	/* updated after scan started */
+			else
+				return HeapTupleInvisible;	/* updated before scan started */
+		}
+
+		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+			return HeapTupleBeingUpdated;
+
+		if (TransactionIdDidCommit(xmax))
+			return HeapTupleUpdated;
+
+		/*
+		 * By here, the update in the Xmax is either aborted or crashed, but
+		 * what about the other members?
+		 */
+
+		if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+		{
+			/*
+			 * There's no member, even just a locker, alive anymore, so we can
+			 * mark the Xmax as invalid.
+			 */
+			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+						InvalidTransactionId);
+			return HeapTupleMayBeUpdated;
+		}
+		else
+		{
+			/* There are lockers running */
+			return HeapTupleBeingUpdated;
+		}
+	}
+
+	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return HeapTupleBeingUpdated;
+		if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+			return HeapTupleSelfUpdated;	/* updated after scan started */
+		else
+			return HeapTupleInvisible;	/* updated before scan started */
+	}
+
+	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+		return HeapTupleBeingUpdated;
+
+	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		/* it must have aborted or crashed */
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return HeapTupleMayBeUpdated;
+	}
+
+	/* xmax transaction committed */
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	{
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return HeapTupleMayBeUpdated;
+	}
+
+	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+				HeapTupleHeaderGetRawXmax(tuple));
+	return HeapTupleUpdated;	/* updated by other */
+}
+
+/*
+ * HeapTupleSatisfiesDirty
+ *		True iff heap tuple is valid including effects of open transactions.
+ *
+ *	Here, we consider the effects of:
+ *		all committed and in-progress transactions (as of the current instant)
+ *		previous commands of this transaction
+ *		changes made by the current command
+ *
+ * This is essentially like HeapTupleSatisfiesSelf as far as effects of
+ * the current transaction and committed/aborted xacts are concerned.
+ * However, we also include the effects of other xacts still in progress.
+ *
+ * A special hack is that the passed-in snapshot struct is used as an
+ * output argument to return the xids of concurrent xacts that affected the
+ * tuple.  snapshot->xmin is set to the tuple's xmin if that is another
+ * transaction that's still in progress; or to InvalidTransactionId if the
+ * tuple's xmin is committed good, committed dead, or my own xact.
+ * Similarly for snapshot->xmax and the tuple's xmax.  If the tuple was
+ * inserted speculatively, meaning that the inserter might still back down
+ * on the insertion without aborting the whole transaction, the associated
+ * token is also returned in snapshot->speculativeToken.
+ */
+static bool
+HeapTupleSatisfiesDirty(StorageTuple stup, Snapshot snapshot,
+						Buffer buffer)
+{
+	HeapTuple htup = (HeapTuple)stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	snapshot->xmin = snapshot->xmax = InvalidTransactionId;
+	snapshot->speculativeToken = 0;
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return false;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return false;
+			if (!TransactionIdIsInProgress(xvac))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (TransactionIdIsInProgress(xvac))
+					return false;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return true;
+
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
+				return true;
+
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleGetUpdateXid(tuple);
+
+				/* not LOCKED_ONLY, so it has to have an xmax */
+				Assert(TransactionIdIsValid(xmax));
+
+				/* updating subtransaction must have aborted */
+				if (!TransactionIdIsCurrentTransactionId(xmax))
+					return true;
+				else
+					return false;
+			}
+
+			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+			{
+				/* deleting subtransaction must have aborted */
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+				return true;
+			}
+
+			return false;
+		}
+		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			/*
+			 * Return the speculative token to caller.  Caller can worry about
+			 * xmax, since it requires a conclusively locked row version, and
+			 * a concurrent update to this tuple is a conflict of its
+			 * purposes.
+			 */
+			if (HeapTupleHeaderIsSpeculative(tuple))
+			{
+				snapshot->speculativeToken =
+					HeapTupleHeaderGetSpeculativeToken(tuple);
+
+				Assert(snapshot->speculativeToken != 0);
+			}
+
+			snapshot->xmin = HeapTupleHeaderGetRawXmin(tuple);
+			/* XXX shouldn't we fall through to look at xmax? */
+			return true;		/* in insertion by other */
+		}
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return false;
+		}
+	}
+
+	/* by here, the inserting transaction has committed */
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
+		return true;
+
+	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+		return false;			/* updated by other */
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+
+		xmax = HeapTupleGetUpdateXid(tuple);
+
+		/* not LOCKED_ONLY, so it has to have an xmax */
+		Assert(TransactionIdIsValid(xmax));
+
+		if (TransactionIdIsCurrentTransactionId(xmax))
+			return false;
+		if (TransactionIdIsInProgress(xmax))
+		{
+			snapshot->xmax = xmax;
+			return true;
+		}
+		if (TransactionIdDidCommit(xmax))
+			return false;
+		/* it must have aborted or crashed */
+		return true;
+	}
+
+	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+		return false;
+	}
+
+	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			snapshot->xmax = HeapTupleHeaderGetRawXmax(tuple);
+		return true;
+	}
+
+	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		/* it must have aborted or crashed */
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return true;
+	}
+
+	/* xmax transaction committed */
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	{
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return true;
+	}
+
+	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+				HeapTupleHeaderGetRawXmax(tuple));
+	return false;				/* updated by other */
+}
+
+/*
+ * HeapTupleSatisfiesMVCC
+ *		True iff heap tuple is valid for the given MVCC snapshot.
+ *
+ *	Here, we consider the effects of:
+ *		all transactions committed as of the time of the given snapshot
+ *		previous commands of this transaction
+ *
+ *	Does _not_ include:
+ *		transactions shown as in-progress by the snapshot
+ *		transactions started after the snapshot was taken
+ *		changes made by the current command
+ *
+ * Notice that here, we will not update the tuple status hint bits if the
+ * inserting/deleting transaction is still running according to our snapshot,
+ * even if in reality it's committed or aborted by now.  This is intentional.
+ * Checking the true transaction state would require access to high-traffic
+ * shared data structures, creating contention we'd rather do without, and it
+ * would not change the result of our visibility check anyway.  The hint bits
+ * will be updated by the first visitor that has a snapshot new enough to see
+ * the inserting/deleting transaction as done.  In the meantime, the cost of
+ * leaving the hint bits unset is basically that each HeapTupleSatisfiesMVCC
+ * call will need to run TransactionIdIsCurrentTransactionId in addition to
+ * XidInMVCCSnapshot (but it would have to do the latter anyway).  In the old
+ * coding where we tried to set the hint bits as soon as possible, we instead
+ * did TransactionIdIsInProgress in each call --- to no avail, as long as the
+ * inserting/deleting transaction was still running --- which was more cycles
+ * and more contention on the PGXACT array.
+ */
+static bool
+HeapTupleSatisfiesMVCC(StorageTuple stup, Snapshot snapshot,
+					   Buffer buffer)
+{
+	HeapTuple htup = (HeapTuple)stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return false;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return false;
+			if (!XidInMVCCSnapshot(xvac, snapshot))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (XidInMVCCSnapshot(xvac, snapshot))
+					return false;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (HeapTupleHeaderGetCmin(tuple) >= snapshot->curcid)
+				return false;	/* inserted after scan started */
+
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return true;
+
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
+				return true;
+
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleGetUpdateXid(tuple);
+
+				/* not LOCKED_ONLY, so it has to have an xmax */
+				Assert(TransactionIdIsValid(xmax));
+
+				/* updating subtransaction must have aborted */
+				if (!TransactionIdIsCurrentTransactionId(xmax))
+					return true;
+				else if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+					return true;	/* updated after scan started */
+				else
+					return false;	/* updated before scan started */
+			}
+
+			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+			{
+				/* deleting subtransaction must have aborted */
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+				return true;
+			}
+
+			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+				return true;	/* deleted after scan started */
+			else
+				return false;	/* deleted before scan started */
+		}
+		else if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
+			return false;
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return false;
+		}
+	}
+	else
+	{
+		/* xmin is committed, but maybe not according to our snapshot */
+		if (!HeapTupleHeaderXminFrozen(tuple) &&
+			XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
+			return false;		/* treat as still in progress */
+	}
+
+	/* by here, the inserting transaction has committed */
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
+		return true;
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+		return true;
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		/* already checked above */
+		Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
+
+		xmax = HeapTupleGetUpdateXid(tuple);
+
+		/* not LOCKED_ONLY, so it has to have an xmax */
+		Assert(TransactionIdIsValid(xmax));
+
+		if (TransactionIdIsCurrentTransactionId(xmax))
+		{
+			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+				return true;	/* deleted after scan started */
+			else
+				return false;	/* deleted before scan started */
+		}
+		if (XidInMVCCSnapshot(xmax, snapshot))
+			return true;
+		if (TransactionIdDidCommit(xmax))
+			return false;		/* updating transaction committed */
+		/* it must have aborted or crashed */
+		return true;
+	}
+
+	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
+	{
+		if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+		{
+			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+				return true;	/* deleted after scan started */
+			else
+				return false;	/* deleted before scan started */
+		}
+
+		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
+			return true;
+
+		if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+						InvalidTransactionId);
+			return true;
+		}
+
+		/* xmax transaction committed */
+		SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+					HeapTupleHeaderGetRawXmax(tuple));
+	}
+	else
+	{
+		/* xmax is committed, but maybe not according to our snapshot */
+		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
+			return true;		/* treat as still in progress */
+	}
+
+	/* xmax transaction committed */
+
+	return false;
+}
+
+
+/*
+ * HeapTupleSatisfiesVacuum
+ *
+ *	Determine the status of tuples for VACUUM purposes.  Here, what
+ *	we mainly want to know is if a tuple is potentially visible to *any*
+ *	running transaction.  If so, it can't be removed yet by VACUUM.
+ *
+ * OldestXmin is a cutoff XID (obtained from GetOldestXmin()).  Tuples
+ * deleted by XIDs >= OldestXmin are deemed "recently dead"; they might
+ * still be visible to some open transaction, so we can't remove them,
+ * even if we see that the deleting transaction has committed.
+ */
+static HTSV_Result
+HeapTupleSatisfiesVacuum(StorageTuple stup, TransactionId OldestXmin,
+						 Buffer buffer)
+{
+	HeapTuple htup = (HeapTuple)stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	/*
+	 * Has inserting transaction committed?
+	 *
+	 * If the inserting transaction aborted, then the tuple was never visible
+	 * to any other transaction, so we can delete it immediately.
+	 */
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return HEAPTUPLE_DEAD;
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return HEAPTUPLE_DELETE_IN_PROGRESS;
+			if (TransactionIdIsInProgress(xvac))
+				return HEAPTUPLE_DELETE_IN_PROGRESS;
+			if (TransactionIdDidCommit(xvac))
+			{
+				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+							InvalidTransactionId);
+				return HEAPTUPLE_DEAD;
+			}
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						InvalidTransactionId);
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return HEAPTUPLE_INSERT_IN_PROGRESS;
+			if (TransactionIdIsInProgress(xvac))
+				return HEAPTUPLE_INSERT_IN_PROGRESS;
+			if (TransactionIdDidCommit(xvac))
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			else
+			{
+				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+							InvalidTransactionId);
+				return HEAPTUPLE_DEAD;
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return HEAPTUPLE_INSERT_IN_PROGRESS;
+			/* only locked? run infomask-only check first, for performance */
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask) ||
+				HeapTupleHeaderIsOnlyLocked(tuple))
+				return HEAPTUPLE_INSERT_IN_PROGRESS;
+			/* inserted and then deleted by same xact */
+			if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple)))
+				return HEAPTUPLE_DELETE_IN_PROGRESS;
+			/* deleting subtransaction must have aborted */
+			return HEAPTUPLE_INSERT_IN_PROGRESS;
+		}
+		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			/*
+			 * It'd be possible to discern between INSERT/DELETE in progress
+			 * here by looking at xmax - but that doesn't seem beneficial for
+			 * the majority of callers and even detrimental for some. We'd
+			 * rather have callers look at/wait for xmin than xmax. It's
+			 * always correct to return INSERT_IN_PROGRESS because that's
+			 * what's happening from the view of other backends.
+			 */
+			return HEAPTUPLE_INSERT_IN_PROGRESS;
+		}
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/*
+			 * Not in Progress, Not Committed, so either Aborted or crashed
+			 */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return HEAPTUPLE_DEAD;
+		}
+
+		/*
+		 * At this point the xmin is known committed, but we might not have
+		 * been able to set the hint bit yet; so we can no longer Assert that
+		 * it's set.
+		 */
+	}
+
+	/*
+	 * Okay, the inserter committed, so it was good at some point.  Now what
+	 * about the deleting transaction?
+	 */
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)
+		return HEAPTUPLE_LIVE;
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	{
+		/*
+		 * "Deleting" xact really only locked it, so the tuple is live in any
+		 * case.  However, we should make sure that either XMAX_COMMITTED or
+		 * XMAX_INVALID gets set once the xact is gone, to reduce the costs of
+		 * examining the tuple for future xacts.
+		 */
+		if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
+		{
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				/*
+				 * If it's a pre-pg_upgrade tuple, the multixact cannot
+				 * possibly be running; otherwise have to check.
+				 */
+				if (!HEAP_LOCKED_UPGRADED(tuple->t_infomask) &&
+					MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
+										 true))
+					return HEAPTUPLE_LIVE;
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
+			}
+			else
+			{
+				if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+					return HEAPTUPLE_LIVE;
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+			}
+		}
+
+		/*
+		 * We don't really care whether xmax did commit, abort or crash. We
+		 * know that xmax did lock the tuple, but it did not and will never
+		 * actually update it.
+		 */
+
+		return HEAPTUPLE_LIVE;
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+		{
+			/* already checked above */
+			Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
+
+			xmax = HeapTupleGetUpdateXid(tuple);
+
+			/* not LOCKED_ONLY, so it has to have an xmax */
+			Assert(TransactionIdIsValid(xmax));
+
+			if (TransactionIdIsInProgress(xmax))
+				return HEAPTUPLE_DELETE_IN_PROGRESS;
+			else if (TransactionIdDidCommit(xmax))
+				/* there are still lockers around -- can't return DEAD here */
+				return HEAPTUPLE_RECENTLY_DEAD;
+			/* updating transaction aborted */
+			return HEAPTUPLE_LIVE;
+		}
+
+		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED));
+
+		xmax = HeapTupleGetUpdateXid(tuple);
+
+		/* not LOCKED_ONLY, so it has to have an xmax */
+		Assert(TransactionIdIsValid(xmax));
+
+		/* multi is not running -- updating xact cannot be */
+		Assert(!TransactionIdIsInProgress(xmax));
+		if (TransactionIdDidCommit(xmax))
+		{
+			if (!TransactionIdPrecedes(xmax, OldestXmin))
+				return HEAPTUPLE_RECENTLY_DEAD;
+			else
+				return HEAPTUPLE_DEAD;
+		}
+
+		/*
+		 * Not in Progress, Not Committed, so either Aborted or crashed.
+		 * Remove the Xmax.
+		 */
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
+		return HEAPTUPLE_LIVE;
+	}
+
+	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
+	{
+		if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+			return HEAPTUPLE_DELETE_IN_PROGRESS;
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+						HeapTupleHeaderGetRawXmax(tuple));
+		else
+		{
+			/*
+			 * Not in Progress, Not Committed, so either Aborted or crashed
+			 */
+			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+						InvalidTransactionId);
+			return HEAPTUPLE_LIVE;
+		}
+
+		/*
+		 * At this point the xmax is known committed, but we might not have
+		 * been able to set the hint bit yet; so we can no longer Assert that
+		 * it's set.
+		 */
+	}
+
+	/*
+	 * Deleter committed, but perhaps it was recent enough that some open
+	 * transactions could still see the tuple.
+	 */
+	if (!TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin))
+		return HEAPTUPLE_RECENTLY_DEAD;
+
+	/* Otherwise, it's dead and removable */
+	return HEAPTUPLE_DEAD;
+}
+
+/*
+ * HeapTupleSatisfiesNonVacuumable
+ *
+ *     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.
+ */
+static bool
+HeapTupleSatisfiesNonVacuumable(StorageTuple htup, Snapshot snapshot,
+                                                               Buffer buffer)
+{
+	return HeapTupleSatisfiesVacuum(htup, snapshot->xmin, buffer)
+				!= HEAPTUPLE_DEAD;
+}
+
+/*
+ * 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 for known-committed
+ * XIDs which could not be ours anyway.
+ */
+static 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;
+}
+
+/*
+ * check whether the transaction id 'xid' is in the pre-sorted array 'xip'.
+ */
+static bool
+TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num)
+{
+	return bsearch(&xid, xip, num,
+				   sizeof(TransactionId), xidComparator) != NULL;
+}
+
+/*
+ * See the comments for HeapTupleSatisfiesMVCC for the semantics this function
+ * obeys.
+ *
+ * Only usable on tuples from catalog tables!
+ *
+ * We don't need to support HEAP_MOVED_(IN|OFF) for now because we only support
+ * reading catalog pages which couldn't have been created in an older version.
+ *
+ * We don't set any hint bits in here as it seems unlikely to be beneficial as
+ * those should already be set by normal access and it seems to be too
+ * dangerous to do so as the semantics of doing so during timetravel are more
+ * complicated than when dealing "only" with the present.
+ */
+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);
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	/* inserting transaction aborted */
+	if (HeapTupleHeaderXminInvalid(tuple))
+	{
+		Assert(!TransactionIdDidCommit(xmin));
+		return false;
+	}
+	/* check if it's one of our txids, toplevel is also in there */
+	else if (TransactionIdInArray(xmin, snapshot->subxip, snapshot->subxcnt))
+	{
+		bool		resolved;
+		CommandId	cmin = HeapTupleHeaderGetRawCommandId(tuple);
+		CommandId	cmax = InvalidCommandId;
+
+		/*
+		 * another transaction might have (tried to) delete this tuple or
+		 * cmin/cmax was stored in a combocid. So we need to lookup the actual
+		 * values externally.
+		 */
+		resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot,
+												 htup, buffer,
+												 &cmin, &cmax);
+
+		if (!resolved)
+			elog(ERROR, "could not resolve cmin/cmax of catalog tuple");
+
+		Assert(cmin != InvalidCommandId);
+
+		if (cmin >= snapshot->curcid)
+			return false;		/* inserted after scan started */
+		/* fall through */
+	}
+	/* committed before our xmin horizon. Do a normal visibility check. */
+	else if (TransactionIdPrecedes(xmin, snapshot->xmin))
+	{
+		Assert(!(HeapTupleHeaderXminCommitted(tuple) &&
+				 !TransactionIdDidCommit(xmin)));
+
+		/* check for hint bit first, consult clog afterwards */
+		if (!HeapTupleHeaderXminCommitted(tuple) &&
+			!TransactionIdDidCommit(xmin))
+			return false;
+		/* fall through */
+	}
+	/* beyond our xmax horizon, i.e. invisible */
+	else if (TransactionIdFollowsOrEquals(xmin, snapshot->xmax))
+	{
+		return false;
+	}
+	/* check if it's a committed transaction in [xmin, xmax) */
+	else if (TransactionIdInArray(xmin, snapshot->xip, snapshot->xcnt))
+	{
+		/* fall through */
+	}
+
+	/*
+	 * none of the above, i.e. between [xmin, xmax) but hasn't committed. I.e.
+	 * invisible.
+	 */
+	else
+	{
+		return false;
+	}
+
+	/* at this point we know xmin is visible, go on to check xmax */
+
+	/* xid invalid or aborted */
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)
+		return true;
+	/* locked tuples are always visible */
+	else if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+		return true;
+
+	/*
+	 * We can see multis here if we're looking at user tables or if somebody
+	 * SELECT ... FOR SHARE/UPDATE a system table.
+	 */
+	else if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		xmax = HeapTupleGetUpdateXid(tuple);
+	}
+
+	/* check if it's one of our txids, toplevel is also in there */
+	if (TransactionIdInArray(xmax, snapshot->subxip, snapshot->subxcnt))
+	{
+		bool		resolved;
+		CommandId	cmin;
+		CommandId	cmax = HeapTupleHeaderGetRawCommandId(tuple);
+
+		/* Lookup actual cmin/cmax values */
+		resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot,
+												 htup, buffer,
+												 &cmin, &cmax);
+
+		if (!resolved)
+			elog(ERROR, "could not resolve combocid to cmax");
+
+		Assert(cmax != InvalidCommandId);
+
+		if (cmax >= snapshot->curcid)
+			return true;		/* deleted after scan started */
+		else
+			return false;		/* deleted before scan started */
+	}
+	/* below xmin horizon, normal transaction state is valid */
+	else if (TransactionIdPrecedes(xmax, snapshot->xmin))
+	{
+		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED &&
+				 !TransactionIdDidCommit(xmax)));
+
+		/* check hint bit first */
+		if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+			return false;
+
+		/* check clog */
+		return !TransactionIdDidCommit(xmax);
+	}
+	/* above xmax horizon, we cannot possibly see the deleting transaction */
+	else if (TransactionIdFollowsOrEquals(xmax, snapshot->xmax))
+		return true;
+	/* xmax is between [xmin, xmax), check known committed array */
+	else if (TransactionIdInArray(xmax, snapshot->xip, snapshot->xcnt))
+		return false;
+	/* xmax is between [xmin, xmax), but known not to have committed yet */
+	else
+		return true;
+}
 
 Datum
 heapam_storage_handler(PG_FUNCTION_ARGS)
@@ -54,5 +1666,15 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
 
 
+	amroutine->snapshot_satisfies[MVCC_VISIBILITY] = HeapTupleSatisfiesMVCC;
+	amroutine->snapshot_satisfies[SELF_VISIBILITY] = HeapTupleSatisfiesSelf;
+	amroutine->snapshot_satisfies[ANY_VISIBILITY] = HeapTupleSatisfiesAny;
+	amroutine->snapshot_satisfies[TOAST_VISIBILITY] = HeapTupleSatisfiesToast;
+	amroutine->snapshot_satisfies[DIRTY_VISIBILITY] = HeapTupleSatisfiesDirty;
+	amroutine->snapshot_satisfies[HISTORIC_MVCC_VISIBILITY] = HeapTupleSatisfiesHistoricMVCC;
+	amroutine->snapshot_satisfies[NON_VACUUMABLE_VISIBILTY] = HeapTupleSatisfiesNonVacuumable;
+
+	amroutine->snapshot_satisfiesUpdate = HeapTupleSatisfiesUpdate;
+	amroutine->snapshot_satisfiesVacuum = HeapTupleSatisfiesVacuum;
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 52231ac..7425068 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/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index bd560e4..191f088 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -108,6 +108,7 @@
 #include "miscadmin.h"
 
 #include "access/heapam.h"
+#include "access/heapam_common.h"
 #include "access/heapam_xlog.h"
 #include "access/rewriteheap.h"
 #include "access/transam.h"
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 05d7da0..01321a2 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/catalog/index.c b/src/backend/catalog/index.c
index c7b2f03..0240df7 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2222,6 +2222,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	TransactionId OldestXmin;
 	BlockNumber root_blkno = InvalidBlockNumber;
 	OffsetNumber root_offsets[MaxHeapTuplesPerPage];
+	StorageAmRoutine *method;
 
 	/*
 	 * sanity checks
@@ -2277,6 +2278,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 */
@@ -2357,7 +2359,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 			 */
 			LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 
-			switch (HeapTupleSatisfiesVacuum(heapTuple, OldestXmin,
+			switch (method->snapshot_satisfiesVacuum(heapTuple, OldestXmin,
 											 scan->rs_cbuf))
 			{
 				case HEAPTUPLE_DEAD:
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 08fc18e..2611cca 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1069,7 +1069,7 @@ acquire_sample_rows(Relation onerel, int elevel,
 			targtuple.t_data = (HeapTupleHeader) PageGetItem(targpage, itemid);
 			targtuple.t_len = ItemIdGetLength(itemid);
 
-			switch (HeapTupleSatisfiesVacuum(&targtuple,
+			switch (onerel->rd_stamroutine->snapshot_satisfiesVacuum(&targtuple,
 											 OldestXmin,
 											 targbuffer))
 			{
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 48f1e6e..dbcc5bc 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 45b1859..dccda5a 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -975,7 +975,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:
 
@@ -2140,7 +2140,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 f7e55e0..60a6cb0 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -428,7 +428,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 bd84778..e609eff 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -190,6 +190,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
  */
 static void
 ExecCheckHeapTupleVisible(EState *estate,
+						  Relation rel,
 						  HeapTuple tuple,
 						  Buffer buffer)
 {
@@ -201,7 +202,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
@@ -236,7 +237,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);
 }
 
@@ -1307,7 +1308,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 9c74a83..6a118d1 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 fba57a0..095d1ed 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 6a6d9d6..22948cb 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 c2891e6..5a6d216 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -289,7 +289,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 5a6e6fa..f17b1c5 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 08a08c8..1f96913 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;
@@ -2040,7 +2040,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),
@@ -2137,7 +2137,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/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c
deleted file mode 100644
index bbac408..0000000
--- a/src/backend/utils/time/tqual.c
+++ /dev/null
@@ -1,1809 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * tqual.c
- *	  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
- * has now committed or aborted (and it is safe to set the hint bits).
- * If the hint bits are changed, MarkBufferDirtyHint is called on
- * the passed-in buffer.  The caller must hold not only a pin, but at least
- * shared buffer content lock on the buffer containing the tuple.
- *
- * NOTE: When using a non-MVCC snapshot, we must check
- * TransactionIdIsInProgress (which looks in the PGXACT array)
- * before TransactionIdDidCommit/TransactionIdDidAbort (which look in
- * pg_xact).  Otherwise we have a race condition: we might decide that a
- * just-committed transaction crashed, because none of the tests succeed.
- * xact.c is careful to record commit/abort in pg_xact before it unsets
- * MyPgXact->xid in the PGXACT array.  That fixes that problem, but it
- * also means there is a window where TransactionIdIsInProgress and
- * TransactionIdDidCommit will both return true.  If we check only
- * TransactionIdDidCommit, we could consider a tuple committed when a
- * later GetSnapshotData call will still think the originating transaction
- * is in progress, which leads to application-level inconsistency.  The
- * upshot is that we gotta check TransactionIdIsInProgress first in all
- * code paths, except for a few cases where we are looking at
- * subtransactions of our own main transaction and so there can't be any
- * race condition.
- *
- * When using an MVCC snapshot, we rely on XidInMVCCSnapshot rather than
- * TransactionIdIsInProgress, but the logic is otherwise the same: do not
- * check pg_xact until after deciding that the xact is no longer in progress.
- *
- *
- * Summary of visibility functions:
- *
- *	 HeapTupleSatisfiesMVCC()
- *		  visible to supplied snapshot, excludes current command
- *	 HeapTupleSatisfiesUpdate()
- *		  visible to instant snapshot, with user-supplied command
- *		  counter and more complex result
- *	 HeapTupleSatisfiesSelf()
- *		  visible to instant snapshot and current command
- *	 HeapTupleSatisfiesDirty()
- *		  like HeapTupleSatisfiesSelf(), but includes open transactions
- *	 HeapTupleSatisfiesVacuum()
- *		  visible to any running transaction, used by VACUUM
- *	 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};
-
-/* local functions */
-static 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);
-}
-
-/*
- * 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
- *		True iff heap tuple is valid "for itself".
- *
- *	Here, we consider the effects of:
- *		all committed transactions (as of the current instant)
- *		previous commands of this transaction
- *		changes made by the current command
- *
- * Note:
- *		Assumes heap tuple is valid.
- *
- * The satisfaction of "itself" requires the following:
- *
- * ((Xmin == my-transaction &&				the row was updated by the current transaction, and
- *		(Xmax is null						it was not deleted
- *		 [|| Xmax != my-transaction)])			[or it was deleted by another transaction]
- * ||
- *
- * (Xmin is committed &&					the row was modified by a committed transaction, and
- *		(Xmax is null ||					the row has not been deleted, or
- *			(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)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return false;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return false;
-			if (!TransactionIdIsInProgress(xvac))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (TransactionIdIsInProgress(xvac))
-					return false;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return true;
-
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
-				return true;
-
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleGetUpdateXid(tuple);
-
-				/* not LOCKED_ONLY, so it has to have an xmax */
-				Assert(TransactionIdIsValid(xmax));
-
-				/* updating subtransaction must have aborted */
-				if (!TransactionIdIsCurrentTransactionId(xmax))
-					return true;
-				else
-					return false;
-			}
-
-			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-			{
-				/* deleting subtransaction must have aborted */
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-				return true;
-			}
-
-			return false;
-		}
-		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
-			return false;
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return false;
-		}
-	}
-
-	/* by here, the inserting transaction has committed */
-
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
-		return true;
-
-	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-		return false;			/* updated by other */
-	}
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-
-		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 true;
-		if (TransactionIdDidCommit(xmax))
-			return false;
-		/* it must have aborted or crashed */
-		return true;
-	}
-
-	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-		return false;
-	}
-
-	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-		return true;
-
-	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		/* it must have aborted or crashed */
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return true;
-	}
-
-	/* xmax transaction committed */
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-	{
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return true;
-	}
-
-	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-				HeapTupleHeaderGetRawXmax(tuple));
-	return false;
-}
-
-/*
- * HeapTupleSatisfiesAny
- *		Dummy "satisfies" routine: any tuple satisfies SnapshotAny.
- */
-bool
-HeapTupleSatisfiesAny(HeapTuple htup, Snapshot snapshot, Buffer buffer)
-{
-	return true;
-}
-
-/*
- * HeapTupleSatisfiesToast
- *		True iff heap tuple is valid as a TOAST row.
- *
- * This is a simplified version that only checks for VACUUM moving conditions.
- * It's appropriate for TOAST usage because TOAST really doesn't want to do
- * its own time qual checks; if you can see the main table row that contains
- * a TOAST reference, you should be able to see the TOASTed value.  However,
- * vacuuming a TOAST table is independent of the main table, and in case such
- * a vacuum fails partway through, we'd better do this much checking.
- *
- * Among other things, this means you can't do UPDATEs of rows in a TOAST
- * table.
- */
-bool
-HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot,
-						Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return false;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return false;
-			if (!TransactionIdIsInProgress(xvac))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (TransactionIdIsInProgress(xvac))
-					return false;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-			}
-		}
-
-		/*
-		 * An invalid Xmin can be left behind by a speculative insertion that
-		 * is canceled by super-deleting the tuple.  This also applies to
-		 * TOAST tuples created during speculative insertion.
-		 */
-		else if (!TransactionIdIsValid(HeapTupleHeaderGetXmin(tuple)))
-			return false;
-	}
-
-	/* otherwise assume the tuple is valid for TOAST. */
-	return true;
-}
-
-/*
- * HeapTupleSatisfiesUpdate
- *
- *	This function returns a more detailed result code than most of the
- *	functions in this file, since UPDATE needs to know more than "is it
- *	visible?".  It also allows for user-supplied CommandId rather than
- *	relying on CurrentCommandId.
- *
- *	The possible return codes are:
- *
- *	HeapTupleInvisible: the tuple didn't exist at all when the scan started,
- *	e.g. it was created by a later CommandId.
- *
- *	HeapTupleMayBeUpdated: The tuple is valid and visible, so it may be
- *	updated.
- *
- *	HeapTupleSelfUpdated: The tuple was updated by the current transaction,
- *	after the current scan started.
- *
- *	HeapTupleUpdated: The tuple was updated by a committed transaction.
- *
- *	HeapTupleBeingUpdated: The tuple is being updated by an in-progress
- *	transaction other than the current transaction.  (Note: this includes
- *	the case where the tuple is share-locked by a MultiXact, even if the
- *	MultiXact includes the current transaction.  Callers that want to
- *	distinguish that case must test for it themselves.)
- */
-HTSU_Result
-HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
-						 Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return HeapTupleInvisible;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return HeapTupleInvisible;
-			if (!TransactionIdIsInProgress(xvac))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return HeapTupleInvisible;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (TransactionIdIsInProgress(xvac))
-					return HeapTupleInvisible;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return HeapTupleInvisible;
-				}
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (HeapTupleHeaderGetCmin(tuple) >= curcid)
-				return HeapTupleInvisible;	/* inserted after scan started */
-
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return HeapTupleMayBeUpdated;
-
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleHeaderGetRawXmax(tuple);
-
-				/*
-				 * Careful here: even though this tuple was created by our own
-				 * transaction, it might be locked by other transactions, if
-				 * the original version was key-share locked when we updated
-				 * it.
-				 */
-
-				if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-				{
-					if (MultiXactIdIsRunning(xmax, true))
-						return HeapTupleBeingUpdated;
-					else
-						return HeapTupleMayBeUpdated;
-				}
-
-				/*
-				 * If the locker is gone, then there is nothing of interest
-				 * left in this Xmax; otherwise, report the tuple as
-				 * locked/updated.
-				 */
-				if (!TransactionIdIsInProgress(xmax))
-					return HeapTupleMayBeUpdated;
-				return HeapTupleBeingUpdated;
-			}
-
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleGetUpdateXid(tuple);
-
-				/* not LOCKED_ONLY, so it has to have an xmax */
-				Assert(TransactionIdIsValid(xmax));
-
-				/* deleting subtransaction must have aborted */
-				if (!TransactionIdIsCurrentTransactionId(xmax))
-				{
-					if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
-											 false))
-						return HeapTupleBeingUpdated;
-					return HeapTupleMayBeUpdated;
-				}
-				else
-				{
-					if (HeapTupleHeaderGetCmax(tuple) >= curcid)
-						return HeapTupleSelfUpdated;	/* updated after scan
-														 * started */
-					else
-						return HeapTupleInvisible;	/* updated before scan
-													 * started */
-				}
-			}
-
-			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-			{
-				/* deleting subtransaction must have aborted */
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-				return HeapTupleMayBeUpdated;
-			}
-
-			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
-				return HeapTupleSelfUpdated;	/* updated after scan started */
-			else
-				return HeapTupleInvisible;	/* updated before scan started */
-		}
-		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
-			return HeapTupleInvisible;
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return HeapTupleInvisible;
-		}
-	}
-
-	/* by here, the inserting transaction has committed */
-
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
-		return HeapTupleMayBeUpdated;
-
-	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return HeapTupleMayBeUpdated;
-		return HeapTupleUpdated;	/* updated by other */
-	}
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		if (HEAP_LOCKED_UPGRADED(tuple->t_infomask))
-			return HeapTupleMayBeUpdated;
-
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-		{
-			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), true))
-				return HeapTupleBeingUpdated;
-
-			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
-			return HeapTupleMayBeUpdated;
-		}
-
-		xmax = HeapTupleGetUpdateXid(tuple);
-		if (!TransactionIdIsValid(xmax))
-		{
-			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
-				return HeapTupleBeingUpdated;
-		}
-
-		/* not LOCKED_ONLY, so it has to have an xmax */
-		Assert(TransactionIdIsValid(xmax));
-
-		if (TransactionIdIsCurrentTransactionId(xmax))
-		{
-			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
-				return HeapTupleSelfUpdated;	/* updated after scan started */
-			else
-				return HeapTupleInvisible;	/* updated before scan started */
-		}
-
-		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
-			return HeapTupleBeingUpdated;
-
-		if (TransactionIdDidCommit(xmax))
-			return HeapTupleUpdated;
-
-		/*
-		 * By here, the update in the Xmax is either aborted or crashed, but
-		 * what about the other members?
-		 */
-
-		if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
-		{
-			/*
-			 * There's no member, even just a locker, alive anymore, so we can
-			 * mark the Xmax as invalid.
-			 */
-			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-						InvalidTransactionId);
-			return HeapTupleMayBeUpdated;
-		}
-		else
-		{
-			/* There are lockers running */
-			return HeapTupleBeingUpdated;
-		}
-	}
-
-	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return HeapTupleBeingUpdated;
-		if (HeapTupleHeaderGetCmax(tuple) >= curcid)
-			return HeapTupleSelfUpdated;	/* updated after scan started */
-		else
-			return HeapTupleInvisible;	/* updated before scan started */
-	}
-
-	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-		return HeapTupleBeingUpdated;
-
-	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		/* it must have aborted or crashed */
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return HeapTupleMayBeUpdated;
-	}
-
-	/* xmax transaction committed */
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-	{
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return HeapTupleMayBeUpdated;
-	}
-
-	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-				HeapTupleHeaderGetRawXmax(tuple));
-	return HeapTupleUpdated;	/* updated by other */
-}
-
-/*
- * HeapTupleSatisfiesDirty
- *		True iff heap tuple is valid including effects of open transactions.
- *
- *	Here, we consider the effects of:
- *		all committed and in-progress transactions (as of the current instant)
- *		previous commands of this transaction
- *		changes made by the current command
- *
- * This is essentially like HeapTupleSatisfiesSelf as far as effects of
- * the current transaction and committed/aborted xacts are concerned.
- * However, we also include the effects of other xacts still in progress.
- *
- * A special hack is that the passed-in snapshot struct is used as an
- * output argument to return the xids of concurrent xacts that affected the
- * tuple.  snapshot->xmin is set to the tuple's xmin if that is another
- * transaction that's still in progress; or to InvalidTransactionId if the
- * tuple's xmin is committed good, committed dead, or my own xact.
- * Similarly for snapshot->xmax and the tuple's xmax.  If the tuple was
- * inserted speculatively, meaning that the inserter might still back down
- * on the insertion without aborting the whole transaction, the associated
- * token is also returned in snapshot->speculativeToken.
- */
-bool
-HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
-						Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	snapshot->xmin = snapshot->xmax = InvalidTransactionId;
-	snapshot->speculativeToken = 0;
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return false;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return false;
-			if (!TransactionIdIsInProgress(xvac))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (TransactionIdIsInProgress(xvac))
-					return false;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return true;
-
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
-				return true;
-
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleGetUpdateXid(tuple);
-
-				/* not LOCKED_ONLY, so it has to have an xmax */
-				Assert(TransactionIdIsValid(xmax));
-
-				/* updating subtransaction must have aborted */
-				if (!TransactionIdIsCurrentTransactionId(xmax))
-					return true;
-				else
-					return false;
-			}
-
-			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-			{
-				/* deleting subtransaction must have aborted */
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-				return true;
-			}
-
-			return false;
-		}
-		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			/*
-			 * Return the speculative token to caller.  Caller can worry about
-			 * xmax, since it requires a conclusively locked row version, and
-			 * a concurrent update to this tuple is a conflict of its
-			 * purposes.
-			 */
-			if (HeapTupleHeaderIsSpeculative(tuple))
-			{
-				snapshot->speculativeToken =
-					HeapTupleHeaderGetSpeculativeToken(tuple);
-
-				Assert(snapshot->speculativeToken != 0);
-			}
-
-			snapshot->xmin = HeapTupleHeaderGetRawXmin(tuple);
-			/* XXX shouldn't we fall through to look at xmax? */
-			return true;		/* in insertion by other */
-		}
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return false;
-		}
-	}
-
-	/* by here, the inserting transaction has committed */
-
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
-		return true;
-
-	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-		return false;			/* updated by other */
-	}
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-
-		xmax = HeapTupleGetUpdateXid(tuple);
-
-		/* not LOCKED_ONLY, so it has to have an xmax */
-		Assert(TransactionIdIsValid(xmax));
-
-		if (TransactionIdIsCurrentTransactionId(xmax))
-			return false;
-		if (TransactionIdIsInProgress(xmax))
-		{
-			snapshot->xmax = xmax;
-			return true;
-		}
-		if (TransactionIdDidCommit(xmax))
-			return false;
-		/* it must have aborted or crashed */
-		return true;
-	}
-
-	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-		return false;
-	}
-
-	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			snapshot->xmax = HeapTupleHeaderGetRawXmax(tuple);
-		return true;
-	}
-
-	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		/* it must have aborted or crashed */
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return true;
-	}
-
-	/* xmax transaction committed */
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-	{
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return true;
-	}
-
-	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-				HeapTupleHeaderGetRawXmax(tuple));
-	return false;				/* updated by other */
-}
-
-/*
- * HeapTupleSatisfiesMVCC
- *		True iff heap tuple is valid for the given MVCC snapshot.
- *
- *	Here, we consider the effects of:
- *		all transactions committed as of the time of the given snapshot
- *		previous commands of this transaction
- *
- *	Does _not_ include:
- *		transactions shown as in-progress by the snapshot
- *		transactions started after the snapshot was taken
- *		changes made by the current command
- *
- * Notice that here, we will not update the tuple status hint bits if the
- * inserting/deleting transaction is still running according to our snapshot,
- * even if in reality it's committed or aborted by now.  This is intentional.
- * Checking the true transaction state would require access to high-traffic
- * shared data structures, creating contention we'd rather do without, and it
- * would not change the result of our visibility check anyway.  The hint bits
- * will be updated by the first visitor that has a snapshot new enough to see
- * the inserting/deleting transaction as done.  In the meantime, the cost of
- * leaving the hint bits unset is basically that each HeapTupleSatisfiesMVCC
- * call will need to run TransactionIdIsCurrentTransactionId in addition to
- * XidInMVCCSnapshot (but it would have to do the latter anyway).  In the old
- * coding where we tried to set the hint bits as soon as possible, we instead
- * did TransactionIdIsInProgress in each call --- to no avail, as long as the
- * inserting/deleting transaction was still running --- which was more cycles
- * and more contention on the PGXACT array.
- */
-bool
-HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
-					   Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return false;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return false;
-			if (!XidInMVCCSnapshot(xvac, snapshot))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (XidInMVCCSnapshot(xvac, snapshot))
-					return false;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (HeapTupleHeaderGetCmin(tuple) >= snapshot->curcid)
-				return false;	/* inserted after scan started */
-
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return true;
-
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
-				return true;
-
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleGetUpdateXid(tuple);
-
-				/* not LOCKED_ONLY, so it has to have an xmax */
-				Assert(TransactionIdIsValid(xmax));
-
-				/* updating subtransaction must have aborted */
-				if (!TransactionIdIsCurrentTransactionId(xmax))
-					return true;
-				else if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
-					return true;	/* updated after scan started */
-				else
-					return false;	/* updated before scan started */
-			}
-
-			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-			{
-				/* deleting subtransaction must have aborted */
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-				return true;
-			}
-
-			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
-				return true;	/* deleted after scan started */
-			else
-				return false;	/* deleted before scan started */
-		}
-		else if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
-			return false;
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return false;
-		}
-	}
-	else
-	{
-		/* xmin is committed, but maybe not according to our snapshot */
-		if (!HeapTupleHeaderXminFrozen(tuple) &&
-			XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
-			return false;		/* treat as still in progress */
-	}
-
-	/* by here, the inserting transaction has committed */
-
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
-		return true;
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-		return true;
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		/* already checked above */
-		Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
-
-		xmax = HeapTupleGetUpdateXid(tuple);
-
-		/* not LOCKED_ONLY, so it has to have an xmax */
-		Assert(TransactionIdIsValid(xmax));
-
-		if (TransactionIdIsCurrentTransactionId(xmax))
-		{
-			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
-				return true;	/* deleted after scan started */
-			else
-				return false;	/* deleted before scan started */
-		}
-		if (XidInMVCCSnapshot(xmax, snapshot))
-			return true;
-		if (TransactionIdDidCommit(xmax))
-			return false;		/* updating transaction committed */
-		/* it must have aborted or crashed */
-		return true;
-	}
-
-	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
-	{
-		if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-		{
-			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
-				return true;	/* deleted after scan started */
-			else
-				return false;	/* deleted before scan started */
-		}
-
-		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
-			return true;
-
-		if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-						InvalidTransactionId);
-			return true;
-		}
-
-		/* xmax transaction committed */
-		SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-					HeapTupleHeaderGetRawXmax(tuple));
-	}
-	else
-	{
-		/* xmax is committed, but maybe not according to our snapshot */
-		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
-			return true;		/* treat as still in progress */
-	}
-
-	/* xmax transaction committed */
-
-	return false;
-}
-
-
-/*
- * HeapTupleSatisfiesVacuum
- *
- *	Determine the status of tuples for VACUUM purposes.  Here, what
- *	we mainly want to know is if a tuple is potentially visible to *any*
- *	running transaction.  If so, it can't be removed yet by VACUUM.
- *
- * OldestXmin is a cutoff XID (obtained from GetOldestXmin()).  Tuples
- * deleted by XIDs >= OldestXmin are deemed "recently dead"; they might
- * still be visible to some open transaction, so we can't remove them,
- * even if we see that the deleting transaction has committed.
- */
-HTSV_Result
-HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
-						 Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	/*
-	 * Has inserting transaction committed?
-	 *
-	 * If the inserting transaction aborted, then the tuple was never visible
-	 * to any other transaction, so we can delete it immediately.
-	 */
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return HEAPTUPLE_DEAD;
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return HEAPTUPLE_DELETE_IN_PROGRESS;
-			if (TransactionIdIsInProgress(xvac))
-				return HEAPTUPLE_DELETE_IN_PROGRESS;
-			if (TransactionIdDidCommit(xvac))
-			{
-				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-							InvalidTransactionId);
-				return HEAPTUPLE_DEAD;
-			}
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						InvalidTransactionId);
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return HEAPTUPLE_INSERT_IN_PROGRESS;
-			if (TransactionIdIsInProgress(xvac))
-				return HEAPTUPLE_INSERT_IN_PROGRESS;
-			if (TransactionIdDidCommit(xvac))
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			else
-			{
-				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-							InvalidTransactionId);
-				return HEAPTUPLE_DEAD;
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return HEAPTUPLE_INSERT_IN_PROGRESS;
-			/* only locked? run infomask-only check first, for performance */
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask) ||
-				HeapTupleHeaderIsOnlyLocked(tuple))
-				return HEAPTUPLE_INSERT_IN_PROGRESS;
-			/* inserted and then deleted by same xact */
-			if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple)))
-				return HEAPTUPLE_DELETE_IN_PROGRESS;
-			/* deleting subtransaction must have aborted */
-			return HEAPTUPLE_INSERT_IN_PROGRESS;
-		}
-		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			/*
-			 * It'd be possible to discern between INSERT/DELETE in progress
-			 * here by looking at xmax - but that doesn't seem beneficial for
-			 * the majority of callers and even detrimental for some. We'd
-			 * rather have callers look at/wait for xmin than xmax. It's
-			 * always correct to return INSERT_IN_PROGRESS because that's
-			 * what's happening from the view of other backends.
-			 */
-			return HEAPTUPLE_INSERT_IN_PROGRESS;
-		}
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/*
-			 * Not in Progress, Not Committed, so either Aborted or crashed
-			 */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return HEAPTUPLE_DEAD;
-		}
-
-		/*
-		 * At this point the xmin is known committed, but we might not have
-		 * been able to set the hint bit yet; so we can no longer Assert that
-		 * it's set.
-		 */
-	}
-
-	/*
-	 * Okay, the inserter committed, so it was good at some point.  Now what
-	 * about the deleting transaction?
-	 */
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)
-		return HEAPTUPLE_LIVE;
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-	{
-		/*
-		 * "Deleting" xact really only locked it, so the tuple is live in any
-		 * case.  However, we should make sure that either XMAX_COMMITTED or
-		 * XMAX_INVALID gets set once the xact is gone, to reduce the costs of
-		 * examining the tuple for future xacts.
-		 */
-		if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
-		{
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				/*
-				 * If it's a pre-pg_upgrade tuple, the multixact cannot
-				 * possibly be running; otherwise have to check.
-				 */
-				if (!HEAP_LOCKED_UPGRADED(tuple->t_infomask) &&
-					MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
-										 true))
-					return HEAPTUPLE_LIVE;
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
-			}
-			else
-			{
-				if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-					return HEAPTUPLE_LIVE;
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-			}
-		}
-
-		/*
-		 * We don't really care whether xmax did commit, abort or crash. We
-		 * know that xmax did lock the tuple, but it did not and will never
-		 * actually update it.
-		 */
-
-		return HEAPTUPLE_LIVE;
-	}
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
-		{
-			/* already checked above */
-			Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
-
-			xmax = HeapTupleGetUpdateXid(tuple);
-
-			/* not LOCKED_ONLY, so it has to have an xmax */
-			Assert(TransactionIdIsValid(xmax));
-
-			if (TransactionIdIsInProgress(xmax))
-				return HEAPTUPLE_DELETE_IN_PROGRESS;
-			else if (TransactionIdDidCommit(xmax))
-				/* there are still lockers around -- can't return DEAD here */
-				return HEAPTUPLE_RECENTLY_DEAD;
-			/* updating transaction aborted */
-			return HEAPTUPLE_LIVE;
-		}
-
-		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED));
-
-		xmax = HeapTupleGetUpdateXid(tuple);
-
-		/* not LOCKED_ONLY, so it has to have an xmax */
-		Assert(TransactionIdIsValid(xmax));
-
-		/* multi is not running -- updating xact cannot be */
-		Assert(!TransactionIdIsInProgress(xmax));
-		if (TransactionIdDidCommit(xmax))
-		{
-			if (!TransactionIdPrecedes(xmax, OldestXmin))
-				return HEAPTUPLE_RECENTLY_DEAD;
-			else
-				return HEAPTUPLE_DEAD;
-		}
-
-		/*
-		 * Not in Progress, Not Committed, so either Aborted or crashed.
-		 * Remove the Xmax.
-		 */
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
-		return HEAPTUPLE_LIVE;
-	}
-
-	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
-	{
-		if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-			return HEAPTUPLE_DELETE_IN_PROGRESS;
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-						HeapTupleHeaderGetRawXmax(tuple));
-		else
-		{
-			/*
-			 * Not in Progress, Not Committed, so either Aborted or crashed
-			 */
-			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-						InvalidTransactionId);
-			return HEAPTUPLE_LIVE;
-		}
-
-		/*
-		 * At this point the xmax is known committed, but we might not have
-		 * been able to set the hint bit yet; so we can no longer Assert that
-		 * it's set.
-		 */
-	}
-
-	/*
-	 * Deleter committed, but perhaps it was recent enough that some open
-	 * transactions could still see the tuple.
-	 */
-	if (!TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin))
-		return HEAPTUPLE_RECENTLY_DEAD;
-
-	/* Otherwise, it's dead and removable */
-	return HEAPTUPLE_DEAD;
-}
-
-
-/*
- * HeapTupleSatisfiesNonVacuumable
- *
- *	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.
- */
-bool
-HeapTupleSatisfiesNonVacuumable(HeapTuple 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 for known-committed
- * XIDs which could not be ours anyway.
- */
-static 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'.
- */
-static bool
-TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num)
-{
-	return bsearch(&xid, xip, num,
-				   sizeof(TransactionId), xidComparator) != NULL;
-}
-
-/*
- * See the comments for HeapTupleSatisfiesMVCC for the semantics this function
- * obeys.
- *
- * Only usable on tuples from catalog tables!
- *
- * We don't need to support HEAP_MOVED_(IN|OFF) for now because we only support
- * reading catalog pages which couldn't have been created in an older version.
- *
- * We don't set any hint bits in here as it seems unlikely to be beneficial as
- * those should already be set by normal access and it seems to be too
- * 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,
-							   Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-	TransactionId xmin = HeapTupleHeaderGetXmin(tuple);
-	TransactionId xmax = HeapTupleHeaderGetRawXmax(tuple);
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	/* inserting transaction aborted */
-	if (HeapTupleHeaderXminInvalid(tuple))
-	{
-		Assert(!TransactionIdDidCommit(xmin));
-		return false;
-	}
-	/* check if it's one of our txids, toplevel is also in there */
-	else if (TransactionIdInArray(xmin, snapshot->subxip, snapshot->subxcnt))
-	{
-		bool		resolved;
-		CommandId	cmin = HeapTupleHeaderGetRawCommandId(tuple);
-		CommandId	cmax = InvalidCommandId;
-
-		/*
-		 * another transaction might have (tried to) delete this tuple or
-		 * cmin/cmax was stored in a combocid. So we need to lookup the actual
-		 * values externally.
-		 */
-		resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot,
-												 htup, buffer,
-												 &cmin, &cmax);
-
-		if (!resolved)
-			elog(ERROR, "could not resolve cmin/cmax of catalog tuple");
-
-		Assert(cmin != InvalidCommandId);
-
-		if (cmin >= snapshot->curcid)
-			return false;		/* inserted after scan started */
-		/* fall through */
-	}
-	/* committed before our xmin horizon. Do a normal visibility check. */
-	else if (TransactionIdPrecedes(xmin, snapshot->xmin))
-	{
-		Assert(!(HeapTupleHeaderXminCommitted(tuple) &&
-				 !TransactionIdDidCommit(xmin)));
-
-		/* check for hint bit first, consult clog afterwards */
-		if (!HeapTupleHeaderXminCommitted(tuple) &&
-			!TransactionIdDidCommit(xmin))
-			return false;
-		/* fall through */
-	}
-	/* beyond our xmax horizon, i.e. invisible */
-	else if (TransactionIdFollowsOrEquals(xmin, snapshot->xmax))
-	{
-		return false;
-	}
-	/* check if it's a committed transaction in [xmin, xmax) */
-	else if (TransactionIdInArray(xmin, snapshot->xip, snapshot->xcnt))
-	{
-		/* fall through */
-	}
-
-	/*
-	 * none of the above, i.e. between [xmin, xmax) but hasn't committed. I.e.
-	 * invisible.
-	 */
-	else
-	{
-		return false;
-	}
-
-	/* at this point we know xmin is visible, go on to check xmax */
-
-	/* xid invalid or aborted */
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)
-		return true;
-	/* locked tuples are always visible */
-	else if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-		return true;
-
-	/*
-	 * We can see multis here if we're looking at user tables or if somebody
-	 * SELECT ... FOR SHARE/UPDATE a system table.
-	 */
-	else if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		xmax = HeapTupleGetUpdateXid(tuple);
-	}
-
-	/* check if it's one of our txids, toplevel is also in there */
-	if (TransactionIdInArray(xmax, snapshot->subxip, snapshot->subxcnt))
-	{
-		bool		resolved;
-		CommandId	cmin;
-		CommandId	cmax = HeapTupleHeaderGetRawCommandId(tuple);
-
-		/* Lookup actual cmin/cmax values */
-		resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot,
-												 htup, buffer,
-												 &cmin, &cmax);
-
-		if (!resolved)
-			elog(ERROR, "could not resolve combocid to cmax");
-
-		Assert(cmax != InvalidCommandId);
-
-		if (cmax >= snapshot->curcid)
-			return true;		/* deleted after scan started */
-		else
-			return false;		/* deleted before scan started */
-	}
-	/* below xmin horizon, normal transaction state is valid */
-	else if (TransactionIdPrecedes(xmax, snapshot->xmin))
-	{
-		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED &&
-				 !TransactionIdDidCommit(xmax)));
-
-		/* check hint bit first */
-		if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
-			return false;
-
-		/* check clog */
-		return !TransactionIdDidCommit(xmax);
-	}
-	/* above xmax horizon, we cannot possibly see the deleting transaction */
-	else if (TransactionIdFollowsOrEquals(xmax, snapshot->xmax))
-		return true;
-	/* xmax is between [xmin, xmax), check known committed array */
-	else if (TransactionIdInArray(xmax, snapshot->xip, snapshot->xcnt))
-		return false;
-	/* xmax is between [xmin, xmax), but known not to have committed yet */
-	else
-		return true;
-}
diff --git a/src/include/access/heapam_common.h b/src/include/access/heapam_common.h
new file mode 100644
index 0000000..ff63cf3
--- /dev/null
+++ b/src/include/access/heapam_common.h
@@ -0,0 +1,96 @@
+/*-------------------------------------------------------------------------
+ *
+ * heapam_shared.h
+ *	  POSTGRES heap access method definitions shared across
+ *	  server and heapam methods.
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/heapam_shared.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef HEAPAM_SHARED_H
+#define HEAPAM_SHARED_H
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/sdir.h"
+#include "access/skey.h"
+#include "access/transam.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "nodes/lockoptions.h"
+#include "nodes/primnodes.h"
+#include "storage/bufpage.h"
+#include "storage/bufmgr.h"
+#include "storage/lockdefs.h"
+#include "storage/lmgr.h"
+#include "utils/relcache.h"
+#include "utils/snapshot.h"
+
+
+/* in heap/heapam_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);
+
+/*
+ * 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							/* HEAPAM_H */
diff --git a/src/include/access/htup.h b/src/include/access/htup.h
index 6459435..5b0068a 100644
--- a/src/include/access/htup.h
+++ b/src/include/access/htup.h
@@ -29,11 +29,12 @@ typedef MinimalTupleData *MinimalTuple;
 typedef enum tuple_visibility_type
 {
 	MVCC_VISIBILITY = 0, 		/* HeapTupleSatisfiesMVCC */
-	SELF_VISIBILITY,				/* HeapTupleSatisfiesSelf */
+	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;
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 98b63fc..b8b823b 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -14,13 +14,13 @@
 #ifndef BUFMGR_H
 #define BUFMGR_H
 
+#include "access/storageamapi.h"
 #include "storage/block.h"
 #include "storage/buf.h"
 #include "storage/bufpage.h"
 #include "storage/relfilenode.h"
 #include "utils/relcache.h"
 #include "utils/snapmgr.h"
-#include "utils/tqual.h"
 
 typedef void *Block;
 
@@ -268,8 +268,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 bf51977..5752ee4 100644
--- a/src/include/utils/snapshot.h
+++ b/src/include/utils/snapshot.h
@@ -52,7 +52,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 de75e01..67a8b1e 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,48 +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))
-
-
-/* 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 void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
-					 uint16 infomask, TransactionId xid);
-extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
-
-/*
- * To avoid leaking too much knowledge about reorderbuffer implementation
- * details this is implemented in reorderbuffer.c not tqual.c.
- */
-struct HTAB;
-extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data,
-							  Snapshot snapshot,
-							  HeapTuple htup,
-							  Buffer buffer,
-							  CommandId *cmin, CommandId *cmax);
+#define HeapTupleSatisfiesVisibility(method, tuple, snapshot, buffer) \
+       (((method)->snapshot_satisfies[(snapshot)->visibility_type]) (tuple, snapshot, buffer))
 
 /*
  * We don't provide a static SnapshotDirty variable because it would be
@@ -91,14 +52,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))
 
 /*
@@ -106,8 +67,19 @@ 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))
 
+/*
+ * To avoid leaking too much knowledge about reorderbuffer implementation
+ * details this is implemented in reorderbuffer.c not tqual.c.
+ */
+struct HTAB;
+extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data,
+                                                         Snapshot snapshot,
+                                                         HeapTuple htup,
+                                                         Buffer buffer,
+                                                         CommandId *cmin, CommandId *cmax);
+
 #endif							/* TQUAL_H */
-- 
2.7.4.windows.1

0005-slot-hooks-are-added-to-storage-AM.patchapplication/octet-stream; name=0005-slot-hooks-are-added-to-storage-AM.patchDownload
From a6f0054aaaf30da1cb3370b36e35072ae7aad972 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Wed, 30 Aug 2017 17:24:35 +1000
Subject: [PATCH 5/7] 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_common.c  | 404 +++++++++++++++++++++++++++++++
 src/backend/access/heap/heapam_storage.c |   1 +
 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    |  26 +-
 src/backend/executor/execReplication.c   |  92 ++-----
 src/backend/executor/execTuples.c        | 269 +++++++++-----------
 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/heapam_common.h       |   2 +
 src/include/access/htup_details.h        |  15 +-
 src/include/executor/tuptable.h          |  54 +++--
 17 files changed, 644 insertions(+), 575 deletions(-)

diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 13ee528..5ed0f15 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"
@@ -1022,111 +1023,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.
  *		It is functionally equivalent to heap_getattr, but fetches of
@@ -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_common.c b/src/backend/access/heap/heapam_common.c
index 502f6db..6cb6c5b 100644
--- a/src/backend/access/heap/heapam_common.c
+++ b/src/backend/access/heap/heapam_common.c
@@ -159,4 +159,408 @@ HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin)
 	return TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin);
 }
 
+/*-----------------------
+ *
+ * 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;
+}
 
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 1bd4bfa..7d7ac75 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -1665,6 +1665,7 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 {
 	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
 
+	amroutine->slot_storageam = heapam_storage_slot_handler;
 
 	amroutine->snapshot_satisfies[MVCC_VISIBILITY] = HeapTupleSatisfiesMVCC;
 	amroutine->snapshot_satisfies[SELF_VISIBILITY] = HeapTupleSatisfiesSelf;
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index cfa3f05..8456bfd 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2695,7 +2695,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 e60210c..a0ec444 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 d2e0376..b440740 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 da0850b..8634473 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2348,7 +2348,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;
@@ -2429,7 +2429,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;
@@ -2777,7 +2777,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;
@@ -2819,7 +2819,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 	if (newSlot != NULL)
 	{
 		slot = ExecFilterJunk(relinfo->ri_junkFilter, newSlot);
-		slottuple = ExecMaterializeSlot(slot);
+		slottuple = ExecHeapifySlot(slot);
 		newtuple = slottuple;
 	}
 
@@ -2928,7 +2928,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;
@@ -3960,14 +3960,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 bd8a15d..4775738 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -503,12 +503,12 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			int			attnum = op->d.var.attnum;
 
 			/* 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 */
 
-			*op->resvalue = heap_getsysattr(innerslot->tts_tuple, attnum,
-											innerslot->tts_tupleDescriptor,
+			*op->resvalue = slot_getattr(innerslot,
+											attnum,
 											op->resnull);
 
 			EEO_NEXT();
@@ -519,12 +519,12 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			int			attnum = op->d.var.attnum;
 
 			/* 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 */
-			*op->resvalue = heap_getsysattr(outerslot->tts_tuple, attnum,
-											outerslot->tts_tupleDescriptor,
+
+			*op->resvalue = slot_getattr(outerslot,
+											attnum,
 											op->resnull);
 
 			EEO_NEXT();
@@ -535,12 +535,12 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			int			attnum = op->d.var.attnum;
 
 			/* 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 */
 
-			*op->resvalue = heap_getsysattr(scanslot->tts_tuple, attnum,
-											scanslot->tts_tupleDescriptor,
+			*op->resvalue = slot_getattr(scanslot,
+											attnum,
 											op->resnull);
 
 			EEO_NEXT();
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index fbb8108..d424074 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,7 +226,8 @@ bool
 RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 						 TupleTableSlot *searchslot, TupleTableSlot *outslot)
 {
-	HeapTuple	scantuple;
+    TupleTableSlot *scanslot;
+    HeapTuple	scantuple;
 	HeapScanDesc scan;
 	SnapshotData snap;
 	TransactionId xwait;
@@ -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_update_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 51d2c5d..b7a2cbc 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -81,6 +81,7 @@
  */
 #include "postgres.h"
 
+#include "access/heapam_common.h"
 #include "access/htup_details.h"
 #include "access/tuptoaster.h"
 #include "funcapi.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;
 }
@@ -206,6 +206,54 @@ MakeSingleTupleTableSlot(TupleDesc tupdesc)
 }
 
 /* --------------------------------
+ *		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
  *
  *		Release a TupleTableSlot made with MakeSingleTupleTableSlot.
@@ -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,18 @@ 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 +459,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 +540,7 @@ ExecStoreAllNullTuple(TupleTableSlot *slot)
  *		however the "system columns" of the result will not be meaningful.
  * --------------------------------
  */
-HeapTuple
+StorageTuple
 ExecCopySlotTuple(TupleTableSlot *slot)
 {
 	/*
@@ -550,20 +549,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 +568,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 +598,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 +645,7 @@ MinimalTuple
 ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 {
 	MemoryContext oldContext;
+	MinimalTuple tup;
 
 	/*
 	 * sanity checks
@@ -659,11 +653,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 +664,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 +697,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 +717,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,19 +728,11 @@ 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
 	 * mustn't leave any dangling pass-by-reference datums in tts_values.
@@ -768,17 +741,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 20892d6..02f6c81 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 e609eff..7bf8bef 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -171,7 +171,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;
@@ -271,7 +271,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
@@ -403,7 +403,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -416,7 +416,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		newId = InvalidOid;
 	}
@@ -434,7 +434,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
@@ -743,7 +743,7 @@ ExecDelete(ModifyTableState *mtstate,
 		 */
 		if (slot->tts_isempty)
 			ExecStoreAllNullTuple(slot);
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
 	}
 	else
@@ -894,7 +894,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))
@@ -955,7 +955,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
@@ -974,7 +974,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW UPDATE Triggers */
@@ -988,7 +988,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
@@ -1004,7 +1004,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
@@ -1120,7 +1120,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 6afcd1a..81964d7 100644
--- a/src/backend/executor/tqueue.c
+++ b/src/backend/executor/tqueue.c
@@ -305,7 +305,7 @@ tqueueReceiveSlot(TupleTableSlot *slot, DestReceiver *self)
 	}
 
 	/* 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 041f387..47dc627 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -724,9 +724,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/heapam_common.h b/src/include/access/heapam_common.h
index ff63cf3..1fe15ed 100644
--- a/src/include/access/heapam_common.h
+++ b/src/include/access/heapam_common.h
@@ -40,6 +40,8 @@ extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
 					 uint16 infomask, TransactionId xid);
 extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
 extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin);
+typedef struct StorageSlotAmRoutine StorageSlotAmRoutine;
+extern StorageSlotAmRoutine* heapam_storage_slot_handler(void);
 
 /*
  * SetHintBits()
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index fa04a63..9539d67 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -21,6 +21,19 @@
 #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
  * a tuple, plus the size of the null-values bitmap (at 1 bit per column),
@@ -653,7 +666,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/executor/tuptable.h b/src/include/executor/tuptable.h
index 55f4cce..84dc293 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.7.4.windows.1

0006-Tuple-Insert-API-is-added-to-Storage-AM.patchapplication/octet-stream; name=0006-Tuple-Insert-API-is-added-to-Storage-AM.patchDownload
From 955ec4cdf0b0db0df97f183d394481f72825f4d0 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Thu, 7 Sep 2017 14:48:24 +1000
Subject: [PATCH 6/7] 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.

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/Makefile         |    2 +-
 src/backend/access/heap/heapam.c         | 2737 ++++--------------------------
 src/backend/access/heap/heapam_storage.c | 2153 ++++++++++++++++++++++-
 src/backend/access/heap/rewriteheap.c    |    5 +-
 src/backend/access/heap/storageam.c      |  306 ++++
 src/backend/access/heap/tuptoaster.c     |    8 +-
 src/backend/commands/copy.c              |   29 +-
 src/backend/commands/createas.c          |   18 +-
 src/backend/commands/matview.c           |   10 +-
 src/backend/commands/tablecmds.c         |    5 +-
 src/backend/commands/trigger.c           |   43 +-
 src/backend/executor/execMain.c          |  120 +-
 src/backend/executor/execReplication.c   |   35 +-
 src/backend/executor/nodeLockRows.c      |   39 +-
 src/backend/executor/nodeModifyTable.c   |  177 +-
 src/backend/executor/nodeTidscan.c       |   23 +-
 src/backend/utils/adt/tid.c              |    5 +-
 src/include/access/heapam.h              |   26 +-
 src/include/access/heapam_common.h       |  127 ++
 src/include/access/htup_details.h        |    1 +
 src/include/access/storageam.h           |   81 +
 src/include/access/storageamapi.h        |   21 +-
 src/include/commands/trigger.h           |    2 +-
 src/include/executor/executor.h          |    6 +-
 src/include/nodes/execnodes.h            |    4 +-
 26 files changed, 3290 insertions(+), 2717 deletions(-)
 create mode 100644 src/backend/access/heap/storageam.c
 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 5ed0f15..714c4d8 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -686,6 +686,30 @@ heap_copy_tuple_as_datum(HeapTuple tuple, TupleDesc tupleDesc)
 }
 
 /*
+ * 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,
  *		which are of the length indicated by tupleDescriptor->natts
diff --git a/src/backend/access/heap/Makefile b/src/backend/access/heap/Makefile
index e6bc18e..162736f 100644
--- a/src/backend/access/heap/Makefile
+++ b/src/backend/access/heap/Makefile
@@ -13,7 +13,7 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = heapam.o heapam_common.o heapam_storage.o hio.o \
-	pruneheap.o rewriteheap.o storageamapi.o \
+	pruneheap.o rewriteheap.o storageam.o storageamapi.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 c21d6f8..d20f211 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -94,8 +94,6 @@ static HeapScanDesc heap_beginscan_internal(Relation relation,
 						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,
-					TransactionId xid, CommandId cid, int options);
 static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 				Buffer newbuf, HeapTuple oldtup,
 				HeapTuple newtup, HeapTuple old_key_tup,
@@ -103,108 +101,17 @@ static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 static Bitmapset *HeapDetermineModifiedColumns(Relation relation,
 							 Bitmapset *interesting_cols,
 							 HeapTuple oldtup, HeapTuple newtup);
-static bool heap_acquire_tuplock(Relation relation, ItemPointer tid,
-					 LockTupleMode mode, LockWaitPolicy wait_policy,
-					 bool *have_tuple_lock);
-static void compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
-						  uint16 old_infomask2, TransactionId add_to_xmax,
-						  LockTupleMode mode, bool is_update,
-						  TransactionId *result_xmax, uint16 *result_infomask,
-						  uint16 *result_infomask2);
-static HTSU_Result heap_lock_updated_tuple(Relation rel, HeapTuple tuple,
-						ItemPointer ctid, TransactionId xid,
-						LockTupleMode mode);
 static void GetMultiXactIdHintBits(MultiXactId multi, uint16 *new_infomask,
 					   uint16 *new_infomask2);
 static TransactionId MultiXactIdGetUpdateXid(TransactionId xmax,
 						uint16 t_infomask);
-static bool DoesMultiXactIdConflict(MultiXactId multi, uint16 infomask,
-						LockTupleMode lockmode);
-static void MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
-				Relation rel, ItemPointer ctid, XLTW_Oper oper,
-				int *remaining);
-static bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
-						   uint16 infomask, Relation rel, int *remaining);
-static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
+static bool Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
+				   uint16 infomask, bool nowait,
+				   Relation rel, ItemPointer ctid, XLTW_Oper oper,
+				   int *remaining);
 static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_modified,
 					   bool *copy);
 
-
-/*
- * Each tuple lock mode has a corresponding heavyweight lock, and one or two
- * corresponding MultiXactStatuses (one to merely lock tuples, another one to
- * update them).  This table (and the macros below) helps us determine the
- * heavyweight lock mode and MultiXactStatus values to use for any particular
- * tuple lock strength.
- *
- * Don't look at lockstatus/updstatus directly!  Use get_mxact_status_for_lock
- * instead.
- */
-static const struct
-{
-	LOCKMODE	hwlock;
-	int			lockstatus;
-	int			updstatus;
-}
-
-			tupleLockExtraInfo[MaxLockTupleMode + 1] =
-{
-	{							/* LockTupleKeyShare */
-		AccessShareLock,
-		MultiXactStatusForKeyShare,
-		-1						/* KeyShare does not allow updating tuples */
-	},
-	{							/* LockTupleShare */
-		RowShareLock,
-		MultiXactStatusForShare,
-		-1						/* Share does not allow updating tuples */
-	},
-	{							/* LockTupleNoKeyExclusive */
-		ExclusiveLock,
-		MultiXactStatusForNoKeyUpdate,
-		MultiXactStatusNoKeyUpdate
-	},
-	{							/* LockTupleExclusive */
-		AccessExclusiveLock,
-		MultiXactStatusForUpdate,
-		MultiXactStatusUpdate
-	}
-};
-
-/* Get the LOCKMODE for a given MultiXactStatus */
-#define LOCKMODE_from_mxstatus(status) \
-			(tupleLockExtraInfo[TUPLOCK_from_mxstatus((status))].hwlock)
-
-/*
- * Acquire heavyweight locks on tuples, using a LockTupleMode strength value.
- * This is more readable than having every caller translate it to lock.h's
- * LOCKMODE.
- */
-#define LockTupleTuplock(rel, tup, mode) \
-	LockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
-#define UnlockTupleTuplock(rel, tup, mode) \
-	UnlockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
-#define ConditionalLockTupleTuplock(rel, tup, mode) \
-	ConditionalLockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
-
-/*
- * This table maps tuple lock strength values for each particular
- * MultiXactStatus value.
- */
-static const int MultiXactStatusLock[MaxMultiXactStatus + 1] =
-{
-	LockTupleKeyShare,			/* ForKeyShare */
-	LockTupleShare,				/* ForShare */
-	LockTupleNoKeyExclusive,	/* ForNoKeyUpdate */
-	LockTupleExclusive,			/* ForUpdate */
-	LockTupleNoKeyExclusive,	/* NoKeyUpdate */
-	LockTupleExclusive			/* Update */
-};
-
-/* Get the LockTupleMode for a given MultiXactStatus */
-#define TUPLOCK_from_mxstatus(status) \
-			(MultiXactStatusLock[(status)])
-
 /* ----------------------------------------------------------------
  *						 heap support routines
  * ----------------------------------------------------------------
@@ -1838,158 +1745,6 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 }
 
 /*
- *	heap_fetch		- retrieve tuple with given tid
- *
- * On entry, tuple->t_self is the TID to fetch.  We pin the buffer holding
- * the tuple, fill in the remaining fields of *tuple, and check the tuple
- * against the specified snapshot.
- *
- * If successful (tuple found and passes snapshot time qual), then *userbuf
- * is set to the buffer holding the tuple and TRUE is returned.  The caller
- * must unpin the buffer when done with the tuple.
- *
- * If the tuple is not found (ie, item number references a deleted slot),
- * then tuple->t_data is set to NULL and FALSE is returned.
- *
- * If the tuple is found but fails the time qual check, then FALSE is returned
- * but tuple->t_data is left pointing to the tuple.
- *
- * keep_buf determines what is done with the buffer in the FALSE-result cases.
- * When the caller specifies keep_buf = true, we retain the pin on the buffer
- * and return it in *userbuf (so the caller must eventually unpin it); when
- * keep_buf = false, the pin is released and *userbuf is set to InvalidBuffer.
- *
- * stats_relation is the relation to charge the heap_fetch operation against
- * for statistical purposes.  (This could be the heap rel itself, an
- * associated index, or NULL to not count the fetch at all.)
- *
- * heap_fetch does not follow HOT chains: only the exact TID requested will
- * be fetched.
- *
- * It is somewhat inconsistent that we ereport() on invalid block number but
- * return false on invalid item number.  There are a couple of reasons though.
- * One is that the caller can relatively easily check the block number for
- * validity, but cannot check the item number without reading the page
- * himself.  Another is that when we are following a t_ctid link, we can be
- * reasonably confident that the page number is valid (since VACUUM shouldn't
- * truncate off the destination page without having killed the referencing
- * tuple first), but the item number might well not be good.
- */
-bool
-heap_fetch(Relation relation,
-		   Snapshot snapshot,
-		   HeapTuple tuple,
-		   Buffer *userbuf,
-		   bool keep_buf,
-		   Relation stats_relation)
-{
-	ItemPointer tid = &(tuple->t_self);
-	ItemId		lp;
-	Buffer		buffer;
-	Page		page;
-	OffsetNumber offnum;
-	bool		valid;
-
-	/*
-	 * Fetch and pin the appropriate page of the relation.
-	 */
-	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
-
-	/*
-	 * Need share lock on buffer to examine tuple commit status.
-	 */
-	LockBuffer(buffer, BUFFER_LOCK_SHARE);
-	page = BufferGetPage(buffer);
-	TestForOldSnapshot(snapshot, relation, page);
-
-	/*
-	 * We'd better check for out-of-range offnum in case of VACUUM since the
-	 * TID was obtained.
-	 */
-	offnum = ItemPointerGetOffsetNumber(tid);
-	if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
-	{
-		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-		if (keep_buf)
-			*userbuf = buffer;
-		else
-		{
-			ReleaseBuffer(buffer);
-			*userbuf = InvalidBuffer;
-		}
-		tuple->t_data = NULL;
-		return false;
-	}
-
-	/*
-	 * get the item line pointer corresponding to the requested tid
-	 */
-	lp = PageGetItemId(page, offnum);
-
-	/*
-	 * Must check for deleted tuple.
-	 */
-	if (!ItemIdIsNormal(lp))
-	{
-		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-		if (keep_buf)
-			*userbuf = buffer;
-		else
-		{
-			ReleaseBuffer(buffer);
-			*userbuf = InvalidBuffer;
-		}
-		tuple->t_data = NULL;
-		return false;
-	}
-
-	/*
-	 * fill in *tuple fields
-	 */
-	tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	tuple->t_len = ItemIdGetLength(lp);
-	tuple->t_tableOid = RelationGetRelid(relation);
-
-	/*
-	 * check time qualification of tuple, then release lock
-	 */
-	valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, tuple, snapshot, buffer);
-
-	if (valid)
-		PredicateLockTuple(relation, tuple, snapshot);
-
-	CheckForSerializableConflictOut(valid, relation, tuple, buffer, snapshot);
-
-	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-
-	if (valid)
-	{
-		/*
-		 * All checks passed, so return the tuple as valid. Caller is now
-		 * responsible for releasing the buffer.
-		 */
-		*userbuf = buffer;
-
-		/* Count the successful fetch against appropriate rel, if any */
-		if (stats_relation != NULL)
-			pgstat_count_heap_fetch(stats_relation);
-
-		return true;
-	}
-
-	/* Tuple failed time qual, but maybe caller wants to see it anyway. */
-	if (keep_buf)
-		*userbuf = buffer;
-	else
-	{
-		ReleaseBuffer(buffer);
-		*userbuf = InvalidBuffer;
-	}
-
-	return false;
-}
-
-/*
  *	heap_hot_search_buffer	- search HOT chain for tuple satisfying snapshot
  *
  * On entry, *tid is the TID of a tuple (either a simple tuple, or the root
@@ -2172,130 +1927,6 @@ heap_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot,
 	return result;
 }
 
-/*
- *	heap_get_latest_tid -  get the latest tid of a specified tuple
- *
- * Actually, this gets the latest version that is visible according to
- * the passed snapshot.  You can pass SnapshotDirty to get the very latest,
- * possibly uncommitted version.
- *
- * *tid is both an input and an output parameter: it is updated to
- * show the latest version of the row.  Note that it will not be changed
- * if no version of the row passes the snapshot test.
- */
-void
-heap_get_latest_tid(Relation relation,
-					Snapshot snapshot,
-					ItemPointer tid)
-{
-	BlockNumber blk;
-	ItemPointerData ctid;
-	TransactionId priorXmax;
-
-	/* this is to avoid Assert failures on bad input */
-	if (!ItemPointerIsValid(tid))
-		return;
-
-	/*
-	 * Since this can be called with user-supplied TID, don't trust the input
-	 * too much.  (RelationGetNumberOfBlocks is an expensive check, so we
-	 * don't check t_ctid links again this way.  Note that it would not do to
-	 * call it just once and save the result, either.)
-	 */
-	blk = ItemPointerGetBlockNumber(tid);
-	if (blk >= RelationGetNumberOfBlocks(relation))
-		elog(ERROR, "block number %u is out of range for relation \"%s\"",
-			 blk, RelationGetRelationName(relation));
-
-	/*
-	 * Loop to chase down t_ctid links.  At top of loop, ctid is the tuple we
-	 * need to examine, and *tid is the TID we will return if ctid turns out
-	 * to be bogus.
-	 *
-	 * Note that we will loop until we reach the end of the t_ctid chain.
-	 * Depending on the snapshot passed, there might be at most one visible
-	 * version of the row, but we don't try to optimize for that.
-	 */
-	ctid = *tid;
-	priorXmax = InvalidTransactionId;	/* cannot check first XMIN */
-	for (;;)
-	{
-		Buffer		buffer;
-		Page		page;
-		OffsetNumber offnum;
-		ItemId		lp;
-		HeapTupleData tp;
-		bool		valid;
-
-		/*
-		 * Read, pin, and lock the page.
-		 */
-		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&ctid));
-		LockBuffer(buffer, BUFFER_LOCK_SHARE);
-		page = BufferGetPage(buffer);
-		TestForOldSnapshot(snapshot, relation, page);
-
-		/*
-		 * Check for bogus item number.  This is not treated as an error
-		 * condition because it can happen while following a t_ctid link. We
-		 * just assume that the prior tid is OK and return it unchanged.
-		 */
-		offnum = ItemPointerGetOffsetNumber(&ctid);
-		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-		lp = PageGetItemId(page, offnum);
-		if (!ItemIdIsNormal(lp))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-
-		/* OK to access the tuple */
-		tp.t_self = ctid;
-		tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-		tp.t_len = ItemIdGetLength(lp);
-		tp.t_tableOid = RelationGetRelid(relation);
-
-		/*
-		 * After following a t_ctid link, we might arrive at an unrelated
-		 * tuple.  Check for XMIN match.
-		 */
-		if (TransactionIdIsValid(priorXmax) &&
-			!TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(tp.t_data)))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-
-		/*
-		 * Check time qualification of tuple; if visible, set it as the new
-		 * result candidate.
-		 */
-		valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, &tp, snapshot, buffer);
-		CheckForSerializableConflictOut(valid, relation, &tp, buffer, snapshot);
-		if (valid)
-			*tid = ctid;
-
-		/*
-		 * If there's a valid t_ctid link, follow it, else we're done.
-		 */
-		if ((tp.t_data->t_infomask & HEAP_XMAX_INVALID) ||
-			HeapTupleHeaderIsOnlyLocked(tp.t_data) ||
-			ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-
-		ctid = tp.t_data->t_ctid;
-		priorXmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
-		UnlockReleaseBuffer(buffer);
-	}							/* end of loop */
-}
-
 
 /*
  * UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends
@@ -2313,7 +1944,7 @@ heap_get_latest_tid(Relation relation,
  *
  * Note this is not allowed for tuples whose xmax is a multixact.
  */
-static void
+void
 UpdateXmaxHintBits(HeapTupleHeader tuple, Buffer buffer, TransactionId xid)
 {
 	Assert(TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple), xid));
@@ -2596,7 +2227,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
  * tuple if not. Note that in any case, the header fields are also set in
  * the original tuple.
  */
-static HeapTuple
+HeapTuple
 heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
 					CommandId cid, int options)
 {
@@ -2664,412 +2295,110 @@ heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
 }
 
 /*
- *	heap_multi_insert	- insert multiple tuple into a heap
+ *	simple_heap_insert - insert a tuple
+ *
+ * Currently, this routine differs from heap_insert only in supplying
+ * a default command ID and not allowing access to the speedup options.
  *
- * This is like heap_insert(), but inserts multiple tuples in one operation.
- * That's faster than calling heap_insert() in a loop, because when multiple
- * tuples can be inserted on a single page, we can write just a single WAL
- * record covering all of them, and only need to lock/unlock the page once.
+ * This should be used rather than using heap_insert directly in most places
+ * where we are modifying system catalogs.
+ */
+Oid
+simple_heap_insert(Relation relation, HeapTuple tup)
+{
+	return heap_insert(relation, tup, GetCurrentCommandId(true), 0, NULL);
+}
+
+/*
+ * Given infomask/infomask2, compute the bits that must be saved in the
+ * "infobits" field of xl_heap_delete, xl_heap_update, xl_heap_lock,
+ * xl_heap_lock_updated WAL records.
  *
- * Note: this leaks memory into the current memory context. You can create a
- * temporary context before calling this, if that's a problem.
+ * See fix_infomask_from_infobits.
  */
-void
-heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
-				  CommandId cid, int options, BulkInsertState bistate)
+uint8
+compute_infobits(uint16 infomask, uint16 infomask2)
+{
+	return
+		((infomask & HEAP_XMAX_IS_MULTI) != 0 ? XLHL_XMAX_IS_MULTI : 0) |
+		((infomask & HEAP_XMAX_LOCK_ONLY) != 0 ? XLHL_XMAX_LOCK_ONLY : 0) |
+		((infomask & HEAP_XMAX_EXCL_LOCK) != 0 ? XLHL_XMAX_EXCL_LOCK : 0) |
+	/* note we ignore HEAP_XMAX_SHR_LOCK here */
+		((infomask & HEAP_XMAX_KEYSHR_LOCK) != 0 ? XLHL_XMAX_KEYSHR_LOCK : 0) |
+		((infomask2 & HEAP_KEYS_UPDATED) != 0 ?
+		 XLHL_KEYS_UPDATED : 0);
+}
+
+
+
+/*
+ *	heap_delete - delete a tuple
+ *
+ * NB: do not call this directly unless you are prepared to deal with
+ * concurrent-update conditions.  Use simple_heap_delete instead.
+ *
+ *	relation - table to be modified (caller must hold suitable lock)
+ *	tid - TID of tuple to be deleted
+ *	cid - delete command ID (used for visibility test, and stored into
+ *		cmax if successful)
+ *	crosscheck - if not InvalidSnapshot, also check tuple against this
+ *	wait - true if should wait for any conflicting update to commit/abort
+ *	hufd - output parameter, filled in failure cases (see below)
+ *
+ * Normal, successful return value is HeapTupleMayBeUpdated, which
+ * actually means we did delete it.  Failure return codes are
+ * HeapTupleSelfUpdated, HeapTupleUpdated, or HeapTupleBeingUpdated
+ * (the last only possible if wait == false).
+ *
+ * In the failure cases, 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.
+ */
+HTSU_Result
+heap_delete(Relation relation, ItemPointer tid,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			HeapUpdateFailureData *hufd)
 {
+	HTSU_Result result;
 	TransactionId xid = GetCurrentTransactionId();
-	HeapTuple  *heaptuples;
-	int			i;
-	int			ndone;
-	char	   *scratch = NULL;
+	ItemId		lp;
+	HeapTupleData tp;
 	Page		page;
-	bool		needwal;
-	Size		saveFreeSpace;
-	bool		need_tuple_data = RelationIsLogicallyLogged(relation);
-	bool		need_cids = RelationIsAccessibleInLogicalDecoding(relation);
-
-	needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation);
-	saveFreeSpace = RelationGetTargetPageFreeSpace(relation,
-												   HEAP_DEFAULT_FILLFACTOR);
+	BlockNumber block;
+	Buffer		buffer;
+	Buffer		vmbuffer = InvalidBuffer;
+	TransactionId new_xmax;
+	uint16		new_infomask,
+				new_infomask2;
+	bool		have_tuple_lock = false;
+	bool		iscombo;
+	bool		all_visible_cleared = false;
+	HeapTuple	old_key_tuple = NULL;	/* replica identity of the tuple */
+	bool		old_key_copied = false;
 
-	/* Toast and set header data in all the tuples */
-	heaptuples = palloc(ntuples * sizeof(HeapTuple));
-	for (i = 0; i < ntuples; i++)
-		heaptuples[i] = heap_prepare_insert(relation, tuples[i],
-											xid, cid, options);
+	Assert(ItemPointerIsValid(tid));
 
 	/*
-	 * Allocate some memory to use for constructing the WAL record. Using
-	 * palloc() within a critical section is not safe, so we allocate this
-	 * beforehand.
+	 * Forbid this during a parallel operation, lest it allocate a combocid.
+	 * Other workers might need that combocid for visibility checks, and we
+	 * have no provision for broadcasting it to them.
 	 */
-	if (needwal)
-		scratch = palloc(BLCKSZ);
+	if (IsInParallelMode())
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+				 errmsg("cannot delete tuples during a parallel operation")));
+
+	block = ItemPointerGetBlockNumber(tid);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
 
 	/*
-	 * We're about to do the actual inserts -- but check for conflict first,
-	 * to minimize the possibility of having to roll back work we've just
-	 * done.
-	 *
-	 * A check here does not definitively prevent a serialization anomaly;
-	 * that check MUST be done at least past the point of acquiring an
-	 * exclusive buffer content lock on every buffer that will be affected,
-	 * and MAY be done after all inserts are reflected in the buffers and
-	 * those locks are released; otherwise there race condition.  Since
-	 * multiple buffers can be locked and unlocked in the loop below, and it
-	 * would not be feasible to identify and lock all of those buffers before
-	 * the loop, we must do a final check at the end.
-	 *
-	 * The check here could be omitted with no loss of correctness; it is
-	 * present strictly as an optimization.
-	 *
-	 * For heap inserts, we only need to check for table-level SSI locks. Our
-	 * new tuples can't possibly conflict with existing tuple locks, and heap
-	 * page locks are only consolidated versions of tuple locks; they do not
-	 * lock "gaps" as index page locks do.  So we don't need to specify a
-	 * buffer when making the call, which makes for a faster check.
-	 */
-	CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
-
-	ndone = 0;
-	while (ndone < ntuples)
-	{
-		Buffer		buffer;
-		Buffer		vmbuffer = InvalidBuffer;
-		bool		all_visible_cleared = false;
-		int			nthispage;
-
-		CHECK_FOR_INTERRUPTS();
-
-		/*
-		 * Find buffer where at least the next tuple will fit.  If the page is
-		 * all-visible, this will also pin the requisite visibility map page.
-		 */
-		buffer = RelationGetBufferForTuple(relation, heaptuples[ndone]->t_len,
-										   InvalidBuffer, options, bistate,
-										   &vmbuffer, NULL);
-		page = BufferGetPage(buffer);
-
-		/* NO EREPORT(ERROR) from here till changes are logged */
-		START_CRIT_SECTION();
-
-		/*
-		 * RelationGetBufferForTuple has ensured that the first tuple fits.
-		 * Put that on the page, and then as many other tuples as fit.
-		 */
-		RelationPutHeapTuple(relation, buffer, heaptuples[ndone], false);
-		for (nthispage = 1; ndone + nthispage < ntuples; nthispage++)
-		{
-			HeapTuple	heaptup = heaptuples[ndone + nthispage];
-
-			if (PageGetHeapFreeSpace(page) < MAXALIGN(heaptup->t_len) + saveFreeSpace)
-				break;
-
-			RelationPutHeapTuple(relation, buffer, heaptup, false);
-
-			/*
-			 * We don't use heap_multi_insert for catalog tuples yet, but
-			 * better be prepared...
-			 */
-			if (needwal && need_cids)
-				log_heap_new_cid(relation, heaptup);
-		}
-
-		if (PageIsAllVisible(page))
-		{
-			all_visible_cleared = true;
-			PageClearAllVisible(page);
-			visibilitymap_clear(relation,
-								BufferGetBlockNumber(buffer),
-								vmbuffer, VISIBILITYMAP_VALID_BITS);
-		}
-
-		/*
-		 * XXX Should we set PageSetPrunable on this page ? See heap_insert()
-		 */
-
-		MarkBufferDirty(buffer);
-
-		/* XLOG stuff */
-		if (needwal)
-		{
-			XLogRecPtr	recptr;
-			xl_heap_multi_insert *xlrec;
-			uint8		info = XLOG_HEAP2_MULTI_INSERT;
-			char	   *tupledata;
-			int			totaldatalen;
-			char	   *scratchptr = scratch;
-			bool		init;
-			int			bufflags = 0;
-
-			/*
-			 * If the page was previously empty, we can reinit the page
-			 * instead of restoring the whole thing.
-			 */
-			init = (ItemPointerGetOffsetNumber(&(heaptuples[ndone]->t_self)) == FirstOffsetNumber &&
-					PageGetMaxOffsetNumber(page) == FirstOffsetNumber + nthispage - 1);
-
-			/* allocate xl_heap_multi_insert struct from the scratch area */
-			xlrec = (xl_heap_multi_insert *) scratchptr;
-			scratchptr += SizeOfHeapMultiInsert;
-
-			/*
-			 * Allocate offsets array. Unless we're reinitializing the page,
-			 * in that case the tuples are stored in order starting at
-			 * FirstOffsetNumber and we don't need to store the offsets
-			 * explicitly.
-			 */
-			if (!init)
-				scratchptr += nthispage * sizeof(OffsetNumber);
-
-			/* the rest of the scratch space is used for tuple data */
-			tupledata = scratchptr;
-
-			xlrec->flags = all_visible_cleared ? XLH_INSERT_ALL_VISIBLE_CLEARED : 0;
-			xlrec->ntuples = nthispage;
-
-			/*
-			 * Write out an xl_multi_insert_tuple and the tuple data itself
-			 * for each tuple.
-			 */
-			for (i = 0; i < nthispage; i++)
-			{
-				HeapTuple	heaptup = heaptuples[ndone + i];
-				xl_multi_insert_tuple *tuphdr;
-				int			datalen;
-
-				if (!init)
-					xlrec->offsets[i] = ItemPointerGetOffsetNumber(&heaptup->t_self);
-				/* xl_multi_insert_tuple needs two-byte alignment. */
-				tuphdr = (xl_multi_insert_tuple *) SHORTALIGN(scratchptr);
-				scratchptr = ((char *) tuphdr) + SizeOfMultiInsertTuple;
-
-				tuphdr->t_infomask2 = heaptup->t_data->t_infomask2;
-				tuphdr->t_infomask = heaptup->t_data->t_infomask;
-				tuphdr->t_hoff = heaptup->t_data->t_hoff;
-
-				/* write bitmap [+ padding] [+ oid] + data */
-				datalen = heaptup->t_len - SizeofHeapTupleHeader;
-				memcpy(scratchptr,
-					   (char *) heaptup->t_data + SizeofHeapTupleHeader,
-					   datalen);
-				tuphdr->datalen = datalen;
-				scratchptr += datalen;
-			}
-			totaldatalen = scratchptr - tupledata;
-			Assert((scratchptr - scratch) < BLCKSZ);
-
-			if (need_tuple_data)
-				xlrec->flags |= XLH_INSERT_CONTAINS_NEW_TUPLE;
-
-			/*
-			 * Signal that this is the last xl_heap_multi_insert record
-			 * emitted by this call to heap_multi_insert(). Needed for logical
-			 * decoding so it knows when to cleanup temporary data.
-			 */
-			if (ndone + nthispage == ntuples)
-				xlrec->flags |= XLH_INSERT_LAST_IN_MULTI;
-
-			if (init)
-			{
-				info |= XLOG_HEAP_INIT_PAGE;
-				bufflags |= REGBUF_WILL_INIT;
-			}
-
-			/*
-			 * If we're doing logical decoding, include the new tuple data
-			 * even if we take a full-page image of the page.
-			 */
-			if (need_tuple_data)
-				bufflags |= REGBUF_KEEP_DATA;
-
-			XLogBeginInsert();
-			XLogRegisterData((char *) xlrec, tupledata - scratch);
-			XLogRegisterBuffer(0, buffer, REGBUF_STANDARD | bufflags);
-
-			XLogRegisterBufData(0, tupledata, totaldatalen);
-
-			/* filtering by origin on a row level is much more efficient */
-			XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
-
-			recptr = XLogInsert(RM_HEAP2_ID, info);
-
-			PageSetLSN(page, recptr);
-		}
-
-		END_CRIT_SECTION();
-
-		UnlockReleaseBuffer(buffer);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
-
-		ndone += nthispage;
-	}
-
-	/*
-	 * We're done with the actual inserts.  Check for conflicts again, to
-	 * ensure that all rw-conflicts in to these inserts are detected.  Without
-	 * this final check, a sequential scan of the heap may have locked the
-	 * table after the "before" check, missing one opportunity to detect the
-	 * conflict, and then scanned the table before the new tuples were there,
-	 * missing the other chance to detect the conflict.
-	 *
-	 * For heap inserts, we only need to check for table-level SSI locks. Our
-	 * new tuples can't possibly conflict with existing tuple locks, and heap
-	 * page locks are only consolidated versions of tuple locks; they do not
-	 * lock "gaps" as index page locks do.  So we don't need to specify a
-	 * buffer when making the call.
-	 */
-	CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
-
-	/*
-	 * If tuples are cachable, mark them for invalidation from the caches in
-	 * case we abort.  Note it is OK to do this after releasing the buffer,
-	 * because the heaptuples data structure is all in local memory, not in
-	 * the shared buffer.
-	 */
-	if (IsCatalogRelation(relation))
-	{
-		for (i = 0; i < ntuples; i++)
-			CacheInvalidateHeapTuple(relation, heaptuples[i], NULL);
-	}
-
-	/*
-	 * Copy t_self fields back to the caller's original tuples. This does
-	 * nothing for untoasted tuples (tuples[i] == heaptuples[i)], but it's
-	 * probably faster to always copy than check.
-	 */
-	for (i = 0; i < ntuples; i++)
-		tuples[i]->t_self = heaptuples[i]->t_self;
-
-	pgstat_count_heap_insert(relation, ntuples);
-}
-
-/*
- *	simple_heap_insert - insert a tuple
- *
- * Currently, this routine differs from heap_insert only in supplying
- * a default command ID and not allowing access to the speedup options.
- *
- * This should be used rather than using heap_insert directly in most places
- * where we are modifying system catalogs.
- */
-Oid
-simple_heap_insert(Relation relation, HeapTuple tup)
-{
-	return heap_insert(relation, tup, GetCurrentCommandId(true), 0, NULL);
-}
-
-/*
- * Given infomask/infomask2, compute the bits that must be saved in the
- * "infobits" field of xl_heap_delete, xl_heap_update, xl_heap_lock,
- * xl_heap_lock_updated WAL records.
- *
- * See fix_infomask_from_infobits.
- */
-static uint8
-compute_infobits(uint16 infomask, uint16 infomask2)
-{
-	return
-		((infomask & HEAP_XMAX_IS_MULTI) != 0 ? XLHL_XMAX_IS_MULTI : 0) |
-		((infomask & HEAP_XMAX_LOCK_ONLY) != 0 ? XLHL_XMAX_LOCK_ONLY : 0) |
-		((infomask & HEAP_XMAX_EXCL_LOCK) != 0 ? XLHL_XMAX_EXCL_LOCK : 0) |
-	/* note we ignore HEAP_XMAX_SHR_LOCK here */
-		((infomask & HEAP_XMAX_KEYSHR_LOCK) != 0 ? XLHL_XMAX_KEYSHR_LOCK : 0) |
-		((infomask2 & HEAP_KEYS_UPDATED) != 0 ?
-		 XLHL_KEYS_UPDATED : 0);
-}
-
-/*
- * Given two versions of the same t_infomask for a tuple, compare them and
- * return whether the relevant status for a tuple Xmax has changed.  This is
- * used after a buffer lock has been released and reacquired: we want to ensure
- * that the tuple state continues to be the same it was when we previously
- * examined it.
- *
- * Note the Xmax field itself must be compared separately.
- */
-static inline bool
-xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask)
-{
-	const uint16 interesting =
-	HEAP_XMAX_IS_MULTI | HEAP_XMAX_LOCK_ONLY | HEAP_LOCK_MASK;
-
-	if ((new_infomask & interesting) != (old_infomask & interesting))
-		return true;
-
-	return false;
-}
-
-/*
- *	heap_delete - delete a tuple
- *
- * NB: do not call this directly unless you are prepared to deal with
- * concurrent-update conditions.  Use simple_heap_delete instead.
- *
- *	relation - table to be modified (caller must hold suitable lock)
- *	tid - TID of tuple to be deleted
- *	cid - delete command ID (used for visibility test, and stored into
- *		cmax if successful)
- *	crosscheck - if not InvalidSnapshot, also check tuple against this
- *	wait - true if should wait for any conflicting update to commit/abort
- *	hufd - output parameter, filled in failure cases (see below)
- *
- * Normal, successful return value is HeapTupleMayBeUpdated, which
- * actually means we did delete it.  Failure return codes are
- * HeapTupleSelfUpdated, HeapTupleUpdated, or HeapTupleBeingUpdated
- * (the last only possible if wait == false).
- *
- * In the failure cases, 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.
- */
-HTSU_Result
-heap_delete(Relation relation, ItemPointer tid,
-			CommandId cid, Snapshot crosscheck, bool wait,
-			HeapUpdateFailureData *hufd)
-{
-	HTSU_Result result;
-	TransactionId xid = GetCurrentTransactionId();
-	ItemId		lp;
-	HeapTupleData tp;
-	Page		page;
-	BlockNumber block;
-	Buffer		buffer;
-	Buffer		vmbuffer = InvalidBuffer;
-	TransactionId new_xmax;
-	uint16		new_infomask,
-				new_infomask2;
-	bool		have_tuple_lock = false;
-	bool		iscombo;
-	bool		all_visible_cleared = false;
-	HeapTuple	old_key_tuple = NULL;	/* replica identity of the tuple */
-	bool		old_key_copied = false;
-
-	Assert(ItemPointerIsValid(tid));
-
-	/*
-	 * Forbid this during a parallel operation, lest it allocate a combocid.
-	 * Other workers might need that combocid for visibility checks, and we
-	 * have no provision for broadcasting it to them.
-	 */
-	if (IsInParallelMode())
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
-				 errmsg("cannot delete tuples during a parallel operation")));
-
-	block = ItemPointerGetBlockNumber(tid);
-	buffer = ReadBuffer(relation, block);
-	page = BufferGetPage(buffer);
-
-	/*
-	 * Before locking the buffer, pin the visibility map page if it appears to
-	 * be necessary.  Since we haven't got the lock yet, someone else might be
-	 * in the middle of changing this, so we'll need to recheck after we have
-	 * the lock.
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
 	 */
 	if (PageIsAllVisible(page))
 		visibilitymap_pin(relation, block, &vmbuffer);
@@ -4504,7 +3833,7 @@ simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup)
 /*
  * Return the MultiXactStatus corresponding to the given tuple lock mode.
  */
-static MultiXactStatus
+MultiXactStatus
 get_mxact_status_for_lock(LockTupleMode mode, bool is_update)
 {
 	int			retval;
@@ -4522,724 +3851,34 @@ get_mxact_status_for_lock(LockTupleMode mode, bool is_update)
 }
 
 /*
- *	heap_lock_tuple - lock a tuple in shared or exclusive mode
- *
- * Note that this acquires a buffer pin, which the caller must release.
- *
- * Input parameters:
- *	relation: relation containing tuple (caller must hold suitable lock)
- *	tuple->t_self: TID of tuple to lock (rest of struct need not be valid)
- *	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
- *	follow_updates: if true, follow the update chain to also lock descendant
- *		tuples.
- *
- * Output parameters:
- *	*tuple: all fields filled in
- *	*buffer: set to buffer holding tuple (pinned but not locked at exit)
- *	*hufd: filled in failure cases (see below)
- *
- * 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
+ * Acquire heavyweight lock on the given tuple, in preparation for acquiring
+ * its normal, Xmax-based tuple lock.
  *
- * 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.
+ * have_tuple_lock is an input and output parameter: on input, it indicates
+ * whether the lock has previously been acquired (and this function does
+ * nothing in that case).  If this function returns success, have_tuple_lock
+ * has been flipped to true.
  *
- * See README.tuplock for a thorough explanation of this mechanism.
+ * Returns false if it was unable to obtain the lock; this can only happen if
+ * wait_policy is Skip.
  */
-HTSU_Result
-heap_lock_tuple(Relation relation, HeapTuple tuple,
-				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
-				bool follow_updates,
-				Buffer *buffer, HeapUpdateFailureData *hufd)
+bool
+heap_acquire_tuplock(Relation relation, ItemPointer tid, LockTupleMode mode,
+					 LockWaitPolicy wait_policy, bool *have_tuple_lock)
 {
-	HTSU_Result result;
-	ItemPointer tid = &(tuple->t_self);
-	ItemId		lp;
-	Page		page;
-	Buffer		vmbuffer = InvalidBuffer;
-	BlockNumber block;
-	TransactionId xid,
-				xmax;
-	uint16		old_infomask,
-				new_infomask,
-				new_infomask2;
-	bool		first_time = true;
-	bool		have_tuple_lock = false;
-	bool		cleared_all_frozen = false;
-
-	*buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
-	block = ItemPointerGetBlockNumber(tid);
-
-	/*
-	 * Before locking the buffer, pin the visibility map page if it appears to
-	 * be necessary.  Since we haven't got the lock yet, someone else might be
-	 * in the middle of changing this, so we'll need to recheck after we have
-	 * the lock.
-	 */
-	if (PageIsAllVisible(BufferGetPage(*buffer)))
-		visibilitymap_pin(relation, block, &vmbuffer);
-
-	LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-	page = BufferGetPage(*buffer);
-	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
-	Assert(ItemIdIsNormal(lp));
+	if (*have_tuple_lock)
+		return true;
 
-	tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	tuple->t_len = ItemIdGetLength(lp);
-	tuple->t_tableOid = RelationGetRelid(relation);
+	switch (wait_policy)
+	{
+		case LockWaitBlock:
+			LockTupleTuplock(relation, tid, mode);
+			break;
 
-l3:
-	result = relation->rd_stamroutine->snapshot_satisfiesUpdate(tuple, cid, *buffer);
-
-	if (result == HeapTupleInvisible)
-	{
-		/*
-		 * This is possible, but only when locking a tuple for ON CONFLICT
-		 * UPDATE.  We return this value here rather than throwing an error in
-		 * order to give that case the opportunity to throw a more specific
-		 * error.
-		 */
-		result = HeapTupleInvisible;
-		goto out_locked;
-	}
-	else if (result == HeapTupleBeingUpdated || result == HeapTupleUpdated)
-	{
-		TransactionId xwait;
-		uint16		infomask;
-		uint16		infomask2;
-		bool		require_sleep;
-		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);
-
-		LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
-
-		/*
-		 * If any subtransaction of the current top transaction already holds
-		 * a lock as strong as or stronger than what we're requesting, we
-		 * effectively hold the desired lock already.  We *must* succeed
-		 * without trying to take the tuple lock, else we will deadlock
-		 * against anyone wanting to acquire a stronger lock.
-		 *
-		 * Note we only do this the first time we loop on the HTSU result;
-		 * there is no point in testing in subsequent passes, because
-		 * evidently our own transaction cannot have acquired a new lock after
-		 * the first time we checked.
-		 */
-		if (first_time)
-		{
-			first_time = false;
-
-			if (infomask & HEAP_XMAX_IS_MULTI)
-			{
-				int			i;
-				int			nmembers;
-				MultiXactMember *members;
-
-				/*
-				 * We don't need to allow old multixacts here; if that had
-				 * been the case, HeapTupleSatisfiesUpdate would have returned
-				 * MayBeUpdated and we wouldn't be here.
-				 */
-				nmembers =
-					GetMultiXactIdMembers(xwait, &members, false,
-										  HEAP_XMAX_IS_LOCKED_ONLY(infomask));
-
-				for (i = 0; i < nmembers; i++)
-				{
-					/* only consider members of our own transaction */
-					if (!TransactionIdIsCurrentTransactionId(members[i].xid))
-						continue;
-
-					if (TUPLOCK_from_mxstatus(members[i].status) >= mode)
-					{
-						pfree(members);
-						result = HeapTupleMayBeUpdated;
-						goto out_unlocked;
-					}
-				}
-
-				if (members)
-					pfree(members);
-			}
-			else if (TransactionIdIsCurrentTransactionId(xwait))
-			{
-				switch (mode)
-				{
-					case LockTupleKeyShare:
-						Assert(HEAP_XMAX_IS_KEYSHR_LOCKED(infomask) ||
-							   HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
-							   HEAP_XMAX_IS_EXCL_LOCKED(infomask));
-						result = HeapTupleMayBeUpdated;
-						goto out_unlocked;
-					case LockTupleShare:
-						if (HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
-							HEAP_XMAX_IS_EXCL_LOCKED(infomask))
-						{
-							result = HeapTupleMayBeUpdated;
-							goto out_unlocked;
-						}
-						break;
-					case LockTupleNoKeyExclusive:
-						if (HEAP_XMAX_IS_EXCL_LOCKED(infomask))
-						{
-							result = HeapTupleMayBeUpdated;
-							goto out_unlocked;
-						}
-						break;
-					case LockTupleExclusive:
-						if (HEAP_XMAX_IS_EXCL_LOCKED(infomask) &&
-							infomask2 & HEAP_KEYS_UPDATED)
-						{
-							result = HeapTupleMayBeUpdated;
-							goto out_unlocked;
-						}
-						break;
-				}
-			}
-		}
-
-		/*
-		 * Initially assume that we will have to wait for the locking
-		 * transaction(s) to finish.  We check various cases below in which
-		 * this can be turned off.
-		 */
-		require_sleep = true;
-		if (mode == LockTupleKeyShare)
-		{
-			/*
-			 * If we're requesting KeyShare, and there's no update present, we
-			 * don't need to wait.  Even if there is an update, we can still
-			 * continue if the key hasn't been modified.
-			 *
-			 * However, if there are updates, we need to walk the update chain
-			 * to mark future versions of the row as locked, too.  That way,
-			 * if somebody deletes that future version, we're protected
-			 * against the key going away.  This locking of future versions
-			 * could block momentarily, if a concurrent transaction is
-			 * deleting a key; or it could return a value to the effect that
-			 * the transaction deleting the key has already committed.  So we
-			 * do this before re-locking the buffer; otherwise this would be
-			 * prone to deadlocks.
-			 *
-			 * Note that the TID we're locking was grabbed before we unlocked
-			 * the buffer.  For it to change while we're not looking, the
-			 * other properties we're testing for below after re-locking the
-			 * buffer would also change, in which case we would restart this
-			 * loop above.
-			 */
-			if (!(infomask2 & HEAP_KEYS_UPDATED))
-			{
-				bool		updated;
-
-				updated = !HEAP_XMAX_IS_LOCKED_ONLY(infomask);
-
-				/*
-				 * If there are updates, follow the update chain; bail out if
-				 * that cannot be done.
-				 */
-				if (follow_updates && updated)
-				{
-					HTSU_Result res;
-
-					res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
-												  GetCurrentTransactionId(),
-												  mode);
-					if (res != HeapTupleMayBeUpdated)
-					{
-						result = res;
-						/* recovery code expects to have buffer lock held */
-						LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-						goto failed;
-					}
-				}
-
-				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-				/*
-				 * Make sure it's still an appropriate lock, else start over.
-				 * Also, if it wasn't updated before we released the lock, but
-				 * is updated now, we start over too; the reason is that we
-				 * 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) ||
-					 !updated))
-					goto l3;
-
-				/* Things look okay, so we can skip sleeping */
-				require_sleep = false;
-
-				/*
-				 * Note we allow Xmax to change here; other updaters/lockers
-				 * could have modified it before we grabbed the buffer lock.
-				 * However, this is not a problem, because with the recheck we
-				 * just did we ensure that they still don't conflict with the
-				 * lock we want.
-				 */
-			}
-		}
-		else if (mode == LockTupleShare)
-		{
-			/*
-			 * If we're requesting Share, we can similarly avoid sleeping if
-			 * there's no update and no exclusive lock present.
-			 */
-			if (HEAP_XMAX_IS_LOCKED_ONLY(infomask) &&
-				!HEAP_XMAX_IS_EXCL_LOCKED(infomask))
-			{
-				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-				/*
-				 * 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))
-					goto l3;
-				require_sleep = false;
-			}
-		}
-		else if (mode == LockTupleNoKeyExclusive)
-		{
-			/*
-			 * If we're requesting NoKeyExclusive, we might also be able to
-			 * avoid sleeping; just ensure that there no conflicting lock
-			 * already acquired.
-			 */
-			if (infomask & HEAP_XMAX_IS_MULTI)
-			{
-				if (!DoesMultiXactIdConflict((MultiXactId) xwait, infomask,
-											 mode))
-				{
-					/*
-					 * No conflict, but if the xmax changed under us 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),
-											 xwait))
-						goto l3;
-
-					/* otherwise, we're good */
-					require_sleep = false;
-				}
-			}
-			else if (HEAP_XMAX_IS_KEYSHR_LOCKED(infomask))
-			{
-				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-				/* if the xmax changed in the meantime, start over */
-				if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
-					!TransactionIdEquals(
-										 HeapTupleHeaderGetRawXmax(tuple->t_data),
-										 xwait))
-					goto l3;
-				/* otherwise, we're good */
-				require_sleep = false;
-			}
-		}
-
-		/*
-		 * As a check independent from those above, we can also avoid sleeping
-		 * if the current transaction is the sole locker of the tuple.  Note
-		 * that the strength of the lock already held is irrelevant; this is
-		 * not about recording the lock in Xmax (which will be done regardless
-		 * of this optimization, below).  Also, note that the cases where we
-		 * hold a lock stronger than we are requesting are already handled
-		 * above by not doing anything.
-		 *
-		 * Note we only deal with the non-multixact case here; MultiXactIdWait
-		 * is well equipped to deal with this situation on its own.
-		 */
-		if (require_sleep && !(infomask & HEAP_XMAX_IS_MULTI) &&
-			TransactionIdIsCurrentTransactionId(xwait))
-		{
-			/* ... 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),
-									 xwait))
-				goto l3;
-			Assert(HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask));
-			require_sleep = false;
-		}
-
-		/*
-		 * Time to sleep on the other transaction/multixact, if necessary.
-		 *
-		 * If the other transaction is an update that's already committed,
-		 * then sleeping cannot possibly do any good: if we're required to
-		 * sleep, get out to raise an error instead.
-		 *
-		 * By here, we either have already acquired the buffer exclusive lock,
-		 * 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)
-		{
-			LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-			goto failed;
-		}
-		else if (require_sleep)
-		{
-			/*
-			 * Acquire tuple lock to establish our priority for the tuple, or
-			 * die trying.  LockTuple will release us when we are next-in-line
-			 * for the tuple.  We must do this even if we are share-locking.
-			 *
-			 * If we are forced to "start over" below, we keep the tuple lock;
-			 * this arranges that we stay at the head of the line while
-			 * rechecking tuple state.
-			 */
-			if (!heap_acquire_tuplock(relation, tid, mode, wait_policy,
-									  &have_tuple_lock))
-			{
-				/*
-				 * This can only happen if wait_policy is Skip and the lock
-				 * couldn't be obtained.
-				 */
-				result = HeapTupleWouldBlock;
-				/* recovery code expects to have buffer lock held */
-				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-				goto failed;
-			}
-
-			if (infomask & HEAP_XMAX_IS_MULTI)
-			{
-				MultiXactStatus status = get_mxact_status_for_lock(mode, false);
-
-				/* We only ever lock tuples, never update them */
-				if (status >= MultiXactStatusNoKeyUpdate)
-					elog(ERROR, "invalid lock mode in heap_lock_tuple");
-
-				/* wait for multixact to end, or die trying  */
-				switch (wait_policy)
-				{
-					case LockWaitBlock:
-						MultiXactIdWait((MultiXactId) xwait, status, infomask,
-										relation, &tuple->t_self, XLTW_Lock, NULL);
-						break;
-					case LockWaitSkip:
-						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
-														status, infomask, relation,
-														NULL))
-						{
-							result = HeapTupleWouldBlock;
-							/* recovery code expects to have buffer lock held */
-							LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-							goto failed;
-						}
-						break;
-					case LockWaitError:
-						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
-														status, infomask, relation,
-														NULL))
-							ereport(ERROR,
-									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-									 errmsg("could not obtain lock on row in relation \"%s\"",
-											RelationGetRelationName(relation))));
-
-						break;
-				}
-
-				/*
-				 * Of course, the multixact might not be done here: if we're
-				 * requesting a light lock mode, other transactions with light
-				 * locks could still be alive, as well as locks owned by our
-				 * own xact or other subxacts of this backend.  We need to
-				 * preserve the surviving MultiXact members.  Note that it
-				 * isn't absolutely necessary in the latter case, but doing so
-				 * is simpler.
-				 */
-			}
-			else
-			{
-				/* wait for regular transaction to end, or die trying */
-				switch (wait_policy)
-				{
-					case LockWaitBlock:
-						XactLockTableWait(xwait, relation, &tuple->t_self,
-										  XLTW_Lock);
-						break;
-					case LockWaitSkip:
-						if (!ConditionalXactLockTableWait(xwait))
-						{
-							result = HeapTupleWouldBlock;
-							/* recovery code expects to have buffer lock held */
-							LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-							goto failed;
-						}
-						break;
-					case LockWaitError:
-						if (!ConditionalXactLockTableWait(xwait))
-							ereport(ERROR,
-									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-									 errmsg("could not obtain lock on row in relation \"%s\"",
-											RelationGetRelationName(relation))));
-						break;
-				}
-			}
-
-			/* if there are updates, follow the update chain */
-			if (follow_updates && !HEAP_XMAX_IS_LOCKED_ONLY(infomask))
-			{
-				HTSU_Result res;
-
-				res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
-											  GetCurrentTransactionId(),
-											  mode);
-				if (res != HeapTupleMayBeUpdated)
-				{
-					result = res;
-					/* recovery code expects to have buffer lock held */
-					LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-					goto failed;
-				}
-			}
-
-			LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-			/*
-			 * xwait is done, but if xwait had just locked the tuple then some
-			 * 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),
-									 xwait))
-				goto l3;
-
-			if (!(infomask & HEAP_XMAX_IS_MULTI))
-			{
-				/*
-				 * Otherwise check if it committed or aborted.  Note we cannot
-				 * be here if the tuple was only locked by somebody who didn't
-				 * conflict with us; that would have been handled above.  So
-				 * that transaction must necessarily be gone by now.  But
-				 * don't check for this in the multixact case, because some
-				 * locker transactions might still be running.
-				 */
-				UpdateXmaxHintBits(tuple->t_data, *buffer, xwait);
-			}
-		}
-
-		/* By here, we're certain that we hold buffer exclusive lock again */
-
-		/*
-		 * We may lock if previous xmax aborted, or if it committed but only
-		 * locked the tuple without updating it; or if we didn't have to wait
-		 * 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))
-			result = HeapTupleMayBeUpdated;
-		else
-			result = HeapTupleUpdated;
-	}
-
-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);
-		if (result == HeapTupleSelfUpdated)
-			hufd->cmax = HeapTupleHeaderGetCmax(tuple->t_data);
-		else
-			hufd->cmax = InvalidCommandId;
-		goto out_locked;
-	}
-
-	/*
-	 * If we didn't pin the visibility map page and the page has become all
-	 * visible while we were busy locking the buffer, or during some
-	 * subsequent window during which we had it unlocked, we'll have to unlock
-	 * and re-lock, to avoid holding the buffer lock across I/O.  That's a bit
-	 * unfortunate, especially since we'll now have to recheck whether the
-	 * tuple has been locked or updated under us, but hopefully it won't
-	 * happen very often.
-	 */
-	if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
-	{
-		LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
-		visibilitymap_pin(relation, block, &vmbuffer);
-		LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-		goto l3;
-	}
-
-	xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
-	old_infomask = tuple->t_data->t_infomask;
-
-	/*
-	 * If this is the first possibly-multixact-able operation in the current
-	 * transaction, set my per-backend OldestMemberMXactId setting. We can be
-	 * certain that the transaction will never become a member of any older
-	 * MultiXactIds than that.  (We have to do this even if we end up just
-	 * using our own TransactionId below, since some other backend could
-	 * incorporate our XID into a MultiXact immediately afterwards.)
-	 */
-	MultiXactIdSetOldestMember();
-
-	/*
-	 * Compute the new xmax and infomask to store into the tuple.  Note we do
-	 * 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,
-							  GetCurrentTransactionId(), mode, false,
-							  &xid, &new_infomask, &new_infomask2);
-
-	START_CRIT_SECTION();
-
-	/*
-	 * Store transaction information of xact locking the tuple.
-	 *
-	 * Note: Cmax is meaningless in this context, so don't set it; this avoids
-	 * possibly generating a useless combo CID.  Moreover, if we're locking a
-	 * previously updated tuple, it's important to preserve the Cmax.
-	 *
-	 * 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;
-	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
-		HeapTupleHeaderClearHotUpdated(tuple->t_data);
-	HeapTupleHeaderSetXmax(tuple->t_data, xid);
-
-	/*
-	 * Make sure there is no forward chain link in t_ctid.  Note that in the
-	 * cases where the tuple has been updated, we must not overwrite t_ctid,
-	 * because it was set by the updater.  Moreover, if the tuple has been
-	 * updated, we need to follow the update chain to lock the new versions of
-	 * the tuple as well.
-	 */
-	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
-		tuple->t_data->t_ctid = *tid;
-
-	/* Clear only the all-frozen bit on visibility map if needed */
-	if (PageIsAllVisible(page) &&
-		visibilitymap_clear(relation, block, vmbuffer,
-							VISIBILITYMAP_ALL_FROZEN))
-		cleared_all_frozen = true;
-
-
-	MarkBufferDirty(*buffer);
-
-	/*
-	 * XLOG stuff.  You might think that we don't need an XLOG record because
-	 * there is no state change worth restoring after a crash.  You would be
-	 * wrong however: we have just written either a TransactionId or a
-	 * MultiXactId that may never have been seen on disk before, and we need
-	 * to make sure that there are XLOG entries covering those ID numbers.
-	 * Else the same IDs might be re-used after a crash, which would be
-	 * disastrous if this page made it to disk before the crash.  Essentially
-	 * we have to enforce the WAL log-before-data rule even in this case.
-	 * (Also, in a PITR log-shipping or 2PC environment, we have to have XLOG
-	 * entries for everything anyway.)
-	 */
-	if (RelationNeedsWAL(relation))
-	{
-		xl_heap_lock xlrec;
-		XLogRecPtr	recptr;
-
-		XLogBeginInsert();
-		XLogRegisterBuffer(0, *buffer, REGBUF_STANDARD);
-
-		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
-		xlrec.locking_xid = xid;
-		xlrec.infobits_set = compute_infobits(new_infomask,
-											  tuple->t_data->t_infomask2);
-		xlrec.flags = cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
-		XLogRegisterData((char *) &xlrec, SizeOfHeapLock);
-
-		/* we don't decode row locks atm, so no need to log the origin */
-
-		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_LOCK);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	result = HeapTupleMayBeUpdated;
-
-out_locked:
-	LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
-
-out_unlocked:
-	if (BufferIsValid(vmbuffer))
-		ReleaseBuffer(vmbuffer);
-
-	/*
-	 * Don't update the visibility map here. Locking a tuple doesn't change
-	 * visibility info.
-	 */
-
-	/*
-	 * Now that we have successfully marked the tuple as locked, we can
-	 * release the lmgr tuple lock, if we had it.
-	 */
-	if (have_tuple_lock)
-		UnlockTupleTuplock(relation, tid, mode);
-
-	return result;
-}
-
-/*
- * Acquire heavyweight lock on the given tuple, in preparation for acquiring
- * its normal, Xmax-based tuple lock.
- *
- * have_tuple_lock is an input and output parameter: on input, it indicates
- * whether the lock has previously been acquired (and this function does
- * nothing in that case).  If this function returns success, have_tuple_lock
- * has been flipped to true.
- *
- * Returns false if it was unable to obtain the lock; this can only happen if
- * wait_policy is Skip.
- */
-static bool
-heap_acquire_tuplock(Relation relation, ItemPointer tid, LockTupleMode mode,
-					 LockWaitPolicy wait_policy, bool *have_tuple_lock)
-{
-	if (*have_tuple_lock)
-		return true;
-
-	switch (wait_policy)
-	{
-		case LockWaitBlock:
-			LockTupleTuplock(relation, tid, mode);
-			break;
-
-		case LockWaitSkip:
-			if (!ConditionalLockTupleTuplock(relation, tid, mode))
-				return false;
-			break;
+		case LockWaitSkip:
+			if (!ConditionalLockTupleTuplock(relation, tid, mode))
+				return false;
+			break;
 
 		case LockWaitError:
 			if (!ConditionalLockTupleTuplock(relation, tid, mode))
@@ -5272,7 +3911,7 @@ heap_acquire_tuplock(Relation relation, ItemPointer tid, LockTupleMode mode,
  * window, but it's still possible to end up creating an unnecessary
  * MultiXactId.  Fortunately this is harmless.
  */
-static void
+void
 compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
 						  uint16 old_infomask2, TransactionId add_to_xmax,
 						  LockTupleMode mode, bool is_update,
@@ -5319,916 +3958,226 @@ l5:
 					break;
 				case LockTupleNoKeyExclusive:
 					new_xmax = add_to_xmax;
-					new_infomask |= HEAP_XMAX_EXCL_LOCK;
-					break;
-				case LockTupleExclusive:
-					new_xmax = add_to_xmax;
-					new_infomask |= HEAP_XMAX_EXCL_LOCK;
-					new_infomask2 |= HEAP_KEYS_UPDATED;
-					break;
-				default:
-					new_xmax = InvalidTransactionId;	/* silence compiler */
-					elog(ERROR, "invalid lock mode");
-			}
-		}
-	}
-	else if (old_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		MultiXactStatus new_status;
-
-		/*
-		 * Currently we don't allow XMAX_COMMITTED to be set for multis, so
-		 * cross-check.
-		 */
-		Assert(!(old_infomask & HEAP_XMAX_COMMITTED));
-
-		/*
-		 * A multixact together with LOCK_ONLY set but neither lock bit set
-		 * (i.e. a pg_upgraded share locked tuple) cannot possibly be running
-		 * anymore.  This check is critical for databases upgraded by
-		 * pg_upgrade; both MultiXactIdIsRunning and MultiXactIdExpand assume
-		 * that such multis are never passed.
-		 */
-		if (HEAP_LOCKED_UPGRADED(old_infomask))
-		{
-			old_infomask &= ~HEAP_XMAX_IS_MULTI;
-			old_infomask |= HEAP_XMAX_INVALID;
-			goto l5;
-		}
-
-		/*
-		 * If the XMAX is already a MultiXactId, then we need to expand it to
-		 * include add_to_xmax; but if all the members were lockers and are
-		 * all gone, we can do away with the IS_MULTI bit and just set
-		 * add_to_xmax as the only locker/updater.  If all lockers are gone
-		 * and we have an updater that aborted, we can also do without a
-		 * multi.
-		 *
-		 * The cost of doing GetMultiXactIdMembers would be paid by
-		 * MultiXactIdExpand if we weren't to do this, so this check is not
-		 * incurring extra work anyhow.
-		 */
-		if (!MultiXactIdIsRunning(xmax, HEAP_XMAX_IS_LOCKED_ONLY(old_infomask)))
-		{
-			if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) ||
-				!TransactionIdDidCommit(MultiXactIdGetUpdateXid(xmax,
-																old_infomask)))
-			{
-				/*
-				 * Reset these bits and restart; otherwise fall through to
-				 * create a new multi below.
-				 */
-				old_infomask &= ~HEAP_XMAX_IS_MULTI;
-				old_infomask |= HEAP_XMAX_INVALID;
-				goto l5;
-			}
-		}
-
-		new_status = get_mxact_status_for_lock(mode, is_update);
-
-		new_xmax = MultiXactIdExpand((MultiXactId) xmax, add_to_xmax,
-									 new_status);
-		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
-	}
-	else if (old_infomask & HEAP_XMAX_COMMITTED)
-	{
-		/*
-		 * It's a committed update, so we need to preserve him as updater of
-		 * the tuple.
-		 */
-		MultiXactStatus status;
-		MultiXactStatus new_status;
-
-		if (old_infomask2 & HEAP_KEYS_UPDATED)
-			status = MultiXactStatusUpdate;
-		else
-			status = MultiXactStatusNoKeyUpdate;
-
-		new_status = get_mxact_status_for_lock(mode, is_update);
-
-		/*
-		 * since it's not running, it's obviously impossible for the old
-		 * updater to be identical to the current one, so we need not check
-		 * for that case as we do in the block above.
-		 */
-		new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
-		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
-	}
-	else if (TransactionIdIsInProgress(xmax))
-	{
-		/*
-		 * If the XMAX is a valid, in-progress TransactionId, then we need to
-		 * create a new MultiXactId that includes both the old locker or
-		 * updater and our own TransactionId.
-		 */
-		MultiXactStatus new_status;
-		MultiXactStatus old_status;
-		LockTupleMode old_mode;
-
-		if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
-		{
-			if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
-				old_status = MultiXactStatusForKeyShare;
-			else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
-				old_status = MultiXactStatusForShare;
-			else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
-			{
-				if (old_infomask2 & HEAP_KEYS_UPDATED)
-					old_status = MultiXactStatusForUpdate;
-				else
-					old_status = MultiXactStatusForNoKeyUpdate;
-			}
-			else
-			{
-				/*
-				 * LOCK_ONLY can be present alone only when a page has been
-				 * upgraded by pg_upgrade.  But in that case,
-				 * TransactionIdIsInProgress() should have returned false.  We
-				 * assume it's no longer locked in this case.
-				 */
-				elog(WARNING, "LOCK_ONLY found for Xid in progress %u", xmax);
-				old_infomask |= HEAP_XMAX_INVALID;
-				old_infomask &= ~HEAP_XMAX_LOCK_ONLY;
-				goto l5;
-			}
-		}
-		else
-		{
-			/* it's an update, but which kind? */
-			if (old_infomask2 & HEAP_KEYS_UPDATED)
-				old_status = MultiXactStatusUpdate;
-			else
-				old_status = MultiXactStatusNoKeyUpdate;
-		}
-
-		old_mode = TUPLOCK_from_mxstatus(old_status);
-
-		/*
-		 * If the lock to be acquired is for the same TransactionId as the
-		 * existing lock, there's an optimization possible: consider only the
-		 * strongest of both locks as the only one present, and restart.
-		 */
-		if (xmax == add_to_xmax)
-		{
-			/*
-			 * Note that it's not possible for the original tuple to be
-			 * updated: we wouldn't be here because the tuple would have been
-			 * invisible and we wouldn't try to update it.  As a subtlety,
-			 * this code can also run when traversing an update chain to lock
-			 * future versions of a tuple.  But we wouldn't be here either,
-			 * because the add_to_xmax would be different from the original
-			 * updater.
-			 */
-			Assert(HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
-
-			/* acquire the strongest of both */
-			if (mode < old_mode)
-				mode = old_mode;
-			/* mustn't touch is_update */
-
-			old_infomask |= HEAP_XMAX_INVALID;
-			goto l5;
-		}
-
-		/* otherwise, just fall back to creating a new multixact */
-		new_status = get_mxact_status_for_lock(mode, is_update);
-		new_xmax = MultiXactIdCreate(xmax, old_status,
-									 add_to_xmax, new_status);
-		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
-	}
-	else if (!HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) &&
-			 TransactionIdDidCommit(xmax))
-	{
-		/*
-		 * It's a committed update, so we gotta preserve him as updater of the
-		 * tuple.
-		 */
-		MultiXactStatus status;
-		MultiXactStatus new_status;
-
-		if (old_infomask2 & HEAP_KEYS_UPDATED)
-			status = MultiXactStatusUpdate;
-		else
-			status = MultiXactStatusNoKeyUpdate;
-
-		new_status = get_mxact_status_for_lock(mode, is_update);
-
-		/*
-		 * since it's not running, it's obviously impossible for the old
-		 * updater to be identical to the current one, so we need not check
-		 * for that case as we do in the block above.
-		 */
-		new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
-		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
-	}
-	else
-	{
-		/*
-		 * Can get here iff the locking/updating transaction was running when
-		 * the infomask was extracted from the tuple, but finished before
-		 * TransactionIdIsInProgress got to run.  Deal with it as if there was
-		 * no locker at all in the first place.
-		 */
-		old_infomask |= HEAP_XMAX_INVALID;
-		goto l5;
-	}
-
-	*result_infomask = new_infomask;
-	*result_infomask2 = new_infomask2;
-	*result_xmax = new_xmax;
-}
-
-/*
- * Subroutine for heap_lock_updated_tuple_rec.
- *
- * Given a hypothetical multixact status held by the transaction identified
- * with the given xid, does the current transaction need to wait, fail, or can
- * it continue if it wanted to acquire a lock of the given mode?  "needwait"
- * is set to true if waiting is necessary; if it can continue, then
- * HeapTupleMayBeUpdated is returned.  If the lock is already held by the
- * current transaction, return HeapTupleSelfUpdated.  In case of a conflict
- * with another transaction, a different HeapTupleSatisfiesUpdate return code
- * is returned.
- *
- * The held status is said to be hypothetical because it might correspond to a
- * lock held by a single Xid, i.e. not a real MultiXactId; we express it this
- * way for simplicity of API.
- */
-static HTSU_Result
-test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid,
-						   LockTupleMode mode, bool *needwait)
-{
-	MultiXactStatus wantedstatus;
-
-	*needwait = false;
-	wantedstatus = get_mxact_status_for_lock(mode, false);
-
-	/*
-	 * Note: we *must* check TransactionIdIsInProgress before
-	 * TransactionIdDidAbort/Commit; see comment at top of tqual.c for an
-	 * explanation.
-	 */
-	if (TransactionIdIsCurrentTransactionId(xid))
-	{
-		/*
-		 * The tuple has already been locked by our own transaction.  This is
-		 * very rare but can happen if multiple transactions are trying to
-		 * lock an ancient version of the same tuple.
-		 */
-		return HeapTupleSelfUpdated;
-	}
-	else if (TransactionIdIsInProgress(xid))
-	{
-		/*
-		 * If the locking transaction is running, what we do depends on
-		 * whether the lock modes conflict: if they do, then we must wait for
-		 * it to finish; otherwise we can fall through to lock this tuple
-		 * version without waiting.
-		 */
-		if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
-								LOCKMODE_from_mxstatus(wantedstatus)))
-		{
-			*needwait = true;
-		}
-
-		/*
-		 * If we set needwait above, then this value doesn't matter;
-		 * otherwise, this value signals to caller that it's okay to proceed.
-		 */
-		return HeapTupleMayBeUpdated;
-	}
-	else if (TransactionIdDidAbort(xid))
-		return HeapTupleMayBeUpdated;
-	else if (TransactionIdDidCommit(xid))
-	{
-		/*
-		 * The other transaction committed.  If it was only a locker, then the
-		 * lock is completely gone now and we can return success; but if it
-		 * was an update, then what we do depends on whether the two lock
-		 * modes conflict.  If they conflict, then we must report error to
-		 * caller. But if they don't, we can fall through to allow the current
-		 * transaction to lock the tuple.
-		 *
-		 * Note: the reason we worry about ISUPDATE here is because as soon as
-		 * a transaction ends, all its locks are gone and meaningless, and
-		 * thus we can ignore them; whereas its updates persist.  In the
-		 * TransactionIdIsInProgress case, above, we don't need to check
-		 * because we know the lock is still "alive" and thus a conflict needs
-		 * always be checked.
-		 */
-		if (!ISUPDATE_from_mxstatus(status))
-			return HeapTupleMayBeUpdated;
-
-		if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
-								LOCKMODE_from_mxstatus(wantedstatus)))
-			/* bummer */
-			return HeapTupleUpdated;
-
-		return HeapTupleMayBeUpdated;
-	}
-
-	/* Not in progress, not aborted, not committed -- must have crashed */
-	return HeapTupleMayBeUpdated;
-}
-
-
-/*
- * Recursive part of heap_lock_updated_tuple
- *
- * Fetch the tuple pointed to by tid in rel, and mark it as locked by the given
- * xid with the given mode; if this tuple is updated, recurse to lock the new
- * version as well.
- */
-static HTSU_Result
-heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid,
-							LockTupleMode mode)
-{
-	HTSU_Result result;
-	ItemPointerData tupid;
-	HeapTupleData mytup;
-	Buffer		buf;
-	uint16		new_infomask,
-				new_infomask2,
-				old_infomask,
-				old_infomask2;
-	TransactionId xmax,
-				new_xmax;
-	TransactionId priorXmax = InvalidTransactionId;
-	bool		cleared_all_frozen = false;
-	Buffer		vmbuffer = InvalidBuffer;
-	BlockNumber block;
-
-	ItemPointerCopy(tid, &tupid);
-
-	for (;;)
-	{
-		new_infomask = 0;
-		new_xmax = InvalidTransactionId;
-		block = ItemPointerGetBlockNumber(&tupid);
-		ItemPointerCopy(&tupid, &(mytup.t_self));
-
-		if (!heap_fetch(rel, SnapshotAny, &mytup, &buf, false, NULL))
-		{
-			/*
-			 * if we fail to find the updated version of the tuple, it's
-			 * because it was vacuumed/pruned away after its creator
-			 * transaction aborted.  So behave as if we got to the end of the
-			 * chain, and there's no further tuple to lock: return success to
-			 * caller.
-			 */
-			return HeapTupleMayBeUpdated;
-		}
-
-l4:
-		CHECK_FOR_INTERRUPTS();
-
-		/*
-		 * Before locking the buffer, pin the visibility map page if it
-		 * appears to be necessary.  Since we haven't got the lock yet,
-		 * someone else might be in the middle of changing this, so we'll need
-		 * to recheck after we have the lock.
-		 */
-		if (PageIsAllVisible(BufferGetPage(buf)))
-			visibilitymap_pin(rel, block, &vmbuffer);
-		else
-			vmbuffer = InvalidBuffer;
-
-		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
-
-		/*
-		 * If we didn't pin the visibility map page and the page has become
-		 * all visible while we were busy locking the buffer, we'll have to
-		 * unlock and re-lock, to avoid holding the buffer lock across I/O.
-		 * That's a bit unfortunate, but hopefully shouldn't happen often.
-		 */
-		if (vmbuffer == InvalidBuffer && PageIsAllVisible(BufferGetPage(buf)))
-		{
-			LockBuffer(buf, BUFFER_LOCK_UNLOCK);
-			visibilitymap_pin(rel, block, &vmbuffer);
-			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
-		}
-
-		/*
-		 * Check the tuple XMIN against prior XMAX, if any.  If we reached the
-		 * end of the chain, we're done, so return success.
-		 */
-		if (TransactionIdIsValid(priorXmax) &&
-			!TransactionIdEquals(HeapTupleHeaderGetXmin(mytup.t_data),
-								 priorXmax))
-		{
-			result = HeapTupleMayBeUpdated;
-			goto out_locked;
-		}
-
-		/*
-		 * Also check Xmin: if this tuple was created by an aborted
-		 * (sub)transaction, then we already locked the last live one in the
-		 * chain, thus we're done, so return success.
-		 */
-		if (TransactionIdDidAbort(HeapTupleHeaderGetXmin(mytup.t_data)))
-		{
-			UnlockReleaseBuffer(buf);
-			return HeapTupleMayBeUpdated;
-		}
-
-		old_infomask = mytup.t_data->t_infomask;
-		old_infomask2 = mytup.t_data->t_infomask2;
-		xmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
-
-		/*
-		 * If this tuple version has been updated or locked by some concurrent
-		 * transaction(s), what we do depends on whether our lock mode
-		 * conflicts with what those other transactions hold, and also on the
-		 * status of them.
-		 */
-		if (!(old_infomask & HEAP_XMAX_INVALID))
-		{
-			TransactionId rawxmax;
-			bool		needwait;
-
-			rawxmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
-			if (old_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				int			nmembers;
-				int			i;
-				MultiXactMember *members;
-
-				/*
-				 * We don't need a test for pg_upgrade'd tuples: this is only
-				 * applied to tuples after the first in an update chain.  Said
-				 * first tuple in the chain may well be locked-in-9.2-and-
-				 * pg_upgraded, but that one was already locked by our caller,
-				 * not us; and any subsequent ones cannot be because our
-				 * caller must necessarily have obtained a snapshot later than
-				 * the pg_upgrade itself.
-				 */
-				Assert(!HEAP_LOCKED_UPGRADED(mytup.t_data->t_infomask));
-
-				nmembers = GetMultiXactIdMembers(rawxmax, &members, false,
-												 HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
-				for (i = 0; i < nmembers; i++)
-				{
-					result = test_lockmode_for_conflict(members[i].status,
-														members[i].xid,
-														mode, &needwait);
-
-					/*
-					 * If the tuple was already locked by ourselves in a
-					 * previous iteration of this (say heap_lock_tuple was
-					 * forced to restart the locking loop because of a change
-					 * in xmax), then we hold the lock already on this tuple
-					 * version and we don't need to do anything; and this is
-					 * not an error condition either.  We just need to skip
-					 * this tuple and continue locking the next version in the
-					 * update chain.
-					 */
-					if (result == HeapTupleSelfUpdated)
-					{
-						pfree(members);
-						goto next;
-					}
-
-					if (needwait)
-					{
-						LockBuffer(buf, BUFFER_LOCK_UNLOCK);
-						XactLockTableWait(members[i].xid, rel,
-										  &mytup.t_self,
-										  XLTW_LockUpdated);
-						pfree(members);
-						goto l4;
-					}
-					if (result != HeapTupleMayBeUpdated)
-					{
-						pfree(members);
-						goto out_locked;
-					}
-				}
-				if (members)
-					pfree(members);
-			}
-			else
-			{
-				MultiXactStatus status;
-
-				/*
-				 * For a non-multi Xmax, we first need to compute the
-				 * corresponding MultiXactStatus by using the infomask bits.
-				 */
-				if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
-				{
-					if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
-						status = MultiXactStatusForKeyShare;
-					else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
-						status = MultiXactStatusForShare;
-					else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
-					{
-						if (old_infomask2 & HEAP_KEYS_UPDATED)
-							status = MultiXactStatusForUpdate;
-						else
-							status = MultiXactStatusForNoKeyUpdate;
-					}
-					else
-					{
-						/*
-						 * LOCK_ONLY present alone (a pg_upgraded tuple marked
-						 * as share-locked in the old cluster) shouldn't be
-						 * seen in the middle of an update chain.
-						 */
-						elog(ERROR, "invalid lock status in tuple");
-					}
-				}
-				else
-				{
-					/* it's an update, but which kind? */
-					if (old_infomask2 & HEAP_KEYS_UPDATED)
-						status = MultiXactStatusUpdate;
-					else
-						status = MultiXactStatusNoKeyUpdate;
-				}
-
-				result = test_lockmode_for_conflict(status, rawxmax, mode,
-													&needwait);
-
-				/*
-				 * If the tuple was already locked by ourselves in a previous
-				 * iteration of this (say heap_lock_tuple was forced to
-				 * restart the locking loop because of a change in xmax), then
-				 * we hold the lock already on this tuple version and we don't
-				 * need to do anything; and this is not an error condition
-				 * either.  We just need to skip this tuple and continue
-				 * locking the next version in the update chain.
-				 */
-				if (result == HeapTupleSelfUpdated)
-					goto next;
-
-				if (needwait)
-				{
-					LockBuffer(buf, BUFFER_LOCK_UNLOCK);
-					XactLockTableWait(rawxmax, rel, &mytup.t_self,
-									  XLTW_LockUpdated);
-					goto l4;
-				}
-				if (result != HeapTupleMayBeUpdated)
-				{
-					goto out_locked;
-				}
-			}
-		}
-
-		/* compute the new Xmax and infomask values for the tuple ... */
-		compute_new_xmax_infomask(xmax, old_infomask, mytup.t_data->t_infomask2,
-								  xid, mode, false,
-								  &new_xmax, &new_infomask, &new_infomask2);
-
-		if (PageIsAllVisible(BufferGetPage(buf)) &&
-			visibilitymap_clear(rel, block, vmbuffer,
-								VISIBILITYMAP_ALL_FROZEN))
-			cleared_all_frozen = true;
-
-		START_CRIT_SECTION();
-
-		/* ... and set them */
-		HeapTupleHeaderSetXmax(mytup.t_data, new_xmax);
-		mytup.t_data->t_infomask &= ~HEAP_XMAX_BITS;
-		mytup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
-		mytup.t_data->t_infomask |= new_infomask;
-		mytup.t_data->t_infomask2 |= new_infomask2;
-
-		MarkBufferDirty(buf);
-
-		/* XLOG stuff */
-		if (RelationNeedsWAL(rel))
-		{
-			xl_heap_lock_updated xlrec;
-			XLogRecPtr	recptr;
-			Page		page = BufferGetPage(buf);
-
-			XLogBeginInsert();
-			XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
-
-			xlrec.offnum = ItemPointerGetOffsetNumber(&mytup.t_self);
-			xlrec.xmax = new_xmax;
-			xlrec.infobits_set = compute_infobits(new_infomask, new_infomask2);
-			xlrec.flags =
-				cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
-
-			XLogRegisterData((char *) &xlrec, SizeOfHeapLockUpdated);
-
-			recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_LOCK_UPDATED);
-
-			PageSetLSN(page, recptr);
-		}
-
-		END_CRIT_SECTION();
-
-next:
-		/* if we find the end of update chain, we're done. */
-		if (mytup.t_data->t_infomask & HEAP_XMAX_INVALID ||
-			ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid) ||
-			HeapTupleHeaderIsOnlyLocked(mytup.t_data))
-		{
-			result = HeapTupleMayBeUpdated;
-			goto out_locked;
-		}
-
-		/* tail recursion */
-		priorXmax = HeapTupleHeaderGetUpdateXid(mytup.t_data);
-		ItemPointerCopy(&(mytup.t_data->t_ctid), &tupid);
-		UnlockReleaseBuffer(buf);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
-	}
-
-	result = HeapTupleMayBeUpdated;
-
-out_locked:
-	UnlockReleaseBuffer(buf);
-
-	if (vmbuffer != InvalidBuffer)
-		ReleaseBuffer(vmbuffer);
-
-	return result;
-
-}
-
-/*
- * heap_lock_updated_tuple
- *		Follow update chain when locking an updated tuple, acquiring locks (row
- *		marks) on the updated versions.
- *
- * The initial tuple is assumed to be already locked.
- *
- * This function doesn't check visibility, it just unconditionally marks the
- * tuple(s) as locked.  If any tuple in the updated chain is being deleted
- * concurrently (or updated with the key being modified), sleep until the
- * transaction doing it is finished.
- *
- * Note that we don't acquire heavyweight tuple locks on the tuples we walk
- * when we have to wait for other transactions to release them, as opposed to
- * what heap_lock_tuple does.  The reason is that having more than one
- * transaction walking the chain is probably uncommon enough that risk of
- * starvation is not likely: one of the preconditions for being here is that
- * the snapshot in use predates the update that created this tuple (because we
- * started at an earlier version of the tuple), but at the same time such a
- * transaction cannot be using repeatable read or serializable isolation
- * levels, because that would lead to a serializability failure.
- */
-static HTSU_Result
-heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid,
-						TransactionId xid, LockTupleMode mode)
-{
-	if (!ItemPointerEquals(&tuple->t_self, ctid))
-	{
-		/*
-		 * If this is the first possibly-multixact-able operation in the
-		 * current transaction, set my per-backend OldestMemberMXactId
-		 * setting. We can be certain that the transaction will never become a
-		 * member of any older MultiXactIds than that.  (We have to do this
-		 * even if we end up just using our own TransactionId below, since
-		 * some other backend could incorporate our XID into a MultiXact
-		 * immediately afterwards.)
-		 */
-		MultiXactIdSetOldestMember();
-
-		return heap_lock_updated_tuple_rec(rel, ctid, xid, mode);
-	}
-
-	/* nothing to lock */
-	return HeapTupleMayBeUpdated;
-}
-
-/*
- *	heap_finish_speculative - mark speculative insertion as successful
- *
- * To successfully finish a speculative insertion we have to clear speculative
- * token from tuple.  To do so the t_ctid field, which will contain a
- * speculative token value, is modified in place to point to the tuple itself,
- * which is characteristic of a newly inserted ordinary tuple.
- *
- * NB: It is not ok to commit without either finishing or aborting a
- * speculative insertion.  We could treat speculative tuples of committed
- * transactions implicitly as completed, but then we would have to be prepared
- * to deal with speculative tokens on committed tuples.  That wouldn't be
- * difficult - no-one looks at the ctid field of a tuple with invalid xmax -
- * but clearing the token at completion isn't very expensive either.
- * An explicit confirmation WAL record also makes logical decoding simpler.
- */
-void
-heap_finish_speculative(Relation relation, HeapTuple tuple)
-{
-	Buffer		buffer;
-	Page		page;
-	OffsetNumber offnum;
-	ItemId		lp = NULL;
-	HeapTupleHeader htup;
-
-	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&(tuple->t_self)));
-	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
-	page = (Page) BufferGetPage(buffer);
-
-	offnum = ItemPointerGetOffsetNumber(&(tuple->t_self));
-	if (PageGetMaxOffsetNumber(page) >= offnum)
-		lp = PageGetItemId(page, offnum);
-
-	if (PageGetMaxOffsetNumber(page) < offnum || !ItemIdIsNormal(lp))
-		elog(ERROR, "invalid lp");
-
-	htup = (HeapTupleHeader) PageGetItem(page, lp);
-
-	/* SpecTokenOffsetNumber should be distinguishable from any real offset */
-	StaticAssertStmt(MaxOffsetNumber < SpecTokenOffsetNumber,
-					 "invalid speculative token constant");
-
-	/* NO EREPORT(ERROR) from here till changes are logged */
-	START_CRIT_SECTION();
-
-	Assert(HeapTupleHeaderIsSpeculative(tuple->t_data));
-
-	MarkBufferDirty(buffer);
-
-	/*
-	 * Replace the speculative insertion token with a real t_ctid, pointing to
-	 * itself like it does on regular tuples.
-	 */
-	htup->t_ctid = tuple->t_self;
-
-	/* XLOG stuff */
-	if (RelationNeedsWAL(relation))
-	{
-		xl_heap_confirm xlrec;
-		XLogRecPtr	recptr;
-
-		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
-
-		XLogBeginInsert();
-
-		/* We want the same filtering on this as on a plain insert */
-		XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
-
-		XLogRegisterData((char *) &xlrec, SizeOfHeapConfirm);
-		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
-
-		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_CONFIRM);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	UnlockReleaseBuffer(buffer);
-}
-
-/*
- *	heap_abort_speculative - kill a speculatively inserted tuple
- *
- * Marks a tuple that was speculatively inserted in the same command as dead,
- * by setting its xmin as invalid.  That makes it immediately appear as dead
- * to all transactions, including our own.  In particular, it makes
- * HeapTupleSatisfiesDirty() regard the tuple as dead, so that another backend
- * inserting a duplicate key value won't unnecessarily wait for our whole
- * transaction to finish (it'll just wait for our speculative insertion to
- * finish).
- *
- * Killing the tuple prevents "unprincipled deadlocks", which are deadlocks
- * that arise due to a mutual dependency that is not user visible.  By
- * definition, unprincipled deadlocks cannot be prevented by the user
- * reordering lock acquisition in client code, because the implementation level
- * lock acquisitions are not under the user's direct control.  If speculative
- * inserters did not take this precaution, then under high concurrency they
- * could deadlock with each other, which would not be acceptable.
- *
- * This is somewhat redundant with heap_delete, but we prefer to have a
- * dedicated routine with stripped down requirements.  Note that this is also
- * used to delete the TOAST tuples created during speculative insertion.
- *
- * This routine does not affect logical decoding as it only looks at
- * confirmation records.
- */
-void
-heap_abort_speculative(Relation relation, HeapTuple tuple)
-{
-	TransactionId xid = GetCurrentTransactionId();
-	ItemPointer tid = &(tuple->t_self);
-	ItemId		lp;
-	HeapTupleData tp;
-	Page		page;
-	BlockNumber block;
-	Buffer		buffer;
-
-	Assert(ItemPointerIsValid(tid));
-
-	block = ItemPointerGetBlockNumber(tid);
-	buffer = ReadBuffer(relation, block);
-	page = BufferGetPage(buffer);
-
-	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
-
-	/*
-	 * Page can't be all visible, we just inserted into it, and are still
-	 * running.
-	 */
-	Assert(!PageIsAllVisible(page));
-
-	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
-	Assert(ItemIdIsNormal(lp));
-
-	tp.t_tableOid = RelationGetRelid(relation);
-	tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	tp.t_len = ItemIdGetLength(lp);
-	tp.t_self = *tid;
-
-	/*
-	 * Sanity check that the tuple really is a speculatively inserted tuple,
-	 * inserted by us.
-	 */
-	if (tp.t_data->t_choice.t_heap.t_xmin != xid)
-		elog(ERROR, "attempted to kill a tuple inserted by another transaction");
-	if (!(IsToastRelation(relation) || HeapTupleHeaderIsSpeculative(tp.t_data)))
-		elog(ERROR, "attempted to kill a non-speculative tuple");
-	Assert(!HeapTupleHeaderIsHeapOnly(tp.t_data));
+					new_infomask |= HEAP_XMAX_EXCL_LOCK;
+					break;
+				case LockTupleExclusive:
+					new_xmax = add_to_xmax;
+					new_infomask |= HEAP_XMAX_EXCL_LOCK;
+					new_infomask2 |= HEAP_KEYS_UPDATED;
+					break;
+				default:
+					new_xmax = InvalidTransactionId;	/* silence compiler */
+					elog(ERROR, "invalid lock mode");
+			}
+		}
+	}
+	else if (old_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		MultiXactStatus new_status;
 
-	/*
-	 * No need to check for serializable conflicts here.  There is never a
-	 * need for a combocid, either.  No need to extract replica identity, or
-	 * do anything special with infomask bits.
-	 */
+		/*
+		 * Currently we don't allow XMAX_COMMITTED to be set for multis, so
+		 * cross-check.
+		 */
+		Assert(!(old_infomask & HEAP_XMAX_COMMITTED));
 
-	START_CRIT_SECTION();
+		/*
+		 * A multixact together with LOCK_ONLY set but neither lock bit set
+		 * (i.e. a pg_upgraded share locked tuple) cannot possibly be running
+		 * anymore.  This check is critical for databases upgraded by
+		 * pg_upgrade; both MultiXactIdIsRunning and MultiXactIdExpand assume
+		 * that such multis are never passed.
+		 */
+		if (HEAP_LOCKED_UPGRADED(old_infomask))
+		{
+			old_infomask &= ~HEAP_XMAX_IS_MULTI;
+			old_infomask |= HEAP_XMAX_INVALID;
+			goto l5;
+		}
 
-	/*
-	 * The tuple will become DEAD immediately.  Flag that this page
-	 * immediately is a candidate for pruning by setting xmin to
-	 * RecentGlobalXmin.  That's not pretty, but it doesn't seem worth
-	 * inventing a nicer API for this.
-	 */
-	Assert(TransactionIdIsValid(RecentGlobalXmin));
-	PageSetPrunable(page, RecentGlobalXmin);
+		/*
+		 * If the XMAX is already a MultiXactId, then we need to expand it to
+		 * include add_to_xmax; but if all the members were lockers and are
+		 * all gone, we can do away with the IS_MULTI bit and just set
+		 * add_to_xmax as the only locker/updater.  If all lockers are gone
+		 * and we have an updater that aborted, we can also do without a
+		 * multi.
+		 *
+		 * The cost of doing GetMultiXactIdMembers would be paid by
+		 * MultiXactIdExpand if we weren't to do this, so this check is not
+		 * incurring extra work anyhow.
+		 */
+		if (!MultiXactIdIsRunning(xmax, HEAP_XMAX_IS_LOCKED_ONLY(old_infomask)))
+		{
+			if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) ||
+				!TransactionIdDidCommit(MultiXactIdGetUpdateXid(xmax,
+																old_infomask)))
+			{
+				/*
+				 * Reset these bits and restart; otherwise fall through to
+				 * create a new multi below.
+				 */
+				old_infomask &= ~HEAP_XMAX_IS_MULTI;
+				old_infomask |= HEAP_XMAX_INVALID;
+				goto l5;
+			}
+		}
 
-	/* store transaction information of xact deleting the tuple */
-	tp.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
-	tp.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+		new_status = get_mxact_status_for_lock(mode, is_update);
 
-	/*
-	 * Set the tuple header xmin to InvalidTransactionId.  This makes the
-	 * tuple immediately invisible everyone.  (In particular, to any
-	 * transactions waiting on the speculative token, woken up later.)
-	 */
-	HeapTupleHeaderSetXmin(tp.t_data, InvalidTransactionId);
+		new_xmax = MultiXactIdExpand((MultiXactId) xmax, add_to_xmax,
+									 new_status);
+		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
+	}
+	else if (old_infomask & HEAP_XMAX_COMMITTED)
+	{
+		/*
+		 * It's a committed update, so we need to preserve him as updater of
+		 * the tuple.
+		 */
+		MultiXactStatus status;
+		MultiXactStatus new_status;
 
-	/* Clear the speculative insertion token too */
-	tp.t_data->t_ctid = tp.t_self;
+		if (old_infomask2 & HEAP_KEYS_UPDATED)
+			status = MultiXactStatusUpdate;
+		else
+			status = MultiXactStatusNoKeyUpdate;
 
-	MarkBufferDirty(buffer);
+		new_status = get_mxact_status_for_lock(mode, is_update);
 
-	/*
-	 * XLOG stuff
-	 *
-	 * The WAL records generated here match heap_delete().  The same recovery
-	 * routines are used.
-	 */
-	if (RelationNeedsWAL(relation))
+		/*
+		 * since it's not running, it's obviously impossible for the old
+		 * updater to be identical to the current one, so we need not check
+		 * for that case as we do in the block above.
+		 */
+		new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
+		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
+	}
+	else if (TransactionIdIsInProgress(xmax))
 	{
-		xl_heap_delete xlrec;
-		XLogRecPtr	recptr;
+		/*
+		 * If the XMAX is a valid, in-progress TransactionId, then we need to
+		 * create a new MultiXactId that includes both the old locker or
+		 * updater and our own TransactionId.
+		 */
+		MultiXactStatus new_status;
+		MultiXactStatus old_status;
+		LockTupleMode old_mode;
 
-		xlrec.flags = XLH_DELETE_IS_SUPER;
-		xlrec.infobits_set = compute_infobits(tp.t_data->t_infomask,
-											  tp.t_data->t_infomask2);
-		xlrec.offnum = ItemPointerGetOffsetNumber(&tp.t_self);
-		xlrec.xmax = xid;
+		if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
+		{
+			if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
+				old_status = MultiXactStatusForKeyShare;
+			else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
+				old_status = MultiXactStatusForShare;
+			else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
+			{
+				if (old_infomask2 & HEAP_KEYS_UPDATED)
+					old_status = MultiXactStatusForUpdate;
+				else
+					old_status = MultiXactStatusForNoKeyUpdate;
+			}
+			else
+			{
+				/*
+				 * LOCK_ONLY can be present alone only when a page has been
+				 * upgraded by pg_upgrade.  But in that case,
+				 * TransactionIdIsInProgress() should have returned false.  We
+				 * assume it's no longer locked in this case.
+				 */
+				elog(WARNING, "LOCK_ONLY found for Xid in progress %u", xmax);
+				old_infomask |= HEAP_XMAX_INVALID;
+				old_infomask &= ~HEAP_XMAX_LOCK_ONLY;
+				goto l5;
+			}
+		}
+		else
+		{
+			/* it's an update, but which kind? */
+			if (old_infomask2 & HEAP_KEYS_UPDATED)
+				old_status = MultiXactStatusUpdate;
+			else
+				old_status = MultiXactStatusNoKeyUpdate;
+		}
 
-		XLogBeginInsert();
-		XLogRegisterData((char *) &xlrec, SizeOfHeapDelete);
-		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
+		old_mode = TUPLOCK_from_mxstatus(old_status);
+
+		/*
+		 * If the lock to be acquired is for the same TransactionId as the
+		 * existing lock, there's an optimization possible: consider only the
+		 * strongest of both locks as the only one present, and restart.
+		 */
+		if (xmax == add_to_xmax)
+		{
+			/*
+			 * Note that it's not possible for the original tuple to be
+			 * updated: we wouldn't be here because the tuple would have been
+			 * invisible and we wouldn't try to update it.  As a subtlety,
+			 * this code can also run when traversing an update chain to lock
+			 * future versions of a tuple.  But we wouldn't be here either,
+			 * because the add_to_xmax would be different from the original
+			 * updater.
+			 */
+			Assert(HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
 
-		/* No replica identity & replication origin logged */
+			/* acquire the strongest of both */
+			if (mode < old_mode)
+				mode = old_mode;
+			/* mustn't touch is_update */
 
-		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_DELETE);
+			old_infomask |= HEAP_XMAX_INVALID;
+			goto l5;
+		}
 
-		PageSetLSN(page, recptr);
+		/* otherwise, just fall back to creating a new multixact */
+		new_status = get_mxact_status_for_lock(mode, is_update);
+		new_xmax = MultiXactIdCreate(xmax, old_status,
+									 add_to_xmax, new_status);
+		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
 	}
+	else if (!HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) &&
+			 TransactionIdDidCommit(xmax))
+	{
+		/*
+		 * It's a committed update, so we gotta preserve him as updater of the
+		 * tuple.
+		 */
+		MultiXactStatus status;
+		MultiXactStatus new_status;
 
-	END_CRIT_SECTION();
+		if (old_infomask2 & HEAP_KEYS_UPDATED)
+			status = MultiXactStatusUpdate;
+		else
+			status = MultiXactStatusNoKeyUpdate;
 
-	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+		new_status = get_mxact_status_for_lock(mode, is_update);
 
-	if (HeapTupleHasExternal(&tp))
+		/*
+		 * since it's not running, it's obviously impossible for the old
+		 * updater to be identical to the current one, so we need not check
+		 * for that case as we do in the block above.
+		 */
+		new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
+		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
+	}
+	else
 	{
-		Assert(!IsToastRelation(relation));
-		toast_delete(relation, &tp, true);
+		/*
+		 * Can get here iff the locking/updating transaction was running when
+		 * the infomask was extracted from the tuple, but finished before
+		 * TransactionIdIsInProgress got to run.  Deal with it as if there was
+		 * no locker at all in the first place.
+		 */
+		old_infomask |= HEAP_XMAX_INVALID;
+		goto l5;
 	}
 
-	/*
-	 * Never need to mark tuple for invalidation, since catalogs don't support
-	 * speculative insertion
-	 */
+	*result_infomask = new_infomask;
+	*result_infomask2 = new_infomask2;
+	*result_xmax = new_xmax;
+}
 
-	/* Now we can release the buffer */
-	ReleaseBuffer(buffer);
 
-	/* count deletion, as we counted the insertion too */
-	pgstat_count_heap_delete(relation);
-}
 
 /*
  * heap_inplace_update - update a tuple "in place" (ie, overwrite it)
@@ -6993,7 +4942,7 @@ HeapTupleGetUpdateXid(HeapTupleHeader tuple)
  *
  * The passed infomask pairs up with the given multixact in the tuple header.
  */
-static bool
+bool
 DoesMultiXactIdConflict(MultiXactId multi, uint16 infomask,
 						LockTupleMode lockmode)
 {
@@ -7160,7 +5109,7 @@ Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
  * We return (in *remaining, if not NULL) the number of members that are still
  * running, including any (non-aborted) subtransactions of our own transaction.
  */
-static void
+void
 MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
 				Relation rel, ItemPointer ctid, XLTW_Oper oper,
 				int *remaining)
@@ -7182,7 +5131,7 @@ MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
  * We return (in *remaining, if not NULL) the number of members that are still
  * running, including any (non-aborted) subtransactions of our own transaction.
  */
-static bool
+bool
 ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
 						   uint16 infomask, Relation rel, int *remaining)
 {
@@ -7744,7 +5693,7 @@ log_heap_update(Relation reln, Buffer oldbuf,
  * This is only used in wal_level >= WAL_LEVEL_LOGICAL, and only for catalog
  * tuples.
  */
-static XLogRecPtr
+XLogRecPtr
 log_heap_new_cid(Relation relation, HeapTuple tup)
 {
 	xl_heap_new_cid xlrec;
@@ -9121,46 +7070,6 @@ heap2_redo(XLogReaderState *record)
 }
 
 /*
- *	heap_sync		- sync a heap, for use when no WAL has been written
- *
- * This forces the heap contents (including TOAST heap if any) down to disk.
- * If we skipped using WAL, and WAL is otherwise needed, we must force the
- * relation down to disk before it's safe to commit the transaction.  This
- * requires writing out any dirty buffers and then doing a forced fsync.
- *
- * Indexes are not touched.  (Currently, index operations associated with
- * the commands that use this are WAL-logged and so do not need fsync.
- * That behavior might change someday, but in any case it's likely that
- * any fsync decisions required would be per-index and hence not appropriate
- * to be done here.)
- */
-void
-heap_sync(Relation rel)
-{
-	/* non-WAL-logged tables never need fsync */
-	if (!RelationNeedsWAL(rel))
-		return;
-
-	/* main heap */
-	FlushRelationBuffers(rel);
-	/* FlushRelationBuffers will have opened rd_smgr */
-	smgrimmedsync(rel->rd_smgr, MAIN_FORKNUM);
-
-	/* FSM is not critical, don't bother syncing it */
-
-	/* toast heap, if any */
-	if (OidIsValid(rel->rd_rel->reltoastrelid))
-	{
-		Relation	toastrel;
-
-		toastrel = heap_open(rel->rd_rel->reltoastrelid, AccessShareLock);
-		FlushRelationBuffers(toastrel);
-		smgrimmedsync(toastrel->rd_smgr, MAIN_FORKNUM);
-		heap_close(toastrel, AccessShareLock);
-	}
-}
-
-/*
  * Mask a heap page before performing consistency checks on it.
  */
 void
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 7d7ac75..a0e3272 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -50,7 +50,13 @@
 
 /* local functions */
 static bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
-
+static bool heap_fetch(Relation relation,
+					   ItemPointer tid,
+					   Snapshot snapshot,
+					   HeapTuple tuple,
+					   Buffer *userbuf,
+					   bool keep_buf,
+					   Relation stats_relation);
 /*-------------------------------------------------------------------------
  *
  * POSTGRES "time qualification" code, ie, tuple visibility rules.
@@ -1660,10 +1666,2149 @@ HeapTupleSatisfiesHistoricMVCC(StorageTuple stup, Snapshot snapshot,
 		return true;
 }
 
-Datum
-heapam_storage_handler(PG_FUNCTION_ARGS)
+
+/*
+ *	heap_fetch		- retrieve tuple with given tid
+ *
+ * On entry, tuple->t_self is the TID to fetch.  We pin the buffer holding
+ * the tuple, fill in the remaining fields of *tuple, and check the tuple
+ * against the specified snapshot.
+ *
+ * If successful (tuple found and passes snapshot time qual), then *userbuf
+ * is set to the buffer holding the tuple and TRUE is returned.  The caller
+ * must unpin the buffer when done with the tuple.
+ *
+ * If the tuple is not found (ie, item number references a deleted slot),
+ * then tuple->t_data is set to NULL and FALSE is returned.
+ *
+ * If the tuple is found but fails the time qual check, then FALSE is returned
+ * but tuple->t_data is left pointing to the tuple.
+ *
+ * keep_buf determines what is done with the buffer in the FALSE-result cases.
+ * When the caller specifies keep_buf = true, we retain the pin on the buffer
+ * and return it in *userbuf (so the caller must eventually unpin it); when
+ * keep_buf = false, the pin is released and *userbuf is set to InvalidBuffer.
+ *
+ * stats_relation is the relation to charge the heap_fetch operation against
+ * for statistical purposes.  (This could be the heap rel itself, an
+ * associated index, or NULL to not count the fetch at all.)
+ *
+ * heap_fetch does not follow HOT chains: only the exact TID requested will
+ * be fetched.
+ *
+ * It is somewhat inconsistent that we ereport() on invalid block number but
+ * return false on invalid item number.  There are a couple of reasons though.
+ * One is that the caller can relatively easily check the block number for
+ * validity, but cannot check the item number without reading the page
+ * himself.  Another is that when we are following a t_ctid link, we can be
+ * reasonably confident that the page number is valid (since VACUUM shouldn't
+ * truncate off the destination page without having killed the referencing
+ * tuple first), but the item number might well not be good.
+ */
+static bool
+heap_fetch(Relation relation,
+		   ItemPointer tid,
+		   Snapshot snapshot,
+		   HeapTuple tuple,
+		   Buffer *userbuf,
+		   bool keep_buf,
+		   Relation stats_relation)
 {
-	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
+	ItemId		lp;
+	Buffer		buffer;
+	Page		page;
+	OffsetNumber offnum;
+	bool		valid;
+
+	/*
+	 * Fetch and pin the appropriate page of the relation.
+	 */
+	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
+
+	/*
+	 * Need share lock on buffer to examine tuple commit status.
+	 */
+	LockBuffer(buffer, BUFFER_LOCK_SHARE);
+	page = BufferGetPage(buffer);
+	TestForOldSnapshot(snapshot, relation, page);
+
+	/*
+	 * We'd better check for out-of-range offnum in case of VACUUM since the
+	 * TID was obtained.
+	 */
+	offnum = ItemPointerGetOffsetNumber(tid);
+	if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
+	{
+		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+		if (keep_buf)
+			*userbuf = buffer;
+		else
+		{
+			ReleaseBuffer(buffer);
+			*userbuf = InvalidBuffer;
+		}
+		return false;
+	}
+
+	/*
+	 * get the item line pointer corresponding to the requested tid
+	 */
+	lp = PageGetItemId(page, offnum);
+
+	/*
+	 * Must check for deleted tuple.
+	 */
+	if (!ItemIdIsNormal(lp))
+	{
+		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+		if (keep_buf)
+			*userbuf = buffer;
+		else
+		{
+			ReleaseBuffer(buffer);
+			*userbuf = InvalidBuffer;
+		}
+		return false;
+	}
+
+	/*
+	 * 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);
+
+	/*
+	 * check time qualification of tuple, then release lock
+	 */
+	valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, tuple, snapshot, buffer);
+
+	if (valid)
+		PredicateLockTuple(relation, tuple, snapshot);
+
+	CheckForSerializableConflictOut(valid, relation, tuple, buffer, snapshot);
+
+	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+
+	if (valid)
+	{
+		/*
+		 * All checks passed, so return the tuple as valid. Caller is now
+		 * responsible for releasing the buffer.
+		 */
+		*userbuf = buffer;
+
+		/* Count the successful fetch against appropriate rel, if any */
+		if (stats_relation != NULL)
+			pgstat_count_heap_fetch(stats_relation);
+
+		return true;
+	}
+
+	/* Tuple failed time qual, but maybe caller wants to see it anyway. */
+	if (keep_buf)
+		*userbuf = buffer;
+	else
+	{
+		ReleaseBuffer(buffer);
+		*userbuf = InvalidBuffer;
+	}
+
+	return false;
+}
+
+
+
+
+
+/* ----------------------------------------------------------------
+ *				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)
+{
+	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);
+
+	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,
+				   CommandId cid, Snapshot crosscheck, bool wait,
+				   HeapUpdateFailureData *hufd, LockTupleMode *lockmode)
+{
+	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);
+
+	return result;
+}
+
+static void
+heapam_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);
+
+	offnum = ItemPointerGetOffsetNumber(&(tuple->t_self));
+	if (PageGetMaxOffsetNumber(page) >= offnum)
+		lp = PageGetItemId(page, offnum);
+
+	if (PageGetMaxOffsetNumber(page) < offnum || !ItemIdIsNormal(lp))
+		elog(ERROR, "invalid lp");
+
+	htup = (HeapTupleHeader) PageGetItem(page, lp);
+
+	/* SpecTokenOffsetNumber should be distinguishable from any real offset */
+	StaticAssertStmt(MaxOffsetNumber < SpecTokenOffsetNumber,
+					 "invalid speculative token constant");
+
+	/* NO EREPORT(ERROR) from here till changes are logged */
+	START_CRIT_SECTION();
+
+	Assert(HeapTupleHeaderIsSpeculative(tuple->t_data));
+
+	MarkBufferDirty(buffer);
+
+	/*
+	 * Replace the speculative insertion token with a real t_ctid, pointing to
+	 * itself like it does on regular tuples.
+	 */
+	htup->t_ctid = tuple->t_self;
+
+	/* XLOG stuff */
+	if (RelationNeedsWAL(relation))
+	{
+		xl_heap_confirm xlrec;
+		XLogRecPtr	recptr;
+
+		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
+
+		XLogBeginInsert();
+
+		/* We want the same filtering on this as on a plain insert */
+		XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
+
+		XLogRegisterData((char *) &xlrec, SizeOfHeapConfirm);
+		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
+
+		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_CONFIRM);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(buffer);
+	slot->tts_speculativeToken = 0;
+}
+
+static void
+heapam_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;
+	HeapTupleData tp;
+	Page		page;
+	BlockNumber block;
+	Buffer		buffer;
+
+	/*Assert(slot->tts_speculativeToken != 0); This needs some update in toast */
+	Assert(ItemPointerIsValid(tid));
+
+	block = ItemPointerGetBlockNumber(tid);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
+
+	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+
+	/*
+	 * Page can't be all visible, we just inserted into it, and are still
+	 * running.
+	 */
+	Assert(!PageIsAllVisible(page));
+
+	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
+	Assert(ItemIdIsNormal(lp));
+
+	tp.t_tableOid = RelationGetRelid(relation);
+	tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	tp.t_len = ItemIdGetLength(lp);
+	tp.t_self = *tid;
+
+	/*
+	 * Sanity check that the tuple really is a speculatively inserted tuple,
+	 * inserted by us.
+	 */
+	if (tp.t_data->t_choice.t_heap.t_xmin != xid)
+		elog(ERROR, "attempted to kill a tuple inserted by another transaction");
+	if (!(IsToastRelation(relation) || HeapTupleHeaderIsSpeculative(tp.t_data)))
+		elog(ERROR, "attempted to kill a non-speculative tuple");
+	Assert(!HeapTupleHeaderIsHeapOnly(tp.t_data));
+
+	/*
+	 * No need to check for serializable conflicts here.  There is never a
+	 * need for a combocid, either.  No need to extract replica identity, or
+	 * do anything special with infomask bits.
+	 */
+
+	START_CRIT_SECTION();
+
+	/*
+	 * The tuple will become DEAD immediately.  Flag that this page
+	 * immediately is a candidate for pruning by setting xmin to
+	 * RecentGlobalXmin.  That's not pretty, but it doesn't seem worth
+	 * inventing a nicer API for this.
+	 */
+	Assert(TransactionIdIsValid(RecentGlobalXmin));
+	PageSetPrunable(page, RecentGlobalXmin);
+
+	/* store transaction information of xact deleting the tuple */
+	tp.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+	tp.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+
+	/*
+	 * Set the tuple header xmin to InvalidTransactionId.  This makes the
+	 * tuple immediately invisible everyone.  (In particular, to any
+	 * transactions waiting on the speculative token, woken up later.)
+	 */
+	HeapTupleHeaderSetXmin(tp.t_data, InvalidTransactionId);
+
+	/* Clear the speculative insertion token too */
+	tp.t_data->t_ctid = tp.t_self;
+
+	MarkBufferDirty(buffer);
+
+	/*
+	 * XLOG stuff
+	 *
+	 * The WAL records generated here match heap_delete().  The same recovery
+	 * routines are used.
+	 */
+	if (RelationNeedsWAL(relation))
+	{
+		xl_heap_delete xlrec;
+		XLogRecPtr	recptr;
+
+		xlrec.flags = XLH_DELETE_IS_SUPER;
+		xlrec.infobits_set = compute_infobits(tp.t_data->t_infomask,
+											  tp.t_data->t_infomask2);
+		xlrec.offnum = ItemPointerGetOffsetNumber(&tp.t_self);
+		xlrec.xmax = xid;
+
+		XLogBeginInsert();
+		XLogRegisterData((char *) &xlrec, SizeOfHeapDelete);
+		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
+
+		/* No replica identity & replication origin logged */
+
+		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_DELETE);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+
+	if (HeapTupleHasExternal(&tp))
+	{
+		Assert(!IsToastRelation(relation));
+		toast_delete(relation, &tp, true);
+	}
+
+	/*
+	 * Never need to mark tuple for invalidation, since catalogs don't support
+	 * speculative insertion
+	 */
+
+	/* Now we can release the buffer */
+	ReleaseBuffer(buffer);
+
+	/* count deletion, as we counted the insertion too */
+	pgstat_count_heap_delete(relation);
+	slot->tts_speculativeToken = 0;
+}
+
+/*
+ *	heapam_multi_insert	- insert multiple tuple into a heap
+ *
+ * This is like heap_insert(), but inserts multiple tuples in one operation.
+ * That's faster than calling heap_insert() in a loop, because when multiple
+ * tuples can be inserted on a single page, we can write just a single WAL
+ * record covering all of them, and only need to lock/unlock the page once.
+ *
+ * Note: this leaks memory into the current memory context. You can create a
+ * temporary context before calling this, if that's a problem.
+ */
+static void
+heapam_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
+				  CommandId cid, int options, BulkInsertState bistate)
+{
+	TransactionId xid = GetCurrentTransactionId();
+	HeapTuple  *heaptuples;
+	int			i;
+	int			ndone;
+	char	   *scratch = NULL;
+	Page		page;
+	bool		needwal;
+	Size		saveFreeSpace;
+	bool		need_tuple_data = RelationIsLogicallyLogged(relation);
+	bool		need_cids = RelationIsAccessibleInLogicalDecoding(relation);
+
+	needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation);
+	saveFreeSpace = RelationGetTargetPageFreeSpace(relation,
+												   HEAP_DEFAULT_FILLFACTOR);
+
+	/* Toast and set header data in all the tuples */
+	heaptuples = palloc(ntuples * sizeof(HeapTuple));
+	for (i = 0; i < ntuples; i++)
+		heaptuples[i] = heap_prepare_insert(relation, tuples[i],
+											xid, cid, options);
+
+	/*
+	 * Allocate some memory to use for constructing the WAL record. Using
+	 * palloc() within a critical section is not safe, so we allocate this
+	 * beforehand.
+	 */
+	if (needwal)
+		scratch = palloc(BLCKSZ);
+
+	/*
+	 * We're about to do the actual inserts -- but check for conflict first,
+	 * to minimize the possibility of having to roll back work we've just
+	 * done.
+	 *
+	 * A check here does not definitively prevent a serialization anomaly;
+	 * that check MUST be done at least past the point of acquiring an
+	 * exclusive buffer content lock on every buffer that will be affected,
+	 * and MAY be done after all inserts are reflected in the buffers and
+	 * those locks are released; otherwise there race condition.  Since
+	 * multiple buffers can be locked and unlocked in the loop below, and it
+	 * would not be feasible to identify and lock all of those buffers before
+	 * the loop, we must do a final check at the end.
+	 *
+	 * The check here could be omitted with no loss of correctness; it is
+	 * present strictly as an optimization.
+	 *
+	 * For heap inserts, we only need to check for table-level SSI locks. Our
+	 * new tuples can't possibly conflict with existing tuple locks, and heap
+	 * page locks are only consolidated versions of tuple locks; they do not
+	 * lock "gaps" as index page locks do.  So we don't need to specify a
+	 * buffer when making the call, which makes for a faster check.
+	 */
+	CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
+
+	ndone = 0;
+	while (ndone < ntuples)
+	{
+		Buffer		buffer;
+		Buffer		vmbuffer = InvalidBuffer;
+		bool		all_visible_cleared = false;
+		int			nthispage;
+
+		CHECK_FOR_INTERRUPTS();
+
+		/*
+		 * Find buffer where at least the next tuple will fit.  If the page is
+		 * all-visible, this will also pin the requisite visibility map page.
+		 */
+		buffer = RelationGetBufferForTuple(relation, heaptuples[ndone]->t_len,
+										   InvalidBuffer, options, bistate,
+										   &vmbuffer, NULL);
+		page = BufferGetPage(buffer);
+
+		/* NO EREPORT(ERROR) from here till changes are logged */
+		START_CRIT_SECTION();
+
+		/*
+		 * RelationGetBufferForTuple has ensured that the first tuple fits.
+		 * Put that on the page, and then as many other tuples as fit.
+		 */
+		RelationPutHeapTuple(relation, buffer, heaptuples[ndone], false);
+		for (nthispage = 1; ndone + nthispage < ntuples; nthispage++)
+		{
+			HeapTuple	heaptup = heaptuples[ndone + nthispage];
+
+			if (PageGetHeapFreeSpace(page) < MAXALIGN(heaptup->t_len) + saveFreeSpace)
+				break;
+
+			RelationPutHeapTuple(relation, buffer, heaptup, false);
+
+			/*
+			 * We don't use heap_multi_insert for catalog tuples yet, but
+			 * better be prepared...
+			 */
+			if (needwal && need_cids)
+				log_heap_new_cid(relation, heaptup);
+		}
+
+		if (PageIsAllVisible(page))
+		{
+			all_visible_cleared = true;
+			PageClearAllVisible(page);
+			visibilitymap_clear(relation,
+								BufferGetBlockNumber(buffer),
+								vmbuffer, VISIBILITYMAP_VALID_BITS);
+		}
+
+		/*
+		 * XXX Should we set PageSetPrunable on this page ? See heap_insert()
+		 */
+
+		MarkBufferDirty(buffer);
+
+		/* XLOG stuff */
+		if (needwal)
+		{
+			XLogRecPtr	recptr;
+			xl_heap_multi_insert *xlrec;
+			uint8		info = XLOG_HEAP2_MULTI_INSERT;
+			char	   *tupledata;
+			int			totaldatalen;
+			char	   *scratchptr = scratch;
+			bool		init;
+			int			bufflags = 0;
+
+			/*
+			 * If the page was previously empty, we can reinit the page
+			 * instead of restoring the whole thing.
+			 */
+			init = (ItemPointerGetOffsetNumber(&(heaptuples[ndone]->t_self)) == FirstOffsetNumber &&
+					PageGetMaxOffsetNumber(page) == FirstOffsetNumber + nthispage - 1);
+
+			/* allocate xl_heap_multi_insert struct from the scratch area */
+			xlrec = (xl_heap_multi_insert *) scratchptr;
+			scratchptr += SizeOfHeapMultiInsert;
+
+			/*
+			 * Allocate offsets array. Unless we're reinitializing the page,
+			 * in that case the tuples are stored in order starting at
+			 * FirstOffsetNumber and we don't need to store the offsets
+			 * explicitly.
+			 */
+			if (!init)
+				scratchptr += nthispage * sizeof(OffsetNumber);
+
+			/* the rest of the scratch space is used for tuple data */
+			tupledata = scratchptr;
+
+			xlrec->flags = all_visible_cleared ? XLH_INSERT_ALL_VISIBLE_CLEARED : 0;
+			xlrec->ntuples = nthispage;
+
+			/*
+			 * Write out an xl_multi_insert_tuple and the tuple data itself
+			 * for each tuple.
+			 */
+			for (i = 0; i < nthispage; i++)
+			{
+				HeapTuple	heaptup = heaptuples[ndone + i];
+				xl_multi_insert_tuple *tuphdr;
+				int			datalen;
+
+				if (!init)
+					xlrec->offsets[i] = ItemPointerGetOffsetNumber(&heaptup->t_self);
+				/* xl_multi_insert_tuple needs two-byte alignment. */
+				tuphdr = (xl_multi_insert_tuple *) SHORTALIGN(scratchptr);
+				scratchptr = ((char *) tuphdr) + SizeOfMultiInsertTuple;
+
+				tuphdr->t_infomask2 = heaptup->t_data->t_infomask2;
+				tuphdr->t_infomask = heaptup->t_data->t_infomask;
+				tuphdr->t_hoff = heaptup->t_data->t_hoff;
+
+				/* write bitmap [+ padding] [+ oid] + data */
+				datalen = heaptup->t_len - SizeofHeapTupleHeader;
+				memcpy(scratchptr,
+					   (char *) heaptup->t_data + SizeofHeapTupleHeader,
+					   datalen);
+				tuphdr->datalen = datalen;
+				scratchptr += datalen;
+			}
+			totaldatalen = scratchptr - tupledata;
+			Assert((scratchptr - scratch) < BLCKSZ);
+
+			if (need_tuple_data)
+				xlrec->flags |= XLH_INSERT_CONTAINS_NEW_TUPLE;
+
+			/*
+			 * Signal that this is the last xl_heap_multi_insert record
+			 * emitted by this call to heap_multi_insert(). Needed for logical
+			 * decoding so it knows when to cleanup temporary data.
+			 */
+			if (ndone + nthispage == ntuples)
+				xlrec->flags |= XLH_INSERT_LAST_IN_MULTI;
+
+			if (init)
+			{
+				info |= XLOG_HEAP_INIT_PAGE;
+				bufflags |= REGBUF_WILL_INIT;
+			}
+
+			/*
+			 * If we're doing logical decoding, include the new tuple data
+			 * even if we take a full-page image of the page.
+			 */
+			if (need_tuple_data)
+				bufflags |= REGBUF_KEEP_DATA;
+
+			XLogBeginInsert();
+			XLogRegisterData((char *) xlrec, tupledata - scratch);
+			XLogRegisterBuffer(0, buffer, REGBUF_STANDARD | bufflags);
+
+			XLogRegisterBufData(0, tupledata, totaldatalen);
+
+			/* filtering by origin on a row level is much more efficient */
+			XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
+
+			recptr = XLogInsert(RM_HEAP2_ID, info);
+
+			PageSetLSN(page, recptr);
+		}
+
+		END_CRIT_SECTION();
+
+		UnlockReleaseBuffer(buffer);
+		if (vmbuffer != InvalidBuffer)
+			ReleaseBuffer(vmbuffer);
+
+		ndone += nthispage;
+	}
+
+	/*
+	 * We're done with the actual inserts.  Check for conflicts again, to
+	 * ensure that all rw-conflicts in to these inserts are detected.  Without
+	 * this final check, a sequential scan of the heap may have locked the
+	 * table after the "before" check, missing one opportunity to detect the
+	 * conflict, and then scanned the table before the new tuples were there,
+	 * missing the other chance to detect the conflict.
+	 *
+	 * For heap inserts, we only need to check for table-level SSI locks. Our
+	 * new tuples can't possibly conflict with existing tuple locks, and heap
+	 * page locks are only consolidated versions of tuple locks; they do not
+	 * lock "gaps" as index page locks do.  So we don't need to specify a
+	 * buffer when making the call.
+	 */
+	CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
+
+	/*
+	 * If tuples are cachable, mark them for invalidation from the caches in
+	 * case we abort.  Note it is OK to do this after releasing the buffer,
+	 * because the heaptuples data structure is all in local memory, not in
+	 * the shared buffer.
+	 */
+	if (IsCatalogRelation(relation))
+	{
+		for (i = 0; i < ntuples; i++)
+			CacheInvalidateHeapTuple(relation, heaptuples[i], NULL);
+	}
+
+	/*
+	 * Copy t_self fields back to the caller's original tuples. This does
+	 * nothing for untoasted tuples (tuples[i] == heaptuples[i)], but it's
+	 * probably faster to always copy than check.
+	 */
+	for (i = 0; i < ntuples; i++)
+		tuples[i]->t_self = heaptuples[i]->t_self;
+
+	pgstat_count_heap_insert(relation, ntuples);
+}
+
+/*
+ * Subroutine for heap_lock_updated_tuple_rec.
+ *
+ * Given a hypothetical multixact status held by the transaction identified
+ * with the given xid, does the current transaction need to wait, fail, or can
+ * it continue if it wanted to acquire a lock of the given mode?  "needwait"
+ * is set to true if waiting is necessary; if it can continue, then
+ * HeapTupleMayBeUpdated is returned.  If the lock is already held by the
+ * current transaction, return HeapTupleSelfUpdated.  In case of a conflict
+ * with another transaction, a different HeapTupleSatisfiesUpdate return code
+ * is returned.
+ *
+ * The held status is said to be hypothetical because it might correspond to a
+ * lock held by a single Xid, i.e. not a real MultiXactId; we express it this
+ * way for simplicity of API.
+ */
+static HTSU_Result
+test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid,
+						   LockTupleMode mode, bool *needwait)
+{
+	MultiXactStatus wantedstatus;
+
+	*needwait = false;
+	wantedstatus = get_mxact_status_for_lock(mode, false);
+
+	/*
+	 * Note: we *must* check TransactionIdIsInProgress before
+	 * TransactionIdDidAbort/Commit; see comment at top of tqual.c for an
+	 * explanation.
+	 */
+	if (TransactionIdIsCurrentTransactionId(xid))
+	{
+		/*
+		 * The tuple has already been locked by our own transaction.  This is
+		 * very rare but can happen if multiple transactions are trying to
+		 * lock an ancient version of the same tuple.
+		 */
+		return HeapTupleSelfUpdated;
+	}
+	else if (TransactionIdIsInProgress(xid))
+	{
+		/*
+		 * If the locking transaction is running, what we do depends on
+		 * whether the lock modes conflict: if they do, then we must wait for
+		 * it to finish; otherwise we can fall through to lock this tuple
+		 * version without waiting.
+		 */
+		if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
+								LOCKMODE_from_mxstatus(wantedstatus)))
+		{
+			*needwait = true;
+		}
+
+		/*
+		 * If we set needwait above, then this value doesn't matter;
+		 * otherwise, this value signals to caller that it's okay to proceed.
+		 */
+		return HeapTupleMayBeUpdated;
+	}
+	else if (TransactionIdDidAbort(xid))
+		return HeapTupleMayBeUpdated;
+	else if (TransactionIdDidCommit(xid))
+	{
+		/*
+		 * The other transaction committed.  If it was only a locker, then the
+		 * lock is completely gone now and we can return success; but if it
+		 * was an update, then what we do depends on whether the two lock
+		 * modes conflict.  If they conflict, then we must report error to
+		 * caller. But if they don't, we can fall through to allow the current
+		 * transaction to lock the tuple.
+		 *
+		 * Note: the reason we worry about ISUPDATE here is because as soon as
+		 * a transaction ends, all its locks are gone and meaningless, and
+		 * thus we can ignore them; whereas its updates persist.  In the
+		 * TransactionIdIsInProgress case, above, we don't need to check
+		 * because we know the lock is still "alive" and thus a conflict needs
+		 * always be checked.
+		 */
+		if (!ISUPDATE_from_mxstatus(status))
+			return HeapTupleMayBeUpdated;
+
+		if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
+								LOCKMODE_from_mxstatus(wantedstatus)))
+			/* bummer */
+			return HeapTupleUpdated;
+
+		return HeapTupleMayBeUpdated;
+	}
+
+	/* Not in progress, not aborted, not committed -- must have crashed */
+	return HeapTupleMayBeUpdated;
+}
+
+
+/*
+ * Recursive part of heap_lock_updated_tuple
+ *
+ * Fetch the tuple pointed to by tid in rel, and mark it as locked by the given
+ * xid with the given mode; if this tuple is updated, recurse to lock the new
+ * version as well.
+ */
+static HTSU_Result
+heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid,
+							LockTupleMode mode)
+{
+	HTSU_Result result;
+	ItemPointerData tupid;
+	HeapTupleData 	mytup;
+	Buffer		buf;
+	uint16		new_infomask,
+				new_infomask2,
+				old_infomask,
+				old_infomask2;
+	TransactionId xmax,
+				new_xmax;
+	TransactionId priorXmax = InvalidTransactionId;
+	bool		cleared_all_frozen = false;
+	Buffer		vmbuffer = InvalidBuffer;
+	BlockNumber block;
+
+	ItemPointerCopy(tid, &tupid);
+
+	for (;;)
+	{
+		new_infomask = 0;
+		new_xmax = InvalidTransactionId;
+		block = ItemPointerGetBlockNumber(&tupid);
+
+		if (!heap_fetch(rel, &tupid, SnapshotAny, &mytup, &buf, false, NULL))
+		{
+			/*
+			 * if we fail to find the updated version of the tuple, it's
+			 * because it was vacuumed/pruned away after its creator
+			 * transaction aborted.  So behave as if we got to the end of the
+			 * chain, and there's no further tuple to lock: return success to
+			 * caller.
+			 */
+			return HeapTupleMayBeUpdated;
+		}
+
+l4:
+		CHECK_FOR_INTERRUPTS();
+
+		/*
+		 * Before locking the buffer, pin the visibility map page if it
+		 * appears to be necessary.  Since we haven't got the lock yet,
+		 * someone else might be in the middle of changing this, so we'll need
+		 * to recheck after we have the lock.
+		 */
+		if (PageIsAllVisible(BufferGetPage(buf)))
+			visibilitymap_pin(rel, block, &vmbuffer);
+		else
+			vmbuffer = InvalidBuffer;
+
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+		/*
+		 * If we didn't pin the visibility map page and the page has become
+		 * all visible while we were busy locking the buffer, we'll have to
+		 * unlock and re-lock, to avoid holding the buffer lock across I/O.
+		 * That's a bit unfortunate, but hopefully shouldn't happen often.
+		 */
+		if (vmbuffer == InvalidBuffer && PageIsAllVisible(BufferGetPage(buf)))
+		{
+			LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+			visibilitymap_pin(rel, block, &vmbuffer);
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+		}
+
+		/*
+		 * Check the tuple XMIN against prior XMAX, if any.  If we reached the
+		 * end of the chain, we're done, so return success.
+		 */
+		if (TransactionIdIsValid(priorXmax) &&
+			!TransactionIdEquals(HeapTupleHeaderGetXmin(mytup.t_data),
+								 priorXmax))
+		{
+			result = HeapTupleMayBeUpdated;
+			goto out_locked;
+		}
+
+		/*
+		 * Also check Xmin: if this tuple was created by an aborted
+		 * (sub)transaction, then we already locked the last live one in the
+		 * chain, thus we're done, so return success.
+		 */
+		if (TransactionIdDidAbort(HeapTupleHeaderGetXmin(mytup.t_data)))
+		{
+			UnlockReleaseBuffer(buf);
+			return HeapTupleMayBeUpdated;
+		}
+
+		old_infomask = mytup.t_data->t_infomask;
+		old_infomask2 = mytup.t_data->t_infomask2;
+		xmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
+
+		/*
+		 * If this tuple version has been updated or locked by some concurrent
+		 * transaction(s), what we do depends on whether our lock mode
+		 * conflicts with what those other transactions hold, and also on the
+		 * status of them.
+		 */
+		if (!(old_infomask & HEAP_XMAX_INVALID))
+		{
+			TransactionId rawxmax;
+			bool		needwait;
+
+			rawxmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
+			if (old_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				int			nmembers;
+				int			i;
+				MultiXactMember *members;
+
+				/*
+				 * We don't need a test for pg_upgrade'd tuples: this is only
+				 * applied to tuples after the first in an update chain.  Said
+				 * first tuple in the chain may well be locked-in-9.2-and-
+				 * pg_upgraded, but that one was already locked by our caller,
+				 * not us; and any subsequent ones cannot be because our
+				 * caller must necessarily have obtained a snapshot later than
+				 * the pg_upgrade itself.
+				 */
+				Assert(!HEAP_LOCKED_UPGRADED(mytup.t_data->t_infomask));
+
+				nmembers = GetMultiXactIdMembers(rawxmax, &members, false,
+												 HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
+				for (i = 0; i < nmembers; i++)
+				{
+					result = test_lockmode_for_conflict(members[i].status,
+														members[i].xid,
+														mode, &needwait);
+
+					/*
+					 * If the tuple was already locked by ourselves in a
+					 * previous iteration of this (say heap_lock_tuple was
+					 * forced to restart the locking loop because of a change
+					 * in xmax), then we hold the lock already on this tuple
+					 * version and we don't need to do anything; and this is
+					 * not an error condition either.  We just need to skip
+					 * this tuple and continue locking the next version in the
+					 * update chain.
+					 */
+					if (result == HeapTupleSelfUpdated)
+					{
+						pfree(members);
+						goto next;
+					}
+
+					if (needwait)
+					{
+						LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+						XactLockTableWait(members[i].xid, rel,
+										  &mytup.t_self,
+										  XLTW_LockUpdated);
+						pfree(members);
+						goto l4;
+					}
+					if (result != HeapTupleMayBeUpdated)
+					{
+						pfree(members);
+						goto out_locked;
+					}
+				}
+				if (members)
+					pfree(members);
+			}
+			else
+			{
+				MultiXactStatus status;
+
+				/*
+				 * For a non-multi Xmax, we first need to compute the
+				 * corresponding MultiXactStatus by using the infomask bits.
+				 */
+				if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
+				{
+					if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
+						status = MultiXactStatusForKeyShare;
+					else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
+						status = MultiXactStatusForShare;
+					else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
+					{
+						if (old_infomask2 & HEAP_KEYS_UPDATED)
+							status = MultiXactStatusForUpdate;
+						else
+							status = MultiXactStatusForNoKeyUpdate;
+					}
+					else
+					{
+						/*
+						 * LOCK_ONLY present alone (a pg_upgraded tuple marked
+						 * as share-locked in the old cluster) shouldn't be
+						 * seen in the middle of an update chain.
+						 */
+						elog(ERROR, "invalid lock status in tuple");
+					}
+				}
+				else
+				{
+					/* it's an update, but which kind? */
+					if (old_infomask2 & HEAP_KEYS_UPDATED)
+						status = MultiXactStatusUpdate;
+					else
+						status = MultiXactStatusNoKeyUpdate;
+				}
+
+				result = test_lockmode_for_conflict(status, rawxmax, mode,
+													&needwait);
+
+				/*
+				 * If the tuple was already locked by ourselves in a previous
+				 * iteration of this (say heap_lock_tuple was forced to
+				 * restart the locking loop because of a change in xmax), then
+				 * we hold the lock already on this tuple version and we don't
+				 * need to do anything; and this is not an error condition
+				 * either.  We just need to skip this tuple and continue
+				 * locking the next version in the update chain.
+				 */
+				if (result == HeapTupleSelfUpdated)
+					goto next;
+
+				if (needwait)
+				{
+					LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+					XactLockTableWait(rawxmax, rel, &mytup.t_self,
+									  XLTW_LockUpdated);
+					goto l4;
+				}
+				if (result != HeapTupleMayBeUpdated)
+				{
+					goto out_locked;
+				}
+			}
+		}
+
+		/* compute the new Xmax and infomask values for the tuple ... */
+		compute_new_xmax_infomask(xmax, old_infomask, mytup.t_data->t_infomask2,
+								  xid, mode, false,
+								  &new_xmax, &new_infomask, &new_infomask2);
+
+		if (PageIsAllVisible(BufferGetPage(buf)) &&
+			visibilitymap_clear(rel, block, vmbuffer,
+								VISIBILITYMAP_ALL_FROZEN))
+			cleared_all_frozen = true;
+
+		START_CRIT_SECTION();
+
+		/* ... and set them */
+		HeapTupleHeaderSetXmax(mytup.t_data, new_xmax);
+		mytup.t_data->t_infomask &= ~HEAP_XMAX_BITS;
+		mytup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+		mytup.t_data->t_infomask |= new_infomask;
+		mytup.t_data->t_infomask2 |= new_infomask2;
+
+		MarkBufferDirty(buf);
+
+		/* XLOG stuff */
+		if (RelationNeedsWAL(rel))
+		{
+			xl_heap_lock_updated xlrec;
+			XLogRecPtr	recptr;
+			Page		page = BufferGetPage(buf);
+
+			XLogBeginInsert();
+			XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
+
+			xlrec.offnum = ItemPointerGetOffsetNumber(&mytup.t_self);
+			xlrec.xmax = new_xmax;
+			xlrec.infobits_set = compute_infobits(new_infomask, new_infomask2);
+			xlrec.flags =
+				cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
+
+			XLogRegisterData((char *) &xlrec, SizeOfHeapLockUpdated);
+
+			recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_LOCK_UPDATED);
+
+			PageSetLSN(page, recptr);
+		}
+
+		END_CRIT_SECTION();
+
+next:
+		/* if we find the end of update chain, we're done. */
+		if (mytup.t_data->t_infomask & HEAP_XMAX_INVALID ||
+			ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid) ||
+			HeapTupleHeaderIsOnlyLocked(mytup.t_data))
+		{
+			result = HeapTupleMayBeUpdated;
+			goto out_locked;
+		}
+
+		/* tail recursion */
+		priorXmax = HeapTupleHeaderGetUpdateXid(mytup.t_data);
+		ItemPointerCopy(&(mytup.t_data->t_ctid), &tupid);
+		UnlockReleaseBuffer(buf);
+		if (vmbuffer != InvalidBuffer)
+			ReleaseBuffer(vmbuffer);
+	}
+
+	result = HeapTupleMayBeUpdated;
+
+out_locked:
+	UnlockReleaseBuffer(buf);
+
+	if (vmbuffer != InvalidBuffer)
+		ReleaseBuffer(vmbuffer);
+
+	return result;
+
+}
+
+/*
+ * heap_lock_updated_tuple
+ *		Follow update chain when locking an updated tuple, acquiring locks (row
+ *		marks) on the updated versions.
+ *
+ * The initial tuple is assumed to be already locked.
+ *
+ * This function doesn't check visibility, it just unconditionally marks the
+ * tuple(s) as locked.  If any tuple in the updated chain is being deleted
+ * concurrently (or updated with the key being modified), sleep until the
+ * transaction doing it is finished.
+ *
+ * Note that we don't acquire heavyweight tuple locks on the tuples we walk
+ * when we have to wait for other transactions to release them, as opposed to
+ * what heap_lock_tuple does.  The reason is that having more than one
+ * transaction walking the chain is probably uncommon enough that risk of
+ * starvation is not likely: one of the preconditions for being here is that
+ * the snapshot in use predates the update that created this tuple (because we
+ * started at an earlier version of the tuple), but at the same time such a
+ * transaction cannot be using repeatable read or serializable isolation
+ * levels, because that would lead to a serializability failure.
+ */
+static HTSU_Result
+heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid,
+						TransactionId xid, LockTupleMode mode)
+{
+	if (!ItemPointerEquals(&tuple->t_self, ctid))
+	{
+		/*
+		 * If this is the first possibly-multixact-able operation in the
+		 * current transaction, set my per-backend OldestMemberMXactId
+		 * setting. We can be certain that the transaction will never become a
+		 * member of any older MultiXactIds than that.  (We have to do this
+		 * even if we end up just using our own TransactionId below, since
+		 * some other backend could incorporate our XID into a MultiXact
+		 * immediately afterwards.)
+		 */
+		MultiXactIdSetOldestMember();
+
+		return heap_lock_updated_tuple_rec(rel, ctid, xid, mode);
+	}
+
+	/* nothing to lock */
+	return HeapTupleMayBeUpdated;
+}
+
+
+/*
+ *	heapam_lock_tuple - lock a tuple in shared or exclusive mode
+ *
+ * Note that this acquires a buffer pin, which the caller must release.
+ *
+ * Input parameters:
+ *	relation: relation containing tuple (caller must hold suitable lock)
+ *	tuple->t_self: TID of tuple to lock (rest of struct need not be valid)
+ *	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
+ *	follow_updates: if true, follow the update chain to also lock descendant
+ *		tuples.
+ *
+ * Output parameters:
+ *	*tuple: all fields filled in
+ *	*buffer: set to buffer holding tuple (pinned but not locked at exit)
+ *	*hufd: filled in failure cases (see below)
+ *
+ * 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.
+ *
+ * See README.tuplock for a thorough explanation of this mechanism.
+ */
+static HTSU_Result
+heapam_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;
+	ItemId		lp;
+	Page		page;
+	Buffer		vmbuffer = InvalidBuffer;
+	BlockNumber block;
+	TransactionId xid,
+				xmax;
+	uint16		old_infomask,
+				new_infomask,
+				new_infomask2;
+	bool		first_time = true;
+	bool		have_tuple_lock = false;
+	bool		cleared_all_frozen = false;
+	HeapTupleData	tuple;
+	Buffer 		buf;
+
+	Assert(stuple != NULL);
+
+	buf = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
+	block = ItemPointerGetBlockNumber(tid);
+	*buffer = buf;
+
+	/*
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
+	 */
+	if (PageIsAllVisible(BufferGetPage(buf)))
+		visibilitymap_pin(relation, block, &vmbuffer);
+
+	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+	page = BufferGetPage(buf);
+	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);
+
+l3:
+	result = HeapTupleSatisfiesUpdate(&tuple, cid, buf);
+
+	if (result == HeapTupleInvisible)
+	{
+		/*
+		 * This is possible, but only when locking a tuple for ON CONFLICT
+		 * UPDATE.  We return this value here rather than throwing an error in
+		 * order to give that case the opportunity to throw a more specific
+		 * error.
+		 */
+		result = HeapTupleInvisible;
+		goto out_locked;
+	}
+	else if (result == HeapTupleBeingUpdated || result == HeapTupleUpdated)
+	{
+		TransactionId xwait;
+		uint16		infomask;
+		uint16		infomask2;
+		bool		require_sleep;
+		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);
+
+		LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+
+		/*
+		 * If any subtransaction of the current top transaction already holds
+		 * a lock as strong as or stronger than what we're requesting, we
+		 * effectively hold the desired lock already.  We *must* succeed
+		 * without trying to take the tuple lock, else we will deadlock
+		 * against anyone wanting to acquire a stronger lock.
+		 *
+		 * Note we only do this the first time we loop on the HTSU result;
+		 * there is no point in testing in subsequent passes, because
+		 * evidently our own transaction cannot have acquired a new lock after
+		 * the first time we checked.
+		 */
+		if (first_time)
+		{
+			first_time = false;
+
+			if (infomask & HEAP_XMAX_IS_MULTI)
+			{
+				int			i;
+				int			nmembers;
+				MultiXactMember *members;
+
+				/*
+				 * We don't need to allow old multixacts here; if that had
+				 * been the case, HeapTupleSatisfiesUpdate would have returned
+				 * MayBeUpdated and we wouldn't be here.
+				 */
+				nmembers =
+					GetMultiXactIdMembers(xwait, &members, false,
+										  HEAP_XMAX_IS_LOCKED_ONLY(infomask));
+
+				for (i = 0; i < nmembers; i++)
+				{
+					/* only consider members of our own transaction */
+					if (!TransactionIdIsCurrentTransactionId(members[i].xid))
+						continue;
+
+					if (TUPLOCK_from_mxstatus(members[i].status) >= mode)
+					{
+						pfree(members);
+						result = HeapTupleMayBeUpdated;
+						goto out_unlocked;
+					}
+				}
+
+				if (members)
+					pfree(members);
+			}
+			else if (TransactionIdIsCurrentTransactionId(xwait))
+			{
+				switch (mode)
+				{
+					case LockTupleKeyShare:
+						Assert(HEAP_XMAX_IS_KEYSHR_LOCKED(infomask) ||
+							   HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
+							   HEAP_XMAX_IS_EXCL_LOCKED(infomask));
+						result = HeapTupleMayBeUpdated;
+						goto out_unlocked;
+					case LockTupleShare:
+						if (HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
+							HEAP_XMAX_IS_EXCL_LOCKED(infomask))
+						{
+							result = HeapTupleMayBeUpdated;
+							goto out_unlocked;
+						}
+						break;
+					case LockTupleNoKeyExclusive:
+						if (HEAP_XMAX_IS_EXCL_LOCKED(infomask))
+						{
+							result = HeapTupleMayBeUpdated;
+							goto out_unlocked;
+						}
+						break;
+					case LockTupleExclusive:
+						if (HEAP_XMAX_IS_EXCL_LOCKED(infomask) &&
+							infomask2 & HEAP_KEYS_UPDATED)
+						{
+							result = HeapTupleMayBeUpdated;
+							goto out_unlocked;
+						}
+						break;
+				}
+			}
+		}
+
+		/*
+		 * Initially assume that we will have to wait for the locking
+		 * transaction(s) to finish.  We check various cases below in which
+		 * this can be turned off.
+		 */
+		require_sleep = true;
+		if (mode == LockTupleKeyShare)
+		{
+			/*
+			 * If we're requesting KeyShare, and there's no update present, we
+			 * don't need to wait.  Even if there is an update, we can still
+			 * continue if the key hasn't been modified.
+			 *
+			 * However, if there are updates, we need to walk the update chain
+			 * to mark future versions of the row as locked, too.  That way,
+			 * if somebody deletes that future version, we're protected
+			 * against the key going away.  This locking of future versions
+			 * could block momentarily, if a concurrent transaction is
+			 * deleting a key; or it could return a value to the effect that
+			 * the transaction deleting the key has already committed.  So we
+			 * do this before re-locking the buffer; otherwise this would be
+			 * prone to deadlocks.
+			 *
+			 * Note that the TID we're locking was grabbed before we unlocked
+			 * the buffer.  For it to change while we're not looking, the
+			 * other properties we're testing for below after re-locking the
+			 * buffer would also change, in which case we would restart this
+			 * loop above.
+			 */
+			if (!(infomask2 & HEAP_KEYS_UPDATED))
+			{
+				bool		updated;
+
+				updated = !HEAP_XMAX_IS_LOCKED_ONLY(infomask);
+
+				/*
+				 * If there are updates, follow the update chain; bail out if
+				 * that cannot be done.
+				 */
+				if (follow_updates && updated)
+				{
+					HTSU_Result res;
+
+					res = heap_lock_updated_tuple(relation, &tuple, &t_ctid,
+												  GetCurrentTransactionId(),
+												  mode);
+					if (res != HeapTupleMayBeUpdated)
+					{
+						result = res;
+						/* recovery code expects to have buffer lock held */
+						LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+						goto failed;
+					}
+				}
+
+				LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+				/*
+				 * Make sure it's still an appropriate lock, else start over.
+				 * Also, if it wasn't updated before we released the lock, but
+				 * is updated now, we start over too; the reason is that we
+				 * 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) ||
+					 !updated))
+					goto l3;
+
+				/* Things look okay, so we can skip sleeping */
+				require_sleep = false;
+
+				/*
+				 * Note we allow Xmax to change here; other updaters/lockers
+				 * could have modified it before we grabbed the buffer lock.
+				 * However, this is not a problem, because with the recheck we
+				 * just did we ensure that they still don't conflict with the
+				 * lock we want.
+				 */
+			}
+		}
+		else if (mode == LockTupleShare)
+		{
+			/*
+			 * If we're requesting Share, we can similarly avoid sleeping if
+			 * there's no update and no exclusive lock present.
+			 */
+			if (HEAP_XMAX_IS_LOCKED_ONLY(infomask) &&
+				!HEAP_XMAX_IS_EXCL_LOCKED(infomask))
+			{
+				LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+				/*
+				 * 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))
+					goto l3;
+				require_sleep = false;
+			}
+		}
+		else if (mode == LockTupleNoKeyExclusive)
+		{
+			/*
+			 * If we're requesting NoKeyExclusive, we might also be able to
+			 * avoid sleeping; just ensure that there no conflicting lock
+			 * already acquired.
+			 */
+			if (infomask & HEAP_XMAX_IS_MULTI)
+			{
+				if (!DoesMultiXactIdConflict((MultiXactId) xwait, infomask,
+											 mode))
+				{
+					/*
+					 * No conflict, but if the xmax changed under us in the
+					 * meantime, start over.
+					 */
+					LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+					if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
+						!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple.t_data),
+											 xwait))
+						goto l3;
+
+					/* otherwise, we're good */
+					require_sleep = false;
+				}
+			}
+			else if (HEAP_XMAX_IS_KEYSHR_LOCKED(infomask))
+			{
+				LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+				/* if the xmax changed in the meantime, start over */
+				if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
+					!TransactionIdEquals(
+										 HeapTupleHeaderGetRawXmax(tuple.t_data),
+										 xwait))
+					goto l3;
+				/* otherwise, we're good */
+				require_sleep = false;
+			}
+		}
+
+		/*
+		 * As a check independent from those above, we can also avoid sleeping
+		 * if the current transaction is the sole locker of the tuple.  Note
+		 * that the strength of the lock already held is irrelevant; this is
+		 * not about recording the lock in Xmax (which will be done regardless
+		 * of this optimization, below).  Also, note that the cases where we
+		 * hold a lock stronger than we are requesting are already handled
+		 * above by not doing anything.
+		 *
+		 * Note we only deal with the non-multixact case here; MultiXactIdWait
+		 * is well equipped to deal with this situation on its own.
+		 */
+		if (require_sleep && !(infomask & HEAP_XMAX_IS_MULTI) &&
+			TransactionIdIsCurrentTransactionId(xwait))
+		{
+			/* ... but if the xmax changed in the meantime, start over */
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+			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));
+			require_sleep = false;
+		}
+
+		/*
+		 * Time to sleep on the other transaction/multixact, if necessary.
+		 *
+		 * If the other transaction is an update that's already committed,
+		 * then sleeping cannot possibly do any good: if we're required to
+		 * sleep, get out to raise an error instead.
+		 *
+		 * By here, we either have already acquired the buffer exclusive lock,
+		 * 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)
+		{
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+			goto failed;
+		}
+		else if (require_sleep)
+		{
+			/*
+			 * Acquire tuple lock to establish our priority for the tuple, or
+			 * die trying.  LockTuple will release us when we are next-in-line
+			 * for the tuple.  We must do this even if we are share-locking.
+			 *
+			 * If we are forced to "start over" below, we keep the tuple lock;
+			 * this arranges that we stay at the head of the line while
+			 * rechecking tuple state.
+			 */
+			if (!heap_acquire_tuplock(relation, tid, mode, wait_policy,
+									  &have_tuple_lock))
+			{
+				/*
+				 * This can only happen if wait_policy is Skip and the lock
+				 * couldn't be obtained.
+				 */
+				result = HeapTupleWouldBlock;
+				/* recovery code expects to have buffer lock held */
+				LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+				goto failed;
+			}
+
+			if (infomask & HEAP_XMAX_IS_MULTI)
+			{
+				MultiXactStatus status = get_mxact_status_for_lock(mode, false);
+
+				/* We only ever lock tuples, never update them */
+				if (status >= MultiXactStatusNoKeyUpdate)
+					elog(ERROR, "invalid lock mode in heap_lock_tuple");
+
+				/* wait for multixact to end, or die trying  */
+				switch (wait_policy)
+				{
+					case LockWaitBlock:
+						MultiXactIdWait((MultiXactId) xwait, status, infomask,
+										relation, &tuple.t_self, XLTW_Lock, NULL);
+						break;
+					case LockWaitSkip:
+						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
+														status, infomask, relation,
+														NULL))
+						{
+							result = HeapTupleWouldBlock;
+							/* recovery code expects to have buffer lock held */
+							LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+							goto failed;
+						}
+						break;
+					case LockWaitError:
+						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
+														status, infomask, relation,
+														NULL))
+							ereport(ERROR,
+									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+									 errmsg("could not obtain lock on row in relation \"%s\"",
+											RelationGetRelationName(relation))));
+
+						break;
+				}
+
+				/*
+				 * Of course, the multixact might not be done here: if we're
+				 * requesting a light lock mode, other transactions with light
+				 * locks could still be alive, as well as locks owned by our
+				 * own xact or other subxacts of this backend.  We need to
+				 * preserve the surviving MultiXact members.  Note that it
+				 * isn't absolutely necessary in the latter case, but doing so
+				 * is simpler.
+				 */
+			}
+			else
+			{
+				/* wait for regular transaction to end, or die trying */
+				switch (wait_policy)
+				{
+					case LockWaitBlock:
+						XactLockTableWait(xwait, relation, &tuple.t_self,
+										  XLTW_Lock);
+						break;
+					case LockWaitSkip:
+						if (!ConditionalXactLockTableWait(xwait))
+						{
+							result = HeapTupleWouldBlock;
+							/* recovery code expects to have buffer lock held */
+							LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+							goto failed;
+						}
+						break;
+					case LockWaitError:
+						if (!ConditionalXactLockTableWait(xwait))
+							ereport(ERROR,
+									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+									 errmsg("could not obtain lock on row in relation \"%s\"",
+											RelationGetRelationName(relation))));
+						break;
+				}
+			}
+
+			/* if there are updates, follow the update chain */
+			if (follow_updates && !HEAP_XMAX_IS_LOCKED_ONLY(infomask))
+			{
+				HTSU_Result res;
+
+				res = heap_lock_updated_tuple(relation, &tuple, &t_ctid,
+											  GetCurrentTransactionId(),
+											  mode);
+				if (res != HeapTupleMayBeUpdated)
+				{
+					result = res;
+					/* recovery code expects to have buffer lock held */
+					LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+					goto failed;
+				}
+			}
+
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+			/*
+			 * xwait is done, but if xwait had just locked the tuple then some
+			 * 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),
+									 xwait))
+				goto l3;
+
+			if (!(infomask & HEAP_XMAX_IS_MULTI))
+			{
+				/*
+				 * Otherwise check if it committed or aborted.  Note we cannot
+				 * be here if the tuple was only locked by somebody who didn't
+				 * conflict with us; that would have been handled above.  So
+				 * that transaction must necessarily be gone by now.  But
+				 * don't check for this in the multixact case, because some
+				 * locker transactions might still be running.
+				 */
+				UpdateXmaxHintBits(tuple.t_data, buf, xwait);
+			}
+		}
+
+		/* By here, we're certain that we hold buffer exclusive lock again */
+
+		/*
+		 * We may lock if previous xmax aborted, or if it committed but only
+		 * locked the tuple without updating it; or if we didn't have to wait
+		 * 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))
+			result = HeapTupleMayBeUpdated;
+		else
+			result = HeapTupleUpdated;
+	}
+
+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);
+		if (result == HeapTupleSelfUpdated)
+			hufd->cmax = HeapTupleHeaderGetCmax(tuple.t_data);
+		else
+			hufd->cmax = InvalidCommandId;
+		goto out_locked;
+	}
+
+	/*
+	 * If we didn't pin the visibility map page and the page has become all
+	 * visible while we were busy locking the buffer, or during some
+	 * subsequent window during which we had it unlocked, we'll have to unlock
+	 * and re-lock, to avoid holding the buffer lock across I/O.  That's a bit
+	 * unfortunate, especially since we'll now have to recheck whether the
+	 * tuple has been locked or updated under us, but hopefully it won't
+	 * happen very often.
+	 */
+	if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
+	{
+		LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+		visibilitymap_pin(relation, block, &vmbuffer);
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+		goto l3;
+	}
+
+	xmax = HeapTupleHeaderGetRawXmax(tuple.t_data);
+	old_infomask = tuple.t_data->t_infomask;
+
+	/*
+	 * If this is the first possibly-multixact-able operation in the current
+	 * transaction, set my per-backend OldestMemberMXactId setting. We can be
+	 * certain that the transaction will never become a member of any older
+	 * MultiXactIds than that.  (We have to do this even if we end up just
+	 * using our own TransactionId below, since some other backend could
+	 * incorporate our XID into a MultiXact immediately afterwards.)
+	 */
+	MultiXactIdSetOldestMember();
+
+	/*
+	 * Compute the new xmax and infomask to store into the tuple.  Note we do
+	 * 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,
+							  GetCurrentTransactionId(), mode, false,
+							  &xid, &new_infomask, &new_infomask2);
+
+	START_CRIT_SECTION();
+
+	/*
+	 * Store transaction information of xact locking the tuple.
+	 *
+	 * Note: Cmax is meaningless in this context, so don't set it; this avoids
+	 * possibly generating a useless combo CID.  Moreover, if we're locking a
+	 * previously updated tuple, it's important to preserve the Cmax.
+	 *
+	 * 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;
+	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
+		HeapTupleHeaderClearHotUpdated(tuple.t_data);
+	HeapTupleHeaderSetXmax(tuple.t_data, xid);
+
+	/*
+	 * Make sure there is no forward chain link in t_ctid.  Note that in the
+	 * cases where the tuple has been updated, we must not overwrite t_ctid,
+	 * because it was set by the updater.  Moreover, if the tuple has been
+	 * updated, we need to follow the update chain to lock the new versions of
+	 * the tuple as well.
+	 */
+	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
+		tuple.t_data->t_ctid = *tid;
+
+	/* Clear only the all-frozen bit on visibility map if needed */
+	if (PageIsAllVisible(page) &&
+		visibilitymap_clear(relation, block, vmbuffer,
+							VISIBILITYMAP_ALL_FROZEN))
+		cleared_all_frozen = true;
+
+
+	MarkBufferDirty(buf);
+
+	/*
+	 * XLOG stuff.  You might think that we don't need an XLOG record because
+	 * there is no state change worth restoring after a crash.  You would be
+	 * wrong however: we have just written either a TransactionId or a
+	 * MultiXactId that may never have been seen on disk before, and we need
+	 * to make sure that there are XLOG entries covering those ID numbers.
+	 * Else the same IDs might be re-used after a crash, which would be
+	 * disastrous if this page made it to disk before the crash.  Essentially
+	 * we have to enforce the WAL log-before-data rule even in this case.
+	 * (Also, in a PITR log-shipping or 2PC environment, we have to have XLOG
+	 * entries for everything anyway.)
+	 */
+	if (RelationNeedsWAL(relation))
+	{
+		xl_heap_lock xlrec;
+		XLogRecPtr	recptr;
+
+		XLogBeginInsert();
+		XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
+
+		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple.t_self);
+		xlrec.locking_xid = xid;
+		xlrec.infobits_set = compute_infobits(new_infomask,
+											  tuple.t_data->t_infomask2);
+		xlrec.flags = cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
+		XLogRegisterData((char *) &xlrec, SizeOfHeapLock);
+
+		/* we don't decode row locks atm, so no need to log the origin */
+
+		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_LOCK);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	result = HeapTupleMayBeUpdated;
+
+out_locked:
+	LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+
+out_unlocked:
+	if (BufferIsValid(vmbuffer))
+		ReleaseBuffer(vmbuffer);
+
+	/*
+	 * Don't update the visibility map here. Locking a tuple doesn't change
+	 * visibility info.
+	 */
+
+	/*
+	 * Now that we have successfully marked the tuple as locked, we can
+	 * release the lmgr tuple lock, if we had it.
+	 */
+	if (have_tuple_lock)
+		UnlockTupleTuplock(relation, tid, mode);
+
+	*stuple = heap_copytuple(&tuple);
+	return result;
+}
+
+/*
+ *	heapam_get_latest_tid -  get the latest tid of a specified tuple
+ *
+ * Actually, this gets the latest version that is visible according to
+ * the passed snapshot.  You can pass SnapshotDirty to get the very latest,
+ * possibly uncommitted version.
+ *
+ * *tid is both an input and an output parameter: it is updated to
+ * show the latest version of the row.  Note that it will not be changed
+ * if no version of the row passes the snapshot test.
+ */
+static void
+heapam_get_latest_tid(Relation relation,
+					Snapshot snapshot,
+					ItemPointer tid)
+{
+	BlockNumber blk;
+	ItemPointerData ctid;
+	TransactionId priorXmax;
+
+	/* this is to avoid Assert failures on bad input */
+	if (!ItemPointerIsValid(tid))
+		return;
+
+	/*
+	 * Since this can be called with user-supplied TID, don't trust the input
+	 * too much.  (RelationGetNumberOfBlocks is an expensive check, so we
+	 * don't check t_ctid links again this way.  Note that it would not do to
+	 * call it just once and save the result, either.)
+	 */
+	blk = ItemPointerGetBlockNumber(tid);
+	if (blk >= RelationGetNumberOfBlocks(relation))
+		elog(ERROR, "block number %u is out of range for relation \"%s\"",
+			 blk, RelationGetRelationName(relation));
+
+	/*
+	 * Loop to chase down t_ctid links.  At top of loop, ctid is the tuple we
+	 * need to examine, and *tid is the TID we will return if ctid turns out
+	 * to be bogus.
+	 *
+	 * Note that we will loop until we reach the end of the t_ctid chain.
+	 * Depending on the snapshot passed, there might be at most one visible
+	 * version of the row, but we don't try to optimize for that.
+	 */
+	ctid = *tid;
+	priorXmax = InvalidTransactionId;	/* cannot check first XMIN */
+	for (;;)
+	{
+		Buffer		buffer;
+		Page		page;
+		OffsetNumber offnum;
+		ItemId		lp;
+		HeapTupleData tp;
+		bool		valid;
+
+		/*
+		 * Read, pin, and lock the page.
+		 */
+		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&ctid));
+		LockBuffer(buffer, BUFFER_LOCK_SHARE);
+		page = BufferGetPage(buffer);
+		TestForOldSnapshot(snapshot, relation, page);
+
+		/*
+		 * Check for bogus item number.  This is not treated as an error
+		 * condition because it can happen while following a t_ctid link. We
+		 * just assume that the prior tid is OK and return it unchanged.
+		 */
+		offnum = ItemPointerGetOffsetNumber(&ctid);
+		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+		lp = PageGetItemId(page, offnum);
+		if (!ItemIdIsNormal(lp))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+
+		/* OK to access the tuple */
+		tp.t_self = ctid;
+		tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+		tp.t_len = ItemIdGetLength(lp);
+		tp.t_tableOid = RelationGetRelid(relation);
+
+		/*
+		 * After following a t_ctid link, we might arrive at an unrelated
+		 * tuple.  Check for XMIN match.
+		 */
+		if (TransactionIdIsValid(priorXmax) &&
+			!TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(tp.t_data)))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+
+		/*
+		 * Check time qualification of tuple; if visible, set it as the new
+		 * result candidate.
+		 */
+		valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, &tp, snapshot, buffer);
+		CheckForSerializableConflictOut(valid, relation, &tp, buffer, snapshot);
+		if (valid)
+			*tid = ctid;
+
+		/*
+		 * If there's a valid t_ctid link, follow it, else we're done.
+		 */
+		if ((tp.t_data->t_infomask & HEAP_XMAX_INVALID) ||
+			HeapTupleHeaderIsOnlyLocked(tp.t_data) ||
+			ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+
+		ctid = tp.t_data->t_ctid;
+		priorXmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
+		UnlockReleaseBuffer(buffer);
+	}							/* end of loop */
+}
+
+
+/*
+ *	heapam_sync		- sync a heap, for use when no WAL has been written
+ *
+ * This forces the heap contents (including TOAST heap if any) down to disk.
+ * If we skipped using WAL, and WAL is otherwise needed, we must force the
+ * relation down to disk before it's safe to commit the transaction.  This
+ * requires writing out any dirty buffers and then doing a forced fsync.
+ *
+ * Indexes are not touched.  (Currently, index operations associated with
+ * the commands that use this are WAL-logged and so do not need fsync.
+ * That behavior might change someday, but in any case it's likely that
+ * any fsync decisions required would be per-index and hence not appropriate
+ * to be done here.)
+ */
+static void
+heapam_sync(Relation rel)
+{
+	/* non-WAL-logged tables never need fsync */
+	if (!RelationNeedsWAL(rel))
+		return;
+
+	/* main heap */
+	FlushRelationBuffers(rel);
+	/* FlushRelationBuffers will have opened rd_smgr */
+	smgrimmedsync(rel->rd_smgr, MAIN_FORKNUM);
+
+	/* FSM is not critical, don't bother syncing it */
+
+	/* toast heap, if any */
+	if (OidIsValid(rel->rd_rel->reltoastrelid))
+	{
+		Relation	toastrel;
+
+		toastrel = heap_open(rel->rd_rel->reltoastrelid, AccessShareLock);
+		FlushRelationBuffers(toastrel);
+		smgrimmedsync(toastrel->rd_smgr, MAIN_FORKNUM);
+		heap_close(toastrel, AccessShareLock);
+	}
+}
+
+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 bool
+heapam_tuple_is_heaopnly(StorageTuple tuple)
+{
+	return HeapTupleIsHeapOnly((HeapTuple)tuple);
+}
+
+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)
+{
+	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
+
+    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 = heapam_multi_insert;
+
+    amroutine->speculative_finish = heapam_finish_speculative;
+    amroutine->speculative_abort = heapam_abort_speculative;
+
+    amroutine->get_tuple_data = heapam_get_tuple_data;
+    amroutine->tuple_is_heaponly = heapam_tuple_is_heaopnly;
+    amroutine->tuple_from_datum = heapam_form_tuple_by_datum;
+    amroutine->tuple_get_latest_tid = heapam_get_latest_tid;
+
+    amroutine->relation_sync = heapam_sync;
 
 	amroutine->slot_storageam = heapam_storage_slot_handler;
 
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 191f088..8fba61c 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -111,6 +111,7 @@
 #include "access/heapam_common.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"
@@ -127,13 +128,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
@@ -358,7 +359,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/storageam.c b/src/backend/access/heap/storageam.c
new file mode 100644
index 0000000..d1d7364
--- /dev/null
+++ b/src/backend/access/heap/storageam.c
@@ -0,0 +1,306 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/heap/storageam.c
+ *
+ *
+ * NOTES
+ *	  This file contains the storage_ routines which implement
+ *	  the POSTGRES storage access method used for all POSTGRES
+ *	  relations.
+ *
+ *-------------------------------------------------------------------------
+ */
+#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 "access/relscan.h"
+#include "access/storageam.h"
+#include "access/storageamapi.h"
+#include "access/tuptoaster.h"
+#include "access/valid.h"
+#include "access/visibilitymap.h"
+#include "access/xloginsert.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/bufmgr.h"
+#include "storage/lmgr.h"
+#include "storage/predicate.h"
+#include "storage/procarray.h"
+#include "storage/smgr.h"
+#include "storage/spin.h"
+#include "utils/builtins.h"
+#include "utils/inval.h"
+#include "utils/rel.h"
+#include "utils/tqual.h"
+
+
+/*
+ *	storage_fetch		- retrieve tuple with given tid
+ *
+ * On entry, tuple->t_self is the TID to fetch.  We pin the buffer holding
+ * the tuple, fill in the remaining fields of *tuple, and check the tuple
+ * against the specified snapshot.
+ *
+ * If successful (tuple found and passes snapshot time qual), then *userbuf
+ * is set to the buffer holding the tuple and TRUE is returned.  The caller
+ * must unpin the buffer when done with the tuple.
+ *
+ * If the tuple is not found (ie, item number references a deleted slot),
+ * then tuple->t_data is set to NULL and FALSE is returned.
+ *
+ * If the tuple is found but fails the time qual check, then FALSE is returned
+ * but tuple->t_data is left pointing to the tuple.
+ *
+ * keep_buf determines what is done with the buffer in the FALSE-result cases.
+ * When the caller specifies keep_buf = true, we retain the pin on the buffer
+ * and return it in *userbuf (so the caller must eventually unpin it); when
+ * keep_buf = false, the pin is released and *userbuf is set to InvalidBuffer.
+ *
+ * stats_relation is the relation to charge the heap_fetch operation against
+ * for statistical purposes.  (This could be the heap rel itself, an
+ * associated index, or NULL to not count the fetch at all.)
+ *
+ * heap_fetch does not follow HOT chains: only the exact TID requested will
+ * be fetched.
+ *
+ * It is somewhat inconsistent that we ereport() on invalid block number but
+ * return false on invalid item number.  There are a couple of reasons though.
+ * One is that the caller can relatively easily check the block number for
+ * validity, but cannot check the item number without reading the page
+ * himself.  Another is that when we are following a t_ctid link, we can be
+ * reasonably confident that the page number is valid (since VACUUM shouldn't
+ * truncate off the destination page without having killed the referencing
+ * tuple first), but the item number might well not be good.
+ */
+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
+ *
+ * Note that this acquires a buffer pin, which the caller must release.
+ *
+ * Input parameters:
+ *	relation: relation containing tuple (caller must hold suitable lock)
+ *	tuple->t_self: TID of tuple to lock (rest of struct need not be valid)
+ *	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
+ *	follow_updates: if true, follow the update chain to also lock descendant
+ *		tuples.
+ *
+ * Output parameters:
+ *	*tuple: all fields filled in
+ *	*buffer: set to buffer holding tuple (pinned but not locked at exit)
+ *	*hufd: filled in failure cases (see below)
+ *
+ * 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.
+ *
+ * See README.tuplock for a thorough explanation of this mechanism.
+ */
+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)
+{
+	return relation->rd_stamroutine->tuple_insert(relation, slot, cid,
+							options, bistate);
+}
+
+/*
+ * 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,
+				   CommandId cid, Snapshot crosscheck, bool wait,
+				   HeapUpdateFailureData *hufd, LockTupleMode *lockmode)
+{
+	return relation->rd_stamroutine->tuple_update(relation, otid, slot, cid,
+							crosscheck, wait, hufd, lockmode);
+}
+
+
+/*
+ *	storage_multi_insert	- insert multiple tuple into a storage
+ *
+ * This is like heap_insert(), but inserts multiple tuples in one operation.
+ * That's faster than calling heap_insert() in a loop, because when multiple
+ * tuples can be inserted on a single page, we can write just a single WAL
+ * record covering all of them, and only need to lock/unlock the page once.
+ *
+ * Note: this leaks memory into the current memory context. You can create a
+ * temporary context before calling this, if that's a problem.
+ */
+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_finish_speculative - mark speculative insertion as successful
+ *
+ * To successfully finish a speculative insertion we have to clear speculative
+ * token from tuple.  To do so the t_ctid field, which will contain a
+ * speculative token value, is modified in place to point to the tuple itself,
+ * which is characteristic of a newly inserted ordinary tuple.
+ *
+ * NB: It is not ok to commit without either finishing or aborting a
+ * speculative insertion.  We could treat speculative tuples of committed
+ * transactions implicitly as completed, but then we would have to be prepared
+ * to deal with speculative tokens on committed tuples.  That wouldn't be
+ * difficult - no-one looks at the ctid field of a tuple with invalid xmax -
+ * but clearing the token at completion isn't very expensive either.
+ * An explicit confirmation WAL record also makes logical decoding simpler.
+ */
+void
+storage_finish_speculative(Relation relation, TupleTableSlot *slot)
+{
+	relation->rd_stamroutine->speculative_finish(relation, slot);
+}
+
+/*
+ *	storage_abort_speculative - kill a speculatively inserted tuple
+ *
+ * Marks a tuple that was speculatively inserted in the same command as dead,
+ * by setting its xmin as invalid.  That makes it immediately appear as dead
+ * to all transactions, including our own.  In particular, it makes
+ * HeapTupleSatisfiesDirty() regard the tuple as dead, so that another backend
+ * inserting a duplicate key value won't unnecessarily wait for our whole
+ * transaction to finish (it'll just wait for our speculative insertion to
+ * finish).
+ *
+ * Killing the tuple prevents "unprincipled deadlocks", which are deadlocks
+ * that arise due to a mutual dependency that is not user visible.  By
+ * definition, unprincipled deadlocks cannot be prevented by the user
+ * reordering lock acquisition in client code, because the implementation level
+ * lock acquisitions are not under the user's direct control.  If speculative
+ * inserters did not take this precaution, then under high concurrency they
+ * could deadlock with each other, which would not be acceptable.
+ *
+ * This is somewhat redundant with heap_delete, but we prefer to have a
+ * dedicated routine with stripped down requirements.  Note that this is also
+ * used to delete the TOAST tuples created during speculative insertion.
+ *
+ * This routine does not affect logical decoding as it only looks at
+ * confirmation records.
+ */
+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);
+}
+
+bool
+storage_tuple_is_heaponly(Relation relation, StorageTuple tuple)
+{
+	return relation->rd_stamroutine->tuple_is_heaponly(tuple);
+}
+
+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
+ *
+ * This forces the heap contents (including TOAST heap if any) down to disk.
+ * If we skipped using WAL, and WAL is otherwise needed, we must force the
+ * relation down to disk before it's safe to commit the transaction.  This
+ * requires writing out any dirty buffers and then doing a forced fsync.
+ *
+ * Indexes are not touched.  (Currently, index operations associated with
+ * the commands that use this are WAL-logged and so do not need fsync.
+ * That behavior might change someday, but in any case it's likely that
+ * any fsync decisions required would be per-index and hence not appropriate
+ * to be done here.)
+ */
+void
+storage_sync(Relation rel)
+{
+	rel->rd_stamroutine->relation_sync(rel);
+}
diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index 5a8f1da..d766a6e 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,12 @@ 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/commands/copy.c b/src/backend/commands/copy.c
index 8456bfd..c81ddf5 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -22,6 +22,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"
@@ -2694,8 +2695,6 @@ CopyFrom(CopyState cstate)
 
 			if (slot == NULL)	/* "do nothing" */
 				skip_tuple = true;
-			else				/* trigger might have changed tuple */
-				tuple = ExecHeapifySlot(slot);
 		}
 
 		if (!skip_tuple)
@@ -2758,19 +2757,18 @@ 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);
+					storage_insert(resultRelInfo->ri_RelationDesc, slot, mycid, hi_options, bistate);
 
 					if (resultRelInfo->ri_NumIndices > 0)
 						recheckIndexes = ExecInsertIndexTuples(slot,
-															   &(tuple->t_self),
+															   &(slot->tts_tid),
 															   estate,
 															   false,
 															   NULL,
 															   NIL);
 
 					/* AFTER ROW INSERT Triggers */
-					ExecARInsertTriggers(estate, resultRelInfo, tuple,
+					ExecARInsertTriggers(estate, resultRelInfo, slot,
 										 recheckIndexes, cstate->transition_capture);
 
 					list_free(recheckIndexes);
@@ -2866,7 +2864,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;
 }
@@ -2899,12 +2897,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);
 
 	/*
@@ -2923,7 +2921,7 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 				ExecInsertIndexTuples(myslot, &(bufferedTuples[i]->t_self),
 									  estate, false, NULL, NIL);
 			ExecARInsertTriggers(estate, resultRelInfo,
-								 bufferedTuples[i],
+								 myslot,
 								 recheckIndexes, cstate->transition_capture);
 			list_free(recheckIndexes);
 		}
@@ -2940,8 +2938,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 a0ec444..d119149 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,24 @@ 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);
+		slot->tts_tupleOid = InvalidOid;
 
-	heap_insert(myState->rel,
-				tuple,
-				myState->output_cid,
-				myState->hi_options,
-				myState->bistate);
+	storage_insert(myState->rel,
+					 slot,
+					myState->output_cid,
+					myState->hi_options,
+					myState->bistate);
 
 	/* We know this is a newly created relation, so there are no indexes */
 
@@ -619,7 +619,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 b440740..6102481 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,16 +492,15 @@ 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);
+	ExecMaterializeSlot(slot);
 
-	heap_insert(myState->transientrel,
-				tuple,
+	storage_insert(myState->transientrel,
+						slot,
 				myState->output_cid,
 				myState->hi_options,
 				myState->bistate);
@@ -522,7 +522,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 d2167ed..bca2a4c 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"
@@ -4652,7 +4653,7 @@ 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);
 
 			ResetExprContext(econtext);
 
@@ -4676,7 +4677,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 8634473..d2438ce 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2411,17 +2411,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 *
@@ -3061,9 +3065,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)
 	{
@@ -3079,11 +3084,11 @@ GetTupleForTrigger(EState *estate,
 		 * lock tuple for update
 		 */
 ltrmark:;
-		tuple.t_self = *tid;
-		test = heap_lock_tuple(relation, &tuple,
+		test = storage_lock_tuple(relation, tid, &tuple,
 							   estate->es_output_cid,
 							   lockmode, LockWaitBlock,
 							   false, &buffer, &hufd);
+		result = tuple;
 		switch (test)
 		{
 			case HeapTupleSelfUpdated:
@@ -3115,7 +3120,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;
@@ -3161,6 +3167,7 @@ ltrmark:;
 	{
 		Page		page;
 		ItemId		lp;
+		HeapTupleData tupledata;
 
 		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 
@@ -3179,17 +3186,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;
 }
 
@@ -3898,8 +3905,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;
@@ -3974,10 +3981,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
@@ -3991,10 +3997,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/execMain.c b/src/backend/executor/execMain.c
index 4b594d4..a7127e4 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1894,7 +1894,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 +1974,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				 */
 				if (resultRelInfo->ri_PartitionRoot)
 				{
-					HeapTuple	tuple = ExecFetchSlotTuple(slot);
+					StorageTuple	tuple = ExecFetchSlotTuple(slot);
 					TupleConversionMap *map;
 
 					rel = resultRelInfo->ri_PartitionRoot;
@@ -2021,7 +2021,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 +2480,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 +2498,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 +2531,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 +2564,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 +2579,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,8 +2598,8 @@ 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),
-									 priorXmax))
+			t_data = storage_tuple_get_data(relation, tuple, XMIN);
+			if (!TransactionIdEquals(t_data.xid, priorXmax))
 			{
 				ReleaseBuffer(buffer);
 				return NULL;
@@ -2617,7 +2620,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,17 +2650,20 @@ 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,
+			test = storage_lock_tuple(relation, tid, tuple,
 								   estate->es_output_cid,
 								   lockmode, wait_policy,
 								   false, &buffer, &hufd);
@@ -2695,12 +2702,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 +2732,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 +2740,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,8 +2749,8 @@ 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),
-								 priorXmax))
+		t_data = storage_tuple_get_data(relation, tuple, XMIN);
+		if (!TransactionIdEquals(t_data.xid, priorXmax))
 		{
 			ReleaseBuffer(buffer);
 			return NULL;
@@ -2762,7 +2768,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 +2778,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 +2836,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 +2855,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 +2883,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 +2914,6 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 
 		if (erm->markType == ROW_MARK_REFERENCE)
 		{
-			HeapTuple	copyTuple;
-
 			Assert(erm->relation != NULL);
 
 			/* fetch the tuple's ctid */
@@ -2929,11 +2937,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,
+				tuple = fdwroutine->RefetchForeignRow(epqstate->estate,
 														  erm,
 														  datum,
 														  &updated);
-				if (copyTuple == NULL)
+				if (tuple == NULL)
 					elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck");
 
 				/*
@@ -2947,23 +2955,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,
+				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 +2976,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);
+
 		}
 	}
 }
@@ -3151,8 +3147,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 d424074..e2bcf90 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),
+		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),
+		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,7 @@ void
 ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 {
 	bool		skip_tuple = false;
-	HeapTuple	tuple;
+	StorageTuple	tuple;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 
@@ -349,6 +351,7 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 	if (!skip_tuple)
 	{
 		List	   *recheckIndexes = NIL;
+		tuple_data	t_data;
 
 		/* Check the constraints of the tuple */
 		if (rel->rd_att->constr)
@@ -359,14 +362,15 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 
 		/* OK, store the tuple and create index entries for it */
 		simple_heap_insert(rel, tuple);
+		t_data = storage_tuple_get_data(rel, tuple, TID);
 
 		if (resultRelInfo->ri_NumIndices > 0)
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+			recheckIndexes = ExecInsertIndexTuples(slot, &(t_data.tid),
 												   estate, false, NULL,
 												   NIL);
 
 		/* AFTER ROW INSERT Triggers */
-		ExecARInsertTriggers(estate, resultRelInfo, tuple,
+		ExecARInsertTriggers(estate, resultRelInfo, slot,
 							 recheckIndexes, NULL);
 
 		/*
@@ -390,7 +394,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);
@@ -426,8 +430,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 		/* OK, update the tuple and index entries for it */
 		simple_heap_update(rel, tid, tuple);
 
-		if (resultRelInfo->ri_NumIndices > 0 &&
-			!HeapTupleIsHeapOnly(tuple))
+		if (resultRelInfo->ri_NumIndices > 0 && !storage_tuple_is_heaponly(rel, tuple))
 			recheckIndexes = ExecInsertIndexTuples(slot, tid,
 												   estate, false, NULL,
 												   NIL);
diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c
index 9389560..f06f34a 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,
+		test = storage_lock_tuple(erm->relation, &tid, &tuple,
 							   estate->es_output_cid,
 							   lockmode, erm->waitPolicy, true,
 							   &buffer, &hufd);
-		ReleaseBuffer(buffer);
+		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,
+			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 7bf8bef..e8e818c 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/executor.h"
 #include "executor/nodeModifyTable.h"
@@ -164,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;
 
@@ -191,7 +192,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
 static void
 ExecCheckHeapTupleVisible(EState *estate,
 						  Relation rel,
-						  HeapTuple tuple,
+						  StorageTuple tuple,
 						  Buffer buffer)
 {
 	if (!IsolationUsesXactSnapshot())
@@ -204,13 +205,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")));
@@ -226,19 +229,20 @@ ExecCheckTIDVisible(EState *estate,
 					ResultRelInfo *relinfo,
 					ItemPointer tid)
 {
+	Buffer buffer;
 	Relation	rel = relinfo->ri_RelationDesc;
-	Buffer		buffer;
-	HeapTupleData tuple;
+	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);
 }
 
 /* ----------------------------------------------------------------
@@ -259,7 +263,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,
 	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
 	 */
 	resultRelInfo = estate->es_result_relation_info;
@@ -284,6 +282,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.
@@ -371,19 +371,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.
@@ -401,9 +413,6 @@ ExecInsert(ModifyTableState *mtstate,
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
-
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -415,9 +424,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)
@@ -433,14 +439,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;
 	}
@@ -460,7 +464,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
@@ -551,24 +556,24 @@ 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,
+			newId = storage_insert(resultRelationDesc, slot,
 								estate->es_output_cid,
 								HEAP_INSERT_SPECULATIVE,
 								NULL);
 
 			/* insert index entries for tuple */
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+			recheckIndexes = ExecInsertIndexTuples(slot, &(slot->tts_tid),
 												   estate, true, &specConflict,
 												   arbiterIndexes);
 
 			/* adjust the tuple's state accordingly */
 			if (!specConflict)
-				heap_finish_speculative(resultRelationDesc, tuple);
+				storage_finish_speculative(resultRelationDesc, slot);
 			else
-				heap_abort_speculative(resultRelationDesc, tuple);
+				storage_abort_speculative(resultRelationDesc, slot);
 
 			/*
 			 * Wake up anyone waiting for our decision.  They will re-check
@@ -596,17 +601,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,
+			newId = storage_insert(resultRelationDesc, slot,
 								estate->es_output_cid,
 								0, NULL);
 
 			/* insert index entries for tuple */
 			if (resultRelInfo->ri_NumIndices > 0)
-				recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+				recheckIndexes = ExecInsertIndexTuples(slot, &(slot->tts_tid),
 													   estate, false, NULL,
 													   arbiterIndexes);
 		}
@@ -616,11 +618,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);
@@ -671,7 +673,7 @@ ExecInsert(ModifyTableState *mtstate,
 static TupleTableSlot *
 ExecDelete(ModifyTableState *mtstate,
 		   ItemPointer tupleid,
-		   HeapTuple oldtuple,
+		   StorageTuple oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate,
@@ -716,8 +718,6 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
-		HeapTuple	tuple;
-
 		/*
 		 * delete from foreign table: let the FDW do it
 		 *
@@ -743,8 +743,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
 	{
@@ -758,7 +760,7 @@ ExecDelete(ModifyTableState *mtstate,
 		 * mode transactions.
 		 */
 ldelete:;
-		result = heap_delete(resultRelationDesc, tupleid,
+		result = storage_delete(resultRelationDesc, tupleid,
 							 estate->es_output_cid,
 							 estate->es_crosscheck_snapshot,
 							 true /* wait for commit */ ,
@@ -858,7 +860,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)
@@ -872,20 +874,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);
@@ -894,7 +895,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))
@@ -931,14 +932,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;
@@ -1003,14 +1004,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
 	{
@@ -1020,7 +1021,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
@@ -1056,7 +1057,7 @@ lreplace:;
 		 * needed for referential integrity updates in transaction-snapshot
 		 * mode transactions.
 		 */
-		result = heap_update(resultRelationDesc, tupleid, tuple,
+		result = storage_update(resultRelationDesc, tupleid, slot,
 							 estate->es_output_cid,
 							 estate->es_crosscheck_snapshot,
 							 true /* wait for commit */ ,
@@ -1148,8 +1149,8 @@ lreplace:;
 		 *
 		 * 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),
+		if ((resultRelInfo->ri_NumIndices > 0) && !storage_tuple_is_heaponly(resultRelationDesc, tuple))
+			recheckIndexes = ExecInsertIndexTuples(slot, &(slot->tts_tid),
 												   estate, false, NULL, NIL);
 	}
 
@@ -1206,11 +1207,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);
@@ -1221,10 +1223,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:
@@ -1249,7 +1249,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"),
@@ -1280,7 +1281,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:
@@ -1308,10 +1311,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
@@ -1326,7 +1329,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 */
 	}
@@ -1366,12 +1371,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;
 }
 
@@ -1552,7 +1559,7 @@ ExecModifyTable(PlanState *pstate)
 	ItemPointer tupleid = NULL;
 	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 0ee76e7..8a6b217 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;
@@ -332,12 +333,6 @@ TidNext(TidScanState *node)
 	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.
 	 */
 	bBackward = ScanDirectionIsBackward(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 083f7d6..52779b7 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"
@@ -354,7 +355,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);
@@ -389,7 +390,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 4e41024..cdd45ef 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -133,40 +133,19 @@ 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,
-		   HeapTuple tuple, Buffer *userbuf, bool keep_buf,
-		   Relation stats_relation);
+extern Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid,
+			int options, BulkInsertState bistate);
 extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
 					   Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
 					   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);
 
 extern BulkInsertState GetBulkInsertState(void);
 extern void FreeBulkInsertState(BulkInsertState);
 extern void ReleaseBulkInsertStatePin(BulkInsertState bistate);
 
-extern Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid,
-			int options, BulkInsertState bistate);
-extern void heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
-				  CommandId cid, int options, BulkInsertState bistate);
-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 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,
-				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);
@@ -179,7 +158,6 @@ extern void simple_heap_delete(Relation relation, ItemPointer tid);
 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);
 
 /* in heap/pruneheap.c */
diff --git a/src/include/access/heapam_common.h b/src/include/access/heapam_common.h
index 1fe15ed..799b4ed 100644
--- a/src/include/access/heapam_common.h
+++ b/src/include/access/heapam_common.h
@@ -34,6 +34,111 @@
 #include "utils/relcache.h"
 #include "utils/snapshot.h"
 
+/*
+ * Each tuple lock mode has a corresponding heavyweight lock, and one or two
+ * corresponding MultiXactStatuses (one to merely lock tuples, another one to
+ * update them).  This table (and the macros below) helps us determine the
+ * heavyweight lock mode and MultiXactStatus values to use for any particular
+ * tuple lock strength.
+ *
+ * Don't look at lockstatus/updstatus directly!  Use get_mxact_status_for_lock
+ * instead.
+ */
+static const struct
+{
+	LOCKMODE	hwlock;
+	int			lockstatus;
+	int			updstatus;
+}
+
+			tupleLockExtraInfo[MaxLockTupleMode + 1] =
+{
+	{							/* LockTupleKeyShare */
+		AccessShareLock,
+		MultiXactStatusForKeyShare,
+		-1						/* KeyShare does not allow updating tuples */
+	},
+	{							/* LockTupleShare */
+		RowShareLock,
+		MultiXactStatusForShare,
+		-1						/* Share does not allow updating tuples */
+	},
+	{							/* LockTupleNoKeyExclusive */
+		ExclusiveLock,
+		MultiXactStatusForNoKeyUpdate,
+		MultiXactStatusNoKeyUpdate
+	},
+	{							/* LockTupleExclusive */
+		AccessExclusiveLock,
+		MultiXactStatusForUpdate,
+		MultiXactStatusUpdate
+	}
+};
+
+/*
+ * This table maps tuple lock strength values for each particular
+ * MultiXactStatus value.
+ */
+static const int MultiXactStatusLock[MaxMultiXactStatus + 1] =
+{
+	LockTupleKeyShare,			/* ForKeyShare */
+	LockTupleShare,				/* ForShare */
+	LockTupleNoKeyExclusive,	/* ForNoKeyUpdate */
+	LockTupleExclusive,			/* ForUpdate */
+	LockTupleNoKeyExclusive,	/* NoKeyUpdate */
+	LockTupleExclusive			/* Update */
+};
+
+/* Get the LockTupleMode for a given MultiXactStatus */
+#define TUPLOCK_from_mxstatus(status) \
+			(MultiXactStatusLock[(status)])
+
+/*
+ * Acquire heavyweight locks on tuples, using a LockTupleMode strength value.
+ * This is more readable than having every caller translate it to lock.h's
+ * LOCKMODE.
+ */
+#define LockTupleTuplock(rel, tup, mode) \
+	LockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
+#define UnlockTupleTuplock(rel, tup, mode) \
+	UnlockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
+#define ConditionalLockTupleTuplock(rel, tup, mode) \
+	ConditionalLockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
+/* Get the LOCKMODE for a given MultiXactStatus */
+#define LOCKMODE_from_mxstatus(status) \
+			(tupleLockExtraInfo[TUPLOCK_from_mxstatus((status))].hwlock)
+extern HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
+					TransactionId xid, CommandId cid, int options);
+
+extern HTSU_Result heap_delete(Relation relation, ItemPointer tid,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			HeapUpdateFailureData *hufd);
+extern HTSU_Result heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			HeapUpdateFailureData *hufd, LockTupleMode *lockmode);
+
+extern XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
+extern uint8 compute_infobits(uint16 infomask, uint16 infomask2);
+extern void compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
+						  uint16 old_infomask2, TransactionId add_to_xmax,
+						  LockTupleMode mode, bool is_update,
+						  TransactionId *result_xmax, uint16 *result_infomask,
+						  uint16 *result_infomask2);
+extern void UpdateXmaxHintBits(HeapTupleHeader tuple, Buffer buffer, TransactionId xid);
+extern bool DoesMultiXactIdConflict(MultiXactId multi, uint16 infomask,
+						LockTupleMode lockmode);
+extern bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
+						   uint16 infomask, Relation rel, int *remaining);
+
+extern void MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
+				Relation rel, ItemPointer ctid, XLTW_Oper oper,
+				int *remaining);
+extern MultiXactStatus get_mxact_status_for_lock(LockTupleMode mode, bool is_update);
+
+extern void heap_inplace_update(Relation relation, HeapTuple tuple);
+extern bool heap_acquire_tuplock(Relation relation, ItemPointer tid,
+					 LockTupleMode mode, LockWaitPolicy wait_policy,
+					 bool *have_tuple_lock);
 
 /* in heap/heapam_common.c */
 extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
@@ -43,6 +148,28 @@ extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin);
 typedef struct StorageSlotAmRoutine StorageSlotAmRoutine;
 extern StorageSlotAmRoutine* heapam_storage_slot_handler(void);
 
+
+/*
+ * Given two versions of the same t_infomask for a tuple, compare them and
+ * return whether the relevant status for a tuple Xmax has changed.  This is
+ * used after a buffer lock has been released and reacquired: we want to ensure
+ * that the tuple state continues to be the same it was when we previously
+ * examined it.
+ *
+ * Note the Xmax field itself must be compared separately.
+ */
+static inline bool
+xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask)
+{
+	const uint16 interesting =
+	HEAP_XMAX_IS_MULTI | HEAP_XMAX_LOCK_ONLY | HEAP_LOCK_MASK;
+
+	if ((new_infomask & interesting) != (old_infomask & interesting))
+		return true;
+
+	return false;
+}
+
 /*
  * SetHintBits()
  *
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index 9539d67..16dfb3e 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -811,6 +811,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/storageam.h b/src/include/access/storageam.h
new file mode 100644
index 0000000..9502c92
--- /dev/null
+++ b/src/include/access/storageam.h
@@ -0,0 +1,81 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "executor/tuptable.h"
+
+/* A physical tuple coming from a storage AM scan */
+typedef void *StorageTuple;
+
+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;
+
+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);
+
+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,
+				   CommandId cid, Snapshot crosscheck, bool wait,
+				   HeapUpdateFailureData *hufd, LockTupleMode *lockmode);
+
+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 void storage_finish_speculative(Relation relation, TupleTableSlot *slot);
+
+extern tuple_data storage_tuple_get_data(Relation relation, StorageTuple tuple, tuple_data_flags flags);
+
+extern bool storage_tuple_is_heaponly(Relation relation, StorageTuple tuple);
+
+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 95fe028..c2e6dc2 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -13,32 +13,13 @@
 
 #include "access/htup.h"
 #include "access/heapam.h"
+#include "access/storageam.h"
 #include "access/sdir.h"
 #include "access/skey.h"
 #include "executor/tuptable.h"
 #include "utils/relcache.h"
 #include "utils/snapshot.h"
 
-/* A physical tuple coming from a storage AM scan */
-typedef void *StorageTuple;
-
-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;
-
-
 typedef HeapScanDesc (*scan_begin_hook) (Relation relation,
 										Snapshot snapshot,
 										int nkeys, ScanKey key,
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index aeb363f..0da9ac9 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -189,7 +189,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 7708818..8704b7b 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -196,7 +196,7 @@ 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,
+extern StorageTuple EvalPlanQualFetch(EState *estate, Relation relation,
 				  int lockmode, LockWaitPolicy wait_policy, ItemPointer tid,
 				  TransactionId priorXmax);
 extern void EvalPlanQualInit(EPQState *epqstate, EState *estate,
@@ -204,8 +204,8 @@ extern void EvalPlanQualInit(EPQState *epqstate, EState *estate,
 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);
 extern void ExecSetupPartitionTupleRouting(Relation rel,
 							   Index resultRTindex,
 							   EState *estate,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 90a60ab..1ed1864 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -503,7 +503,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 */
 
@@ -2021,7 +2021,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.7.4.windows.1

#78Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Haribabu Kommi (#77)
Re: Pluggable storage

On Sat, Sep 9, 2017 at 1:23 PM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:

I rebased the patch to the latest master and also fixed the duplicate OID
and some slot fixes. Updated patches are attached.

While analyzing the removal of HeapScanDesc usage other than heap
modules, The mostly used member is "*rs_cbuf*" for the purpose of locking
the buffer, visibility checks and marking it as dirty. The buffer is
tightly integrated
with the visibility. Buffer may not be required for some storage routines
where
the data is always in the memory and etc.

I found the references of its structure members in the following
places. I feel other than the following 4 parameters, rest of them needs to
be
moved into their corresponding storage routines.

* 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 */*

But currently I am treating the "*rs_cbuf" *also a needed member and also
expecting all storage routines will be provide it. Or we may need a another
approach to mark the buffer as dirty.

Suggestions?

Following are the rest of the parameters that are used
outside the heap.

* BlockNumber rs_nblocks; /* total number of blocks in rel */*

pgstattuple.c, tsm_system_rows.c, tsm_system_time.c, system.c
nodeBitmapheapscan.c nodesamplescan.c,
*Mostly for the purpose of checking the number of blocks in a rel.*

* BufferAccessStrategy rs_strategy; /* access strategy for reads */*

pgstattuple.c

* bool rs_pageatatime; /* verify visibility page-at-a-time? */*
* BlockNumber rs_startblock; /* block # to start at */*
* bool rs_syncscan; /* report location to syncscan logic? */*
* bool rs_inited; /* false = scan not init'd yet */*

nodesamplescan.c

* HeapTupleData rs_ctup; /* current tuple in scan, if any */*

*genam.c, nodeBitmapHeapscan.c, nodesamplescan.c*
*Used for retrieving the last scanned tuple.*

* BlockNumber rs_cblock; /* current block # in scan, if any */*

*index.c, nodesamplescan.c*

* Buffer rs_cbuf; /* current buffer in scan, if any */*

*pgrowlocks.c, pgstattuple.c, genam.c, index.c, cluster.c,*
*tablecmds.c, nodeBitmapHeapscan.c, nodesamplescan.c*
*Mostly used for Locking the Buffer.*

* ParallelHeapScanDesc rs_parallel; /* parallel scan information */*

*nodeseqscan.c*

* int rs_cindex; /* current tuple's index in vistuples */*

*nodeBitmapHeapScan.c*

* int rs_ntuples; /* number of visible tuples on page */*
* OffsetNumber rs_vistuples[MaxHeapTuplesPerPage]; /* their offsets */*

*tsm_system_rows.c, nodeBitmapHeapscan.c, nodesamplescan.c*
*Used for retrieve the offsets mainly Bitmap and sample scans.*

I think rest of the above parameters usage other than heap can be changed
once the Bitmap and Sample scans are modified to use the storage routines
while returning the tuple instead of their own implementations. I feel these
scans are the major users of the rest of the parameters. This approach may
need to some more API's to get rid of Bitmap and sample scan's own
implementation.

suggestions?

Regards,
Hari Babu
Fujitsu Australia

#79Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Haribabu Kommi (#78)
8 attachment(s)
Re: Pluggable storage

On Tue, Sep 12, 2017 at 3:52 PM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:

* int rs_ntuples; /* number of visible tuples on page */*
* OffsetNumber rs_vistuples[MaxHeapTuplesPerPage]; /* their offsets */*

*tsm_system_rows.c, nodeBitmapHeapscan.c, nodesamplescan.c*
*Used for retrieve the offsets mainly Bitmap and sample scans.*

I think rest of the above parameters usage other than heap can be changed
once the Bitmap and Sample scans are modified to use the storage routines
while returning the tuple instead of their own implementations. I feel
these
scans are the major users of the rest of the parameters. This approach may
need to some more API's to get rid of Bitmap and sample scan's own
implementation.

Instead of modifying the Bitmap Heap and Sample scan's to avoid referring
the internal members of the HeapScanDesc, I divided the HeapScanDesc
into two parts.

1. StorageScanDesc
2. HeapPageScanDesc

The StorageScanDesc contains the minimal information that is required
outside
the Storage routine and this must be provided by all storage routines. This
structure contains minimal information such as relation, snapshot, buffer
and
etc.

The HeapPageScanDesc contains other extra information that is required for
Bitmap Heap and Sample scans to work. This structure contains the
information
of blocks, visible offsets and etc. Currently this structure is used only
in
Bitmap Heap and Sample scan and it's supported contrib modules, except
the pgstattuple module. The pgstattuple needs some additional changes.

By adding additional storage API to return HeapPageScanDesc as it required
by the Bitmap Heap and Sample scan's and this API is called only in these
two scan's. And also these scan methods are choosen by the planner only
when the storage routine supports to returning of HeapPageScanDesc API.
Currently Implemented the planner support only for Bitmap, yet to do it
for Sample scan.

With the above approach, I removed all the references of HeapScanDesc
outside the heap. The changes of this approach is available in the
0008-Remove-HeapScanDesc-usage-outside-heap.patch

Suggestions/comments with the above approach.

Because of a recent commit in the master, there was an OID conflict with
the other patches. Rebased patches are attached.

Regards,
Hari Babu
Fujitsu Australia

Attachments:

0008-Remove-HeapScanDesc-usage-outside-heap.patchapplication/octet-stream; name=0008-Remove-HeapScanDesc-usage-outside-heap.patchDownload
From cd44aebb658950103342d5a09d62a29db90326ff Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Thu, 14 Sep 2017 12:34:30 +1000
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           |  37 +--
 src/backend/access/heap/heapam_storage.c   | 417 ++++++++++++++++-------------
 src/backend/access/heap/storageam.c        |  51 +++-
 src/backend/access/index/genam.c           |   4 +-
 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  |  67 +++--
 src/backend/executor/nodeSamplescan.c      |  51 ++--
 src/backend/executor/nodeSeqscan.c         |   5 +-
 src/backend/optimizer/util/plancat.c       |   3 +-
 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                |   4 +-
 src/include/access/relscan.h               |  47 ++--
 src/include/access/storageam.h             |  32 +--
 src/include/access/storageamapi.h          |  23 +-
 src/include/nodes/execnodes.h              |   5 +-
 39 files changed, 488 insertions(+), 380 deletions(-)

diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index 063e079..55d2e09 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 eb33b26..6d9a07a 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 544458e..1412029 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 af8d025..aa72522 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 b64fec8..9b5f24a 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -107,8 +107,9 @@ static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_mo
  * 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;
@@ -118,13 +119,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;
 	}
 
 	/*
@@ -135,20 +136,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
@@ -158,7 +159,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;
 
@@ -193,7 +194,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);
@@ -201,20 +202,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;
 }
 
 
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 12a8f56..a678402 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -1711,7 +1711,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;
 		}
 	}
@@ -1753,10 +1753,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.
@@ -1765,12 +1765,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;
@@ -1799,9 +1799,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
@@ -1815,8 +1815,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;
@@ -1827,20 +1827,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)
 	{
@@ -1849,25 +1849,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 */
 
@@ -1875,7 +1875,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
@@ -1883,7 +1883,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);
 }
 
 
@@ -1917,7 +1917,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;
@@ -1932,14 +1932,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;
 			}
@@ -1952,29 +1952,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 */
 
@@ -1985,14 +1985,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;
 			}
@@ -2003,30 +2003,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
 		{
@@ -2042,20 +2042,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));
@@ -2086,21 +2086,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;
 				}
 			}
@@ -2125,17 +2125,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)
@@ -2146,10 +2146,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
@@ -2163,8 +2163,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);
 		}
 
 		/*
@@ -2172,21 +2172,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)
@@ -2237,14 +2237,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;
 			}
@@ -2257,27 +2257,27 @@ 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;
 		}
 
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
+		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;
@@ -2287,14 +2287,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;
 			}
@@ -2305,32 +2305,32 @@ 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 */
 		}
 
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
+		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 */
 
@@ -2341,20 +2341,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));
@@ -2363,8 +2363,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;
 	}
@@ -2377,7 +2377,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));
 
@@ -2392,17 +2392,17 @@ 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;
+					scan->rs_pagescan.rs_cindex = lineindex;
 					return;
 				}
 			}
 			else
 			{
-				scan->rs_cindex = lineindex;
+				scan->rs_pagescan.rs_cindex = lineindex;
 				return;
 			}
 
@@ -2422,10 +2422,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)
@@ -2436,10 +2436,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
@@ -2453,8 +2453,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);
 		}
 
 		/*
@@ -2462,20 +2462,20 @@ 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);
 
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
+		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;
@@ -2485,7 +2485,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 }
 
 
-static HeapScanDesc
+static StorageScanDesc
 heapam_beginscan(Relation relation, Snapshot snapshot,
 				int nkeys, ScanKey key,
 				ParallelHeapScanDesc parallel_scan,
@@ -2512,12 +2512,12 @@ heapam_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;
@@ -2526,7 +2526,7 @@ heapam_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
@@ -2550,13 +2550,29 @@ heapam_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;
+}
+
+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;
 }
 
 /* ----------------
@@ -2564,21 +2580,23 @@ heapam_beginscan(Relation relation, Snapshot snapshot,
  * ----------------
  */
 static void
-heapam_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
+heapam_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
@@ -2609,29 +2627,31 @@ heapam_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
  * ----------------
  */
 static void
-heapam_endscan(HeapScanDesc scan)
+heapam_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);
 }
@@ -2643,12 +2663,14 @@ heapam_endscan(HeapScanDesc scan)
  * ----------------
  */
 static void
-heapam_scan_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
+heapam_scan_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;
 }
 
@@ -2677,7 +2699,7 @@ heapam_scan_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
 
 
 static StorageTuple
-heapam_getnext(HeapScanDesc sscan, ScanDirection direction)
+heapam_getnext(StorageScanDesc sscan, ScanDirection direction)
 {
 	HeapScanDesc scan = (HeapScanDesc) sscan;
 
@@ -2685,11 +2707,11 @@ heapam_getnext(HeapScanDesc sscan, ScanDirection direction)
 
 	HEAPAMDEBUG_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)
 	{
@@ -2703,7 +2725,7 @@ heapam_getnext(HeapScanDesc sscan, ScanDirection direction)
 	 */
 	HEAPAMDEBUG_3;				/* heap_getnext returning tuple */
 
-	pgstat_count_heap_getnext(scan->rs_rd);
+	pgstat_count_heap_getnext(scan->rs_scan.rs_rd);
 
 	return &(scan->rs_ctup);
 }
@@ -2723,7 +2745,7 @@ heapam_getnext(HeapScanDesc sscan, ScanDirection direction)
 #endif
 
 static TupleTableSlot *
-heapam_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
+heapam_getnextslot(StorageScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
 {
 	HeapScanDesc scan = (HeapScanDesc) sscan;
 
@@ -2731,11 +2753,11 @@ heapam_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *
 
 	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)
 	{
@@ -2750,11 +2772,32 @@ heapam_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *
 	 */
 	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);
 }
 
+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);
+}
+
 /*
  *	heap_fetch		- retrieve tuple with given tid
  *
@@ -3069,18 +3112,18 @@ heapam_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
  * numBlks is number of pages to scan (InvalidBlockNumber means "all")
  */
 static void
-heapam_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
+heapam_setscanlimits(StorageScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
 {
 	HeapScanDesc scan = (HeapScanDesc) sscan;
 
-	Assert(!scan->rs_inited);	/* else too late to change */
-	Assert(!scan->rs_syncscan); /* else rs_startblock is significant */
+	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;
 }
 
 
@@ -5086,12 +5129,26 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 	amroutine->scansetlimits = heapam_setscanlimits;
 	amroutine->scan_getnext = heapam_getnext;
 	amroutine->scan_getnextslot = heapam_getnextslot;
+	amroutine->scan_fetch_tuple_from_offset = heapam_fetch_tuple_from_offset;
 	amroutine->scan_end = heapam_endscan;
 	amroutine->scan_rescan = heapam_rescan;
 	amroutine->scan_update_snapshot = heapam_scan_update_snapshot;
 	amroutine->tuple_freeze = heapam_freeze_tuple;
 	amroutine->hot_search_buffer = heapam_hot_search_buffer;
 
+	/*
+	 * 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;
     amroutine->tuple_delete = heapam_heap_delete;
diff --git a/src/backend/access/heap/storageam.c b/src/backend/access/heap/storageam.c
index 76b94dc..af59d46 100644
--- a/src/backend/access/heap/storageam.c
+++ b/src/backend/access/heap/storageam.c
@@ -54,7 +54,7 @@
  *		Caller must hold a suitable lock on the correct relation.
  * ----------------
  */
-HeapScanDesc
+StorageScanDesc
 storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan)
 {
 	Snapshot	snapshot;
@@ -67,6 +67,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
  *
@@ -74,7 +93,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);
 }
@@ -104,7 +123,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)
 {
@@ -112,7 +131,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);
@@ -122,7 +141,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)
@@ -132,7 +151,7 @@ storage_beginscan_strat(Relation relation, Snapshot snapshot,
 								   false, false, false);
 }
 
-HeapScanDesc
+StorageScanDesc
 storage_beginscan_bm(Relation relation, Snapshot snapshot,
 				  int nkeys, ScanKey key)
 {
@@ -140,7 +159,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)
@@ -155,7 +174,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);
@@ -171,7 +190,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,
@@ -186,7 +205,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);
 }
@@ -199,23 +218,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);
+}
+
 /*
  *	storage_fetch		- retrieve tuple with given tid
  *
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index db5c93b..bcdef50 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/tablesample/system.c b/src/backend/access/tablesample/system.c
index e270cbc..8793b95 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 975cd5b..17c1e1f 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -564,7 +564,7 @@ boot_openrel(char *relname)
 	int			i;
 	struct typmap **app;
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tup;
 
 	if (strlen(relname) >= NAMEDATALEN)
@@ -880,7 +880,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 d2a8a06..281e3a8 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -789,7 +789,7 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 				{
 					ScanKeyData key[1];
 					Relation	rel;
-					HeapScanDesc scan;
+					StorageScanDesc scan;
 					HeapTuple	tuple;
 
 					ScanKeyInit(&key[0],
@@ -830,7 +830,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 68c46f7..2564b14 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1897,7 +1897,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],
@@ -2210,7 +2210,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];
@@ -2649,7 +2649,7 @@ IndexCheckExclusion(Relation heapRelation,
 					Relation indexRelation,
 					IndexInfo *indexInfo)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
@@ -2963,7 +2963,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 b8137f2..1a766ac 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -945,7 +945,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 1d048e6..842abcc 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 517e310..63324cf 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 145e3c1..5e3915b 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 a51f2e4..050dfa3 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 e0f6973..ccdbe70 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 00e71e3..98c09d3 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2022,7 +2022,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 39850b1..0913577 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 46bc3da..f8cbfcc 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1875,7 +1875,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 6b6beb9..0327030 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4481,7 +4481,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;
@@ -5053,7 +5053,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;
 
@@ -8202,7 +8202,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	Expr	   *origexpr;
 	ExprState  *exprstate;
 	TupleDesc	tupdesc;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	ExprContext *econtext;
 	MemoryContext oldcxt;
@@ -8285,7 +8285,7 @@ validateForeignKeyConstraint(char *conname,
 							 Oid pkindOid,
 							 Oid constraintOid)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	Trigger		trig;
 	Snapshot	snapshot;
@@ -10737,7 +10737,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 cdfa8ff..be3c0db 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 c07f508..09b5b9f 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2310,7 +2310,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;
 
@@ -2706,7 +2706,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 e24ac9f..20f3a6c 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -443,7 +443,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
 		 * pg_class
 		 */
 		Relation	pgclass;
-		HeapScanDesc scan;
+		StorageScanDesc scan;
 		HeapTuple	tuple;
 
 		pgclass = heap_open(RelationRelationId, AccessShareLock);
@@ -1091,7 +1091,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 7921025..208ff91 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -54,14 +54,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);
 
@@ -76,7 +76,8 @@ static TupleTableSlot *
 BitmapHeapNext(BitmapHeapScanState *node)
 {
 	ExprContext *econtext;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
+	HeapPageScanDesc pagescan;
 	TIDBitmap  *tbm;
 	TBMIterator *tbmiterator = NULL;
 	TBMSharedIterator *shared_tbmiterator = NULL;
@@ -92,6 +93,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;
@@ -191,8 +193,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 
 	for (;;)
 	{
-		Page		dp;
-		ItemId		lp;
+		StorageTuple tuple;
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -219,7 +220,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;
@@ -228,7 +229,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			/*
 			 * Fetch the current heap page and identify candidate tuples.
 			 */
-			bitgetpage(scan, tbmres);
+			bitgetpage(node, tbmres);
 
 			if (tbmres->ntuples >= 0)
 				node->exact_pages++;
@@ -238,7 +239,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);
@@ -248,7 +249,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			/*
 			 * Continuing in previously obtained page; advance rs_cindex
 			 */
-			scan->rs_cindex++;
+			pagescan->rs_cindex++;
 
 #ifdef USE_PREFETCH
 
@@ -275,7 +276,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;
@@ -293,23 +294,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);
@@ -350,8 +342,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;
@@ -360,7 +354,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,
@@ -403,7 +397,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
@@ -419,23 +413,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);
 		}
 	}
@@ -443,7 +434,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 
 	Assert(ntup <= MaxHeapTuplesPerPage);
-	scan->rs_ntuples = ntup;
+	pagescan->rs_ntuples = ntup;
 }
 
 /*
@@ -569,7 +560,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;
@@ -724,7 +715,7 @@ void
 ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 {
 	Relation	relation;
-	HeapScanDesc scanDesc;
+	StorageScanDesc scanDesc;
 
 	/*
 	 * extract information from the node
@@ -871,6 +862,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 04f85e5..589505a 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -31,7 +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
@@ -66,8 +66,7 @@ SampleNext(SampleScanState *node)
 	if (tuple)
 		ExecStoreTuple(tuple,	/* tuple to store */
 					   slot,	/* slot to store in */
-					   //harinode->ss.ss_currentScanDesc->rs_cbuf,	/* tuple's buffer */
-					   InvalidBuffer,
+					   node->ss.ss_currentScanDesc->rs_cbuf,	/* tuple's buffer */
 					   false);	/* don't pfree this pointer */
 	else
 		ExecClearTuple(slot);
@@ -356,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
 	{
@@ -381,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;
@@ -395,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)
@@ -406,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;
 	}
@@ -455,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)
@@ -493,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;
 
 			/*
@@ -513,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);
 		}
 
 		/*
@@ -528,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 */
@@ -548,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,
@@ -569,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 839d3a6..ec67024 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -316,9 +316,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 a1ebd4a..4503593 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -248,7 +248,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 fec203d..9f505a5 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1864,7 +1864,7 @@ get_database_list(void)
 {
 	List	   *dblist = NIL;
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tup;
 	MemoryContext resultcxt;
 
@@ -1930,7 +1930,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 74113a7..86db35d 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 fca56d4..0e840c5 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 4924dac..7122987 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -420,7 +420,7 @@ DefineQueryRewrite(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 82c042a..48b6924 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -1204,7 +1204,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 4cddd73..42c2686 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -97,6 +97,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;
 
@@ -106,7 +108,7 @@ typedef struct ParallelHeapScanDescData *ParallelHeapScanDesc;
  */
 #define HeapScanIsValid(scan) PointerIsValid(scan)
 
-extern void heapgetpage(HeapScanDesc scan, BlockNumber page);
+extern void heapgetpage(StorageScanDesc sscan, BlockNumber page);
 
 extern Size heap_parallelscan_estimate(Snapshot snapshot);
 extern void heap_parallelscan_initialize(ParallelHeapScanDesc target,
diff --git a/src/include/access/relscan.h b/src/include/access/relscan.h
index 147f862..8e00c84 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/storageam.h b/src/include/access/storageam.h
index 507af71..f335c79 100644
--- a/src/include/access/storageam.h
+++ b/src/include/access/storageam.h
@@ -19,7 +19,7 @@
 
 /* A physical tuple coming from a storage AM scan */
 typedef void *StorageTuple;
-typedef void *StorageScanDesc;
+typedef struct StorageScanDescData *StorageScanDesc;
 
 typedef union tuple_data
 {
@@ -37,29 +37,31 @@ typedef enum tuple_data_flags
 	CTID
 } tuple_data_flags;
 
-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,
+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 HeapScanDesc storage_beginscan_catalog(Relation relation, int nkeys, ScanKey key);
-extern HeapScanDesc storage_beginscan_strat(Relation relation, Snapshot snapshot,
+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 HeapScanDesc storage_beginscan_bm(Relation relation, Snapshot snapshot,
+extern StorageScanDesc storage_beginscan_bm(Relation relation, Snapshot snapshot,
 				  int nkeys, ScanKey key);
-extern HeapScanDesc storage_beginscan_sampling(Relation relation, Snapshot snapshot,
+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(HeapScanDesc scan);
-extern void storage_rescan(HeapScanDesc scan, ScanKey key);
-extern void storage_rescan_set_params(HeapScanDesc scan, ScanKey key,
+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 c2e6dc2..d849b86 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -20,7 +20,7 @@
 #include "utils/relcache.h"
 #include "utils/snapshot.h"
 
-typedef HeapScanDesc (*scan_begin_hook) (Relation relation,
+typedef StorageScanDesc (*scan_begin_hook) (Relation relation,
 										Snapshot snapshot,
 										int nkeys, ScanKey key,
 										ParallelHeapScanDesc parallel_scan,
@@ -30,22 +30,26 @@ typedef HeapScanDesc (*scan_begin_hook) (Relation relation,
 										bool is_bitmapscan,
 										bool is_samplescan,
 										bool temp_snap);
-typedef void (*scan_setlimits_hook) (HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
+typedef ParallelHeapScanDesc (*scan_get_parallelheapscandesc_hook) (StorageScanDesc scan);
+typedef HeapPageScanDesc (*scan_get_heappagescandesc_hook) (StorageScanDesc scan);
+typedef void (*scan_setlimits_hook) (StorageScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
 
 /* must return a TupleTableSlot? */
-typedef StorageTuple (*scan_getnext_hook) (HeapScanDesc scan,
+typedef StorageTuple (*scan_getnext_hook) (StorageScanDesc scan,
 											ScanDirection direction);
 
-typedef TupleTableSlot* (*scan_getnext_slot_hook) (HeapScanDesc scan,
+typedef TupleTableSlot* (*scan_getnext_slot_hook) (StorageScanDesc scan,
 										ScanDirection direction, TupleTableSlot *slot);
+typedef StorageTuple (*scan_fetch_tuple_from_offset_hook) (StorageScanDesc scan,
+							BlockNumber blkno, OffsetNumber offset);
 
-typedef void (*scan_end_hook) (HeapScanDesc scan);
+typedef void (*scan_end_hook) (StorageScanDesc scan);
 
 
-typedef void (*scan_getpage_hook) (HeapScanDesc scan, BlockNumber page);
-typedef void (*scan_rescan_hook) (HeapScanDesc scan, ScanKey key, bool set_params,
+typedef void (*scan_getpage_hook) (StorageScanDesc scan, BlockNumber page);
+typedef void (*scan_rescan_hook) (StorageScanDesc scan, ScanKey key, bool set_params,
 					bool allow_strat, bool allow_sync, bool allow_pagemode);
-typedef void (*scan_update_snapshot_hook) (HeapScanDesc scan, Snapshot snapshot);
+typedef void (*scan_update_snapshot_hook) (StorageScanDesc scan, Snapshot snapshot);
 
 typedef bool (*hot_search_buffer_hook) (ItemPointer tid, Relation relation,
 					   Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
@@ -161,9 +165,12 @@ typedef struct StorageAmRoutine
 
 	/* Operations on relation scans */
 	scan_begin_hook scan_begin;
+	scan_get_parallelheapscandesc_hook scan_get_parallelheapscandesc;
+	scan_get_heappagescandesc_hook scan_get_heappagescandesc;
 	scan_setlimits_hook scansetlimits;
 	scan_getnext_hook scan_getnext;
 	scan_getnext_slot_hook scan_getnextslot;
+	scan_fetch_tuple_from_offset_hook scan_fetch_tuple_from_offset;
 	scan_end_hook scan_end;
 	scan_getpage_hook scan_getpage;
 	scan_rescan_hook scan_rescan;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 1ed1864..1037f12 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/storageam.h"
 #include "access/tupconvert.h"
 #include "executor/instrument.h"
 #include "lib/pairingheap.h"
@@ -1097,7 +1098,7 @@ typedef struct ScanState
 {
 	PlanState	ps;				/* its first field is NodeTag */
 	Relation	ss_currentRelation;
-	HeapScanDesc ss_currentScanDesc;
+	StorageScanDesc ss_currentScanDesc;
 	TupleTableSlot *ss_ScanTupleSlot;
 } ScanState;
 
@@ -1118,6 +1119,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 */
@@ -1342,6 +1344,7 @@ typedef struct ParallelBitmapHeapState
 typedef struct BitmapHeapScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
+	HeapPageScanDesc pagescan;
 	ExprState  *bitmapqualorig;
 	TIDBitmap  *tbm;
 	TBMIterator *tbmiterator;
-- 
2.7.4.windows.1

0001-Change-Create-Access-method-to-include-storage-handl.patchapplication/octet-stream; name=0001-Change-Create-Access-method-to-include-storage-handl.patchDownload
From 7c4bcfa472fd96c5c8a39309fc1394e94831e395 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
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              |  4 ++++
 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, 63 insertions(+), 11 deletions(-)

diff --git a/src/backend/commands/amcmds.c b/src/backend/commands/amcmds.c
index 7e0a9aa..33079c1 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 c303818..d4ac340 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
@@ -5172,16 +5174,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 be79353..0a7e0a3 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 e021f5b..2c3e33c 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
@@ -83,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 f73c6c6..6b675b0 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3879,6 +3879,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 ffdb452..ea352fa 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 fcf8bd7..3bc9607 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1711,11 +1711,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 2945966..f1f58a3 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1153,15 +1153,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.7.4.windows.1

0002-Storage-AM-API-hooks-and-related-functions.patchapplication/octet-stream; name=0002-Storage-AM-API-hooks-and-related-functions.patchDownload
From bef98460128bdaed155017b54f1b89cde2a16c2b Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Wed, 30 Aug 2017 12:41:15 +1000
Subject: [PATCH 2/8] Storage AM API hooks and related functions

---
 src/backend/access/heap/Makefile         |   3 +-
 src/backend/access/heap/heapam_storage.c |  58 ++++++++
 src/backend/access/heap/storageamapi.c   | 103 ++++++++++++++
 src/include/access/htup.h                |  21 +++
 src/include/access/storageamapi.h        | 237 +++++++++++++++++++++++++++++++
 src/include/catalog/pg_proc.h            |   5 +
 src/include/nodes/nodes.h                |   1 +
 src/include/utils/tqual.h                |   9 --
 8 files changed, 427 insertions(+), 10 deletions(-)
 create mode 100644 src/backend/access/heap/heapam_storage.c
 create mode 100644 src/backend/access/heap/storageamapi.c
 create mode 100644 src/include/access/storageamapi.h

diff --git a/src/backend/access/heap/Makefile b/src/backend/access/heap/Makefile
index b83d496..02a3909 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 storageamapi.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 0000000..88827e7
--- /dev/null
+++ b/src/backend/access/heap/heapam_storage.c
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/heapam.h"
+#include "access/heapam_xlog.h"
+#include "access/hio.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/relscan.h"
+#include "access/storageamapi.h"
+#include "access/subtrans.h"
+#include "access/tuptoaster.h"
+#include "access/valid.h"
+#include "access/visibilitymap.h"
+#include "access/xloginsert.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/bufmgr.h"
+#include "storage/lmgr.h"
+#include "storage/predicate.h"
+#include "storage/procarray.h"
+#include "storage/smgr.h"
+#include "storage/spin.h"
+#include "utils/builtins.h"
+#include "utils/inval.h"
+#include "utils/rel.h"
+#include "utils/tqual.h"
+
+
+Datum
+heapam_storage_handler(PG_FUNCTION_ARGS)
+{
+	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
+
+
+	PG_RETURN_POINTER(amroutine);
+}
diff --git a/src/backend/access/heap/storageamapi.c b/src/backend/access/heap/storageamapi.c
new file mode 100644
index 0000000..def2029
--- /dev/null
+++ b/src/backend/access/heap/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/htup.h b/src/include/access/htup.h
index 61b3e68..6459435 100644
--- a/src/include/access/htup.h
+++ b/src/include/access/htup.h
@@ -26,6 +26,27 @@ typedef struct MinimalTupleData MinimalTupleData;
 
 typedef MinimalTupleData *MinimalTuple;
 
+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 */
+
+	END_OF_VISIBILITY
+} tuple_visibility_type;
+
+/* 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;
 
 /*
  * HeapTupleData is an in-memory data structure that points to a tuple.
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
new file mode 100644
index 0000000..95fe028
--- /dev/null
+++ b/src/include/access/storageamapi.h
@@ -0,0 +1,237 @@
+/*---------------------------------------------------------------------
+ *
+ * storageamapi.h
+ *		API for Postgres storage access methods
+ *
+ * Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * src/include/access/storageamapi.h
+ *---------------------------------------------------------------------
+ */
+#ifndef STORAGEAMAPI_H
+#define STORAGEAMAPI_H
+
+#include "access/htup.h"
+#include "access/heapam.h"
+#include "access/sdir.h"
+#include "access/skey.h"
+#include "executor/tuptable.h"
+#include "utils/relcache.h"
+#include "utils/snapshot.h"
+
+/* A physical tuple coming from a storage AM scan */
+typedef void *StorageTuple;
+
+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;
+
+
+typedef HeapScanDesc (*scan_begin_hook) (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 (*scan_setlimits_hook) (HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
+
+/* must return a TupleTableSlot? */
+typedef StorageTuple (*scan_getnext_hook) (HeapScanDesc scan,
+											ScanDirection direction);
+
+typedef TupleTableSlot* (*scan_getnext_slot_hook) (HeapScanDesc scan,
+										ScanDirection direction, TupleTableSlot *slot);
+
+typedef void (*scan_end_hook) (HeapScanDesc scan);
+
+
+typedef void (*scan_getpage_hook) (HeapScanDesc scan, BlockNumber page);
+typedef void (*scan_rescan_hook) (HeapScanDesc scan, ScanKey key, bool set_params,
+					bool allow_strat, bool allow_sync, bool allow_pagemode);
+typedef void (*scan_update_snapshot_hook) (HeapScanDesc scan, Snapshot snapshot);
+
+typedef bool (*hot_search_buffer_hook) (ItemPointer tid, Relation relation,
+					   Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
+					   bool *all_dead, bool first_call);
+
+typedef Oid (*tuple_insert_hook) (Relation relation,
+									  TupleTableSlot *tupslot,
+									  CommandId cid,
+									  int options,
+									  BulkInsertState bistate);
+
+typedef HTSU_Result (*tuple_delete_hook) (Relation relation,
+											  ItemPointer tid,
+											  CommandId cid,
+											  Snapshot crosscheck,
+											  bool wait,
+											  HeapUpdateFailureData *hufd);
+
+typedef HTSU_Result (*tuple_update_hook) (Relation relation,
+											  ItemPointer otid,
+											  TupleTableSlot *slot,
+											  CommandId cid,
+											  Snapshot crosscheck,
+											  bool wait,
+											  HeapUpdateFailureData *hufd,
+											  LockTupleMode *lockmode);
+
+typedef bool (*tuple_fetch_hook) (Relation relation,
+									ItemPointer tid,
+									Snapshot snapshot,
+									StorageTuple *tuple,
+									Buffer *userbuf,
+									bool keep_buf,
+									Relation stats_relation);
+
+typedef HTSU_Result (*tuple_lock_hook) (Relation relation,
+											ItemPointer tid,
+											StorageTuple *tuple,
+											CommandId cid,
+											LockTupleMode mode,
+											LockWaitPolicy wait_policy,
+											bool follow_update,
+											Buffer *buffer,
+											HeapUpdateFailureData *hufd);
+
+typedef void (*multi_insert_hook) (Relation relation, HeapTuple *tuples, int ntuples,
+				  CommandId cid, int options, BulkInsertState bistate);
+
+typedef bool (*tuple_freeze_hook) (HeapTupleHeader tuple, TransactionId cutoff_xid,
+				  TransactionId cutoff_multi);
+
+typedef void (*tuple_get_latest_tid_hook) (Relation relation,
+											Snapshot snapshot,
+											ItemPointer tid);
+
+typedef tuple_data (*get_tuple_data_hook) (StorageTuple tuple, tuple_data_flags flags);
+
+typedef StorageTuple (*tuple_from_datum_hook) (Datum data, Oid tableoid);
+
+typedef bool (*tuple_is_heaponly_hook) (StorageTuple tuple);
+
+typedef void (*slot_store_tuple_hook) (TupleTableSlot *slot,
+										   StorageTuple tuple,
+										   bool shouldFree,
+										   bool minumumtuple);
+typedef void (*slot_clear_tuple_hook) (TupleTableSlot *slot);
+typedef Datum (*slot_getattr_hook) (TupleTableSlot *slot,
+										int attnum, bool *isnull);
+typedef void (*slot_virtualize_tuple_hook) (TupleTableSlot *slot, int16 upto);
+
+typedef HeapTuple (*slot_tuple_hook) (TupleTableSlot *slot, bool palloc_copy);
+typedef MinimalTuple (*slot_min_tuple_hook) (TupleTableSlot *slot, bool palloc_copy);
+
+typedef void (*slot_update_tableoid_hook) (TupleTableSlot *slot, Oid tableoid);
+
+typedef void (*speculative_finish_hook) (Relation rel,
+											 TupleTableSlot *slot);
+typedef void (*speculative_abort_hook) (Relation rel,
+											TupleTableSlot *slot);
+
+typedef void (*relation_sync_hook) (Relation relation);
+
+typedef bool (*snapshot_satisfies_hook) (StorageTuple htup, Snapshot snapshot, Buffer buffer);
+typedef HTSU_Result (*snapshot_satisfies_update_hook) (StorageTuple htup, CommandId curcid, Buffer buffer);
+typedef HTSV_Result (*snapshot_satisfies_vacuum_hook) (StorageTuple htup, TransactionId OldestXmin, Buffer buffer);
+
+typedef struct StorageSlotAmRoutine
+{
+	/* Operations on TupleTableSlot */
+	slot_store_tuple_hook	slot_store_tuple;
+	slot_virtualize_tuple_hook	slot_virtualize_tuple;
+	slot_clear_tuple_hook	slot_clear_tuple;
+	slot_getattr_hook	slot_getattr;
+	slot_tuple_hook     slot_tuple;
+	slot_min_tuple_hook      slot_min_tuple;
+	slot_update_tableoid_hook slot_update_tableoid;
+} StorageSlotAmRoutine;
+
+typedef StorageSlotAmRoutine* (*slot_storageam_hook) (void);
+
+/*
+ * 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;
+
+	/* Operations on relation scans */
+	scan_begin_hook scan_begin;
+	scan_setlimits_hook scansetlimits;
+	scan_getnext_hook scan_getnext;
+	scan_getnext_slot_hook scan_getnextslot;
+	scan_end_hook scan_end;
+	scan_getpage_hook scan_getpage;
+	scan_rescan_hook scan_rescan;
+	scan_update_snapshot_hook scan_update_snapshot;
+	hot_search_buffer_hook hot_search_buffer; /* heap_hot_search_buffer */
+
+	// heap_sync_function		heap_sync;		/* heap_sync */
+	/* not implemented */
+	//	parallelscan_estimate_function	parallelscan_estimate;	/* heap_parallelscan_estimate */
+	//	parallelscan_initialize_function parallelscan_initialize;	/* heap_parallelscan_initialize */
+	//	parallelscan_begin_function	parallelscan_begin;	/* heap_beginscan_parallel */
+
+	/* Operations on physical tuples */
+	tuple_insert_hook		tuple_insert;	/* heap_insert */
+	tuple_update_hook		tuple_update;	/* heap_update */
+	tuple_delete_hook		tuple_delete;	/* heap_delete */
+	tuple_fetch_hook		tuple_fetch;	/* heap_fetch */
+	tuple_lock_hook			tuple_lock;		/* heap_lock_tuple */
+	multi_insert_hook		multi_insert;	/* heap_multi_insert */
+	tuple_freeze_hook       tuple_freeze;	/* heap_freeze_tuple */
+	tuple_get_latest_tid_hook tuple_get_latest_tid; /* heap_get_latest_tid */
+
+	get_tuple_data_hook		get_tuple_data;
+	tuple_is_heaponly_hook	tuple_is_heaponly;
+	tuple_from_datum_hook	tuple_from_datum;
+
+
+	slot_storageam_hook	slot_storageam;
+
+	/*
+	 * 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:
+	 */
+	speculative_finish_hook	speculative_finish;
+	speculative_abort_hook	speculative_abort;
+
+
+	relation_sync_hook	relation_sync;	/* heap_sync */
+
+	snapshot_satisfies_hook snapshot_satisfies[END_OF_VISIBILITY];
+	snapshot_satisfies_update_hook snapshot_satisfiesUpdate; /* HeapTupleSatisfiesUpdate */
+	snapshot_satisfies_vacuum_hook snapshot_satisfiesVacuum; /* HeapTupleSatisfiesVacuum */
+} 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_proc.h b/src/include/catalog/pg_proc.h
index 6b675b0..1dfb711 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 27bd4f3..108df9b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -496,6 +496,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;
diff --git a/src/include/utils/tqual.h b/src/include/utils/tqual.h
index 9a3b56e..de75e01 100644
--- a/src/include/utils/tqual.h
+++ b/src/include/utils/tqual.h
@@ -45,15 +45,6 @@ extern PGDLLIMPORT SnapshotData CatalogSnapshotData;
 #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,
-- 
2.7.4.windows.1

0003-Adding-storageam-hanlder-to-relation-structure.patchapplication/octet-stream; name=0003-Adding-storageam-hanlder-to-relation-structure.patchDownload
From bb4ef35668ba13430fe669d902f554290f50fa77 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
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 | 117 ++++++++++++++++++++++++++++++++++++-
 src/include/utils/rel.h            |  12 ++++
 src/include/utils/relcache.h       |   2 +
 3 files changed, 128 insertions(+), 3 deletions(-)

diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index b8e3780..40cbc67 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -34,6 +34,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"
@@ -1368,10 +1369,26 @@ 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);
@@ -1868,6 +1885,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
@@ -2027,6 +2109,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
 	 */
 	if (IsBootstrapProcessingMode())
@@ -2354,6 +2441,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)
@@ -3368,6 +3457,13 @@ 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.
 	 *
@@ -3889,6 +3985,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);
 
@@ -5607,6 +5715,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 4bc61e5..82d8a53 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -161,6 +161,12 @@ typedef struct RelationData
 	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)
 	 *
 	 * Note: only default support procs for each opclass are cached, namely
@@ -428,6 +434,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 3c53cef..03d996f 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -71,6 +71,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.7.4.windows.1

0004-Adding-tuple-visibility-function-to-storage-AM.patchapplication/octet-stream; name=0004-Adding-tuple-visibility-function-to-storage-AM.patchDownload
From cfd2a2b9181eb15c98cc424deca68024f35c6718 Mon Sep 17 00:00:00 2001
From: Hari Babu Kommi <kommi.haribabu@gmail.com>
Date: Fri, 8 Sep 2017 15:38:28 +1000
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 heapam storage is moved into heapam_common.c
and heapam_common.h files.
---
 contrib/pg_visibility/pg_visibility.c       |   10 +-
 contrib/pgrowlocks/pgrowlocks.c             |    3 +-
 contrib/pgstattuple/pgstatapprox.c          |    7 +-
 contrib/pgstattuple/pgstattuple.c           |    3 +-
 src/backend/access/heap/Makefile            |    3 +-
 src/backend/access/heap/heapam.c            |   23 +-
 src/backend/access/heap/heapam_common.c     |  162 +++
 src/backend/access/heap/heapam_storage.c    | 1622 ++++++++++++++++++++++++
 src/backend/access/heap/pruneheap.c         |    4 +-
 src/backend/access/heap/rewriteheap.c       |    1 +
 src/backend/access/index/genam.c            |    4 +-
 src/backend/catalog/index.c                 |    4 +-
 src/backend/commands/analyze.c              |    2 +-
 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/backend/utils/time/tqual.c              | 1809 ---------------------------
 src/include/access/heapam_common.h          |   96 ++
 src/include/access/htup.h                   |    3 +-
 src/include/storage/bufmgr.h                |    6 +-
 src/include/utils/snapshot.h                |    2 +-
 src/include/utils/tqual.h                   |   66 +-
 29 files changed, 1964 insertions(+), 1907 deletions(-)
 create mode 100644 src/backend/access/heap/heapam_common.c
 delete mode 100644 src/backend/utils/time/tqual.c
 create mode 100644 src/include/access/heapam_common.h

diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c
index 2cc9575..10d47cd 100644
--- a/contrib/pg_visibility/pg_visibility.c
+++ b/contrib/pg_visibility/pg_visibility.c
@@ -51,7 +51,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 +656,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 +681,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 +739,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 eabca65..5f076ef 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,7 +150,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
 		/* must hold a buffer lock to call HeapTupleSatisfiesUpdate */
 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 
-		htsu = HeapTupleSatisfiesUpdate(tuple,
+		htsu = rel->rd_stamroutine->snapshot_satisfiesUpdate(tuple,
 										GetCurrentCommandId(false),
 										scan->rs_cbuf);
 		xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
diff --git a/contrib/pgstattuple/pgstatapprox.c b/contrib/pgstattuple/pgstatapprox.c
index 5bf0613..284eabc 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 7a91cc3..f7b68a8 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 02a3909..e6bc18e 100644
--- a/src/backend/access/heap/Makefile
+++ b/src/backend/access/heap/Makefile
@@ -12,7 +12,8 @@ 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 storageamapi.o \
+OBJS = heapam.o heapam_common.o heapam_storage.o hio.o \
+	pruneheap.o rewriteheap.o storageamapi.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 d20f038..c21d6f8 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -40,11 +40,13 @@
 
 #include "access/bufmask.h"
 #include "access/heapam.h"
+#include "access/heapam_common.h"
 #include "access/heapam_xlog.h"
 #include "access/hio.h"
 #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 +440,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 +655,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);
 
@@ -1950,7 +1953,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 +2100,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 +2274,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;
@@ -3095,7 +3098,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)
 	{
@@ -3206,7 +3209,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;
 	}
 
@@ -3666,7 +3669,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);
@@ -3847,7 +3850,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;
 	}
 
@@ -4598,7 +4601,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_common.c b/src/backend/access/heap/heapam_common.c
new file mode 100644
index 0000000..502f6db
--- /dev/null
+++ b/src/backend/access/heap/heapam_common.c
@@ -0,0 +1,162 @@
+/*-------------------------------------------------------------------------
+ *
+ * heapam_common.c
+ *	  heapam 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/heap/heapam_common.c
+ *
+ *
+ * NOTES
+ *	  This file contains the storage_ routines which implement
+ *	  the POSTGRES storage access method used for all POSTGRES
+ *	  relations.
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/heapam_common.h"
+#include "access/htup_details.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);
+}
+
+
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 88827e7..1bd4bfa 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -21,6 +21,7 @@
 #include "postgres.h"
 
 #include "access/heapam.h"
+#include "access/heapam_common.h"
 #include "access/heapam_xlog.h"
 #include "access/hio.h"
 #include "access/htup_details.h"
@@ -47,6 +48,1617 @@
 #include "utils/rel.h"
 #include "utils/tqual.h"
 
+/* local functions */
+static bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
+
+/*-------------------------------------------------------------------------
+ *
+ * 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
+ * has now committed or aborted (and it is safe to set the hint bits).
+ * If the hint bits are changed, MarkBufferDirtyHint is called on
+ * the passed-in buffer.  The caller must hold not only a pin, but at least
+ * shared buffer content lock on the buffer containing the tuple.
+ *
+ * NOTE: When using a non-MVCC snapshot, we must check
+ * TransactionIdIsInProgress (which looks in the PGXACT array)
+ * before TransactionIdDidCommit/TransactionIdDidAbort (which look in
+ * pg_xact).  Otherwise we have a race condition: we might decide that a
+ * just-committed transaction crashed, because none of the tests succeed.
+ * xact.c is careful to record commit/abort in pg_xact before it unsets
+ * MyPgXact->xid in the PGXACT array.  That fixes that problem, but it
+ * also means there is a window where TransactionIdIsInProgress and
+ * TransactionIdDidCommit will both return true.  If we check only
+ * TransactionIdDidCommit, we could consider a tuple committed when a
+ * later GetSnapshotData call will still think the originating transaction
+ * is in progress, which leads to application-level inconsistency.  The
+ * upshot is that we gotta check TransactionIdIsInProgress first in all
+ * code paths, except for a few cases where we are looking at
+ * subtransactions of our own main transaction and so there can't be any
+ * race condition.
+ *
+ * When using an MVCC snapshot, we rely on XidInMVCCSnapshot rather than
+ * TransactionIdIsInProgress, but the logic is otherwise the same: do not
+ * check pg_xact until after deciding that the xact is no longer in progress.
+ *
+ *
+ * Summary of visibility functions:
+ *
+ *	 HeapTupleSatisfiesMVCC()
+ *		  visible to supplied snapshot, excludes current command
+ *	 HeapTupleSatisfiesUpdate()
+ *		  visible to instant snapshot, with user-supplied command
+ *		  counter and more complex result
+ *	 HeapTupleSatisfiesSelf()
+ *		  visible to instant snapshot and current command
+ *	 HeapTupleSatisfiesDirty()
+ *		  like HeapTupleSatisfiesSelf(), but includes open transactions
+ *	 HeapTupleSatisfiesVacuum()
+ *		  visible to any running transaction, used by VACUUM
+ *   HeapTupleSatisfiesNonVacuumable()
+ *        Snapshot-style API for HeapTupleSatisfiesVacuum
+ *	 HeapTupleSatisfiesToast()
+ *		  visible unless part of interrupted vacuum, used for TOAST
+ *	 HeapTupleSatisfiesAny()
+ *		  all tuples are visible
+ *
+ * -------------------------------------------------------------------------
+ */
+
+/*
+ * HeapTupleSatisfiesSelf
+ *		True iff heap tuple is valid "for itself".
+ *
+ *	Here, we consider the effects of:
+ *		all committed transactions (as of the current instant)
+ *		previous commands of this transaction
+ *		changes made by the current command
+ *
+ * Note:
+ *		Assumes heap tuple is valid.
+ *
+ * The satisfaction of "itself" requires the following:
+ *
+ * ((Xmin == my-transaction &&				the row was updated by the current transaction, and
+ *		(Xmax is null						it was not deleted
+ *		 [|| Xmax != my-transaction)])			[or it was deleted by another transaction]
+ * ||
+ *
+ * (Xmin is committed &&					the row was modified by a committed transaction, and
+ *		(Xmax is null ||					the row has not been deleted, or
+ *			(Xmax != my-transaction &&			the row was deleted by another transaction
+ *			 Xmax is not committed)))			that has not been committed
+ */
+static bool
+HeapTupleSatisfiesSelf(StorageTuple stup, Snapshot snapshot, Buffer buffer)
+{
+	HeapTuple htup = (HeapTuple)stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return false;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return false;
+			if (!TransactionIdIsInProgress(xvac))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (TransactionIdIsInProgress(xvac))
+					return false;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return true;
+
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
+				return true;
+
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleGetUpdateXid(tuple);
+
+				/* not LOCKED_ONLY, so it has to have an xmax */
+				Assert(TransactionIdIsValid(xmax));
+
+				/* updating subtransaction must have aborted */
+				if (!TransactionIdIsCurrentTransactionId(xmax))
+					return true;
+				else
+					return false;
+			}
+
+			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+			{
+				/* deleting subtransaction must have aborted */
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+				return true;
+			}
+
+			return false;
+		}
+		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+			return false;
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return false;
+		}
+	}
+
+	/* by here, the inserting transaction has committed */
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
+		return true;
+
+	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+		return false;			/* updated by other */
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+
+		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 true;
+		if (TransactionIdDidCommit(xmax))
+			return false;
+		/* it must have aborted or crashed */
+		return true;
+	}
+
+	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+		return false;
+	}
+
+	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+		return true;
+
+	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		/* it must have aborted or crashed */
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return true;
+	}
+
+	/* xmax transaction committed */
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	{
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return true;
+	}
+
+	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+				HeapTupleHeaderGetRawXmax(tuple));
+	return false;
+}
+
+/*
+ * HeapTupleSatisfiesAny
+ *		Dummy "satisfies" routine: any tuple satisfies SnapshotAny.
+ */
+static bool
+HeapTupleSatisfiesAny(StorageTuple stup, Snapshot snapshot, Buffer buffer)
+{
+	return true;
+}
+
+/*
+ * HeapTupleSatisfiesToast
+ *		True iff heap tuple is valid as a TOAST row.
+ *
+ * This is a simplified version that only checks for VACUUM moving conditions.
+ * It's appropriate for TOAST usage because TOAST really doesn't want to do
+ * its own time qual checks; if you can see the main table row that contains
+ * a TOAST reference, you should be able to see the TOASTed value.  However,
+ * vacuuming a TOAST table is independent of the main table, and in case such
+ * a vacuum fails partway through, we'd better do this much checking.
+ *
+ * Among other things, this means you can't do UPDATEs of rows in a TOAST
+ * table.
+ */
+static bool
+HeapTupleSatisfiesToast(StorageTuple stup, Snapshot snapshot,
+						Buffer buffer)
+{
+	HeapTuple htup = (HeapTuple)stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return false;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return false;
+			if (!TransactionIdIsInProgress(xvac))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (TransactionIdIsInProgress(xvac))
+					return false;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+			}
+		}
+
+		/*
+		 * An invalid Xmin can be left behind by a speculative insertion that
+		 * is canceled by super-deleting the tuple.  This also applies to
+		 * TOAST tuples created during speculative insertion.
+		 */
+		else if (!TransactionIdIsValid(HeapTupleHeaderGetXmin(tuple)))
+			return false;
+	}
+
+	/* otherwise assume the tuple is valid for TOAST. */
+	return true;
+}
+
+/*
+ * HeapTupleSatisfiesUpdate
+ *
+ *	This function returns a more detailed result code than most of the
+ *	functions in this file, since UPDATE needs to know more than "is it
+ *	visible?".  It also allows for user-supplied CommandId rather than
+ *	relying on CurrentCommandId.
+ *
+ *	The possible return codes are:
+ *
+ *	HeapTupleInvisible: the tuple didn't exist at all when the scan started,
+ *	e.g. it was created by a later CommandId.
+ *
+ *	HeapTupleMayBeUpdated: The tuple is valid and visible, so it may be
+ *	updated.
+ *
+ *	HeapTupleSelfUpdated: The tuple was updated by the current transaction,
+ *	after the current scan started.
+ *
+ *	HeapTupleUpdated: The tuple was updated by a committed transaction.
+ *
+ *	HeapTupleBeingUpdated: The tuple is being updated by an in-progress
+ *	transaction other than the current transaction.  (Note: this includes
+ *	the case where the tuple is share-locked by a MultiXact, even if the
+ *	MultiXact includes the current transaction.  Callers that want to
+ *	distinguish that case must test for it themselves.)
+ */
+static HTSU_Result
+HeapTupleSatisfiesUpdate(StorageTuple stup, CommandId curcid,
+						 Buffer buffer)
+{
+	HeapTuple htup = (HeapTuple)stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return HeapTupleInvisible;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return HeapTupleInvisible;
+			if (!TransactionIdIsInProgress(xvac))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return HeapTupleInvisible;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (TransactionIdIsInProgress(xvac))
+					return HeapTupleInvisible;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return HeapTupleInvisible;
+				}
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (HeapTupleHeaderGetCmin(tuple) >= curcid)
+				return HeapTupleInvisible;	/* inserted after scan started */
+
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return HeapTupleMayBeUpdated;
+
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleHeaderGetRawXmax(tuple);
+
+				/*
+				 * Careful here: even though this tuple was created by our own
+				 * transaction, it might be locked by other transactions, if
+				 * the original version was key-share locked when we updated
+				 * it.
+				 */
+
+				if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+				{
+					if (MultiXactIdIsRunning(xmax, true))
+						return HeapTupleBeingUpdated;
+					else
+						return HeapTupleMayBeUpdated;
+				}
+
+				/*
+				 * If the locker is gone, then there is nothing of interest
+				 * left in this Xmax; otherwise, report the tuple as
+				 * locked/updated.
+				 */
+				if (!TransactionIdIsInProgress(xmax))
+					return HeapTupleMayBeUpdated;
+				return HeapTupleBeingUpdated;
+			}
+
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleGetUpdateXid(tuple);
+
+				/* not LOCKED_ONLY, so it has to have an xmax */
+				Assert(TransactionIdIsValid(xmax));
+
+				/* deleting subtransaction must have aborted */
+				if (!TransactionIdIsCurrentTransactionId(xmax))
+				{
+					if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
+											 false))
+						return HeapTupleBeingUpdated;
+					return HeapTupleMayBeUpdated;
+				}
+				else
+				{
+					if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+						return HeapTupleSelfUpdated;	/* updated after scan
+														 * started */
+					else
+						return HeapTupleInvisible;	/* updated before scan
+													 * started */
+				}
+			}
+
+			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+			{
+				/* deleting subtransaction must have aborted */
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+				return HeapTupleMayBeUpdated;
+			}
+
+			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+				return HeapTupleSelfUpdated;	/* updated after scan started */
+			else
+				return HeapTupleInvisible;	/* updated before scan started */
+		}
+		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+			return HeapTupleInvisible;
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return HeapTupleInvisible;
+		}
+	}
+
+	/* by here, the inserting transaction has committed */
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
+		return HeapTupleMayBeUpdated;
+
+	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return HeapTupleMayBeUpdated;
+		return HeapTupleUpdated;	/* updated by other */
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		if (HEAP_LOCKED_UPGRADED(tuple->t_infomask))
+			return HeapTupleMayBeUpdated;
+
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+		{
+			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), true))
+				return HeapTupleBeingUpdated;
+
+			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
+			return HeapTupleMayBeUpdated;
+		}
+
+		xmax = HeapTupleGetUpdateXid(tuple);
+		if (!TransactionIdIsValid(xmax))
+		{
+			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+				return HeapTupleBeingUpdated;
+		}
+
+		/* not LOCKED_ONLY, so it has to have an xmax */
+		Assert(TransactionIdIsValid(xmax));
+
+		if (TransactionIdIsCurrentTransactionId(xmax))
+		{
+			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+				return HeapTupleSelfUpdated;	/* updated after scan started */
+			else
+				return HeapTupleInvisible;	/* updated before scan started */
+		}
+
+		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+			return HeapTupleBeingUpdated;
+
+		if (TransactionIdDidCommit(xmax))
+			return HeapTupleUpdated;
+
+		/*
+		 * By here, the update in the Xmax is either aborted or crashed, but
+		 * what about the other members?
+		 */
+
+		if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+		{
+			/*
+			 * There's no member, even just a locker, alive anymore, so we can
+			 * mark the Xmax as invalid.
+			 */
+			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+						InvalidTransactionId);
+			return HeapTupleMayBeUpdated;
+		}
+		else
+		{
+			/* There are lockers running */
+			return HeapTupleBeingUpdated;
+		}
+	}
+
+	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return HeapTupleBeingUpdated;
+		if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+			return HeapTupleSelfUpdated;	/* updated after scan started */
+		else
+			return HeapTupleInvisible;	/* updated before scan started */
+	}
+
+	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+		return HeapTupleBeingUpdated;
+
+	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		/* it must have aborted or crashed */
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return HeapTupleMayBeUpdated;
+	}
+
+	/* xmax transaction committed */
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	{
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return HeapTupleMayBeUpdated;
+	}
+
+	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+				HeapTupleHeaderGetRawXmax(tuple));
+	return HeapTupleUpdated;	/* updated by other */
+}
+
+/*
+ * HeapTupleSatisfiesDirty
+ *		True iff heap tuple is valid including effects of open transactions.
+ *
+ *	Here, we consider the effects of:
+ *		all committed and in-progress transactions (as of the current instant)
+ *		previous commands of this transaction
+ *		changes made by the current command
+ *
+ * This is essentially like HeapTupleSatisfiesSelf as far as effects of
+ * the current transaction and committed/aborted xacts are concerned.
+ * However, we also include the effects of other xacts still in progress.
+ *
+ * A special hack is that the passed-in snapshot struct is used as an
+ * output argument to return the xids of concurrent xacts that affected the
+ * tuple.  snapshot->xmin is set to the tuple's xmin if that is another
+ * transaction that's still in progress; or to InvalidTransactionId if the
+ * tuple's xmin is committed good, committed dead, or my own xact.
+ * Similarly for snapshot->xmax and the tuple's xmax.  If the tuple was
+ * inserted speculatively, meaning that the inserter might still back down
+ * on the insertion without aborting the whole transaction, the associated
+ * token is also returned in snapshot->speculativeToken.
+ */
+static bool
+HeapTupleSatisfiesDirty(StorageTuple stup, Snapshot snapshot,
+						Buffer buffer)
+{
+	HeapTuple htup = (HeapTuple)stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	snapshot->xmin = snapshot->xmax = InvalidTransactionId;
+	snapshot->speculativeToken = 0;
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return false;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return false;
+			if (!TransactionIdIsInProgress(xvac))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (TransactionIdIsInProgress(xvac))
+					return false;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return true;
+
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
+				return true;
+
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleGetUpdateXid(tuple);
+
+				/* not LOCKED_ONLY, so it has to have an xmax */
+				Assert(TransactionIdIsValid(xmax));
+
+				/* updating subtransaction must have aborted */
+				if (!TransactionIdIsCurrentTransactionId(xmax))
+					return true;
+				else
+					return false;
+			}
+
+			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+			{
+				/* deleting subtransaction must have aborted */
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+				return true;
+			}
+
+			return false;
+		}
+		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			/*
+			 * Return the speculative token to caller.  Caller can worry about
+			 * xmax, since it requires a conclusively locked row version, and
+			 * a concurrent update to this tuple is a conflict of its
+			 * purposes.
+			 */
+			if (HeapTupleHeaderIsSpeculative(tuple))
+			{
+				snapshot->speculativeToken =
+					HeapTupleHeaderGetSpeculativeToken(tuple);
+
+				Assert(snapshot->speculativeToken != 0);
+			}
+
+			snapshot->xmin = HeapTupleHeaderGetRawXmin(tuple);
+			/* XXX shouldn't we fall through to look at xmax? */
+			return true;		/* in insertion by other */
+		}
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return false;
+		}
+	}
+
+	/* by here, the inserting transaction has committed */
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
+		return true;
+
+	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+		return false;			/* updated by other */
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+
+		xmax = HeapTupleGetUpdateXid(tuple);
+
+		/* not LOCKED_ONLY, so it has to have an xmax */
+		Assert(TransactionIdIsValid(xmax));
+
+		if (TransactionIdIsCurrentTransactionId(xmax))
+			return false;
+		if (TransactionIdIsInProgress(xmax))
+		{
+			snapshot->xmax = xmax;
+			return true;
+		}
+		if (TransactionIdDidCommit(xmax))
+			return false;
+		/* it must have aborted or crashed */
+		return true;
+	}
+
+	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+		return false;
+	}
+
+	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			snapshot->xmax = HeapTupleHeaderGetRawXmax(tuple);
+		return true;
+	}
+
+	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		/* it must have aborted or crashed */
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return true;
+	}
+
+	/* xmax transaction committed */
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	{
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return true;
+	}
+
+	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+				HeapTupleHeaderGetRawXmax(tuple));
+	return false;				/* updated by other */
+}
+
+/*
+ * HeapTupleSatisfiesMVCC
+ *		True iff heap tuple is valid for the given MVCC snapshot.
+ *
+ *	Here, we consider the effects of:
+ *		all transactions committed as of the time of the given snapshot
+ *		previous commands of this transaction
+ *
+ *	Does _not_ include:
+ *		transactions shown as in-progress by the snapshot
+ *		transactions started after the snapshot was taken
+ *		changes made by the current command
+ *
+ * Notice that here, we will not update the tuple status hint bits if the
+ * inserting/deleting transaction is still running according to our snapshot,
+ * even if in reality it's committed or aborted by now.  This is intentional.
+ * Checking the true transaction state would require access to high-traffic
+ * shared data structures, creating contention we'd rather do without, and it
+ * would not change the result of our visibility check anyway.  The hint bits
+ * will be updated by the first visitor that has a snapshot new enough to see
+ * the inserting/deleting transaction as done.  In the meantime, the cost of
+ * leaving the hint bits unset is basically that each HeapTupleSatisfiesMVCC
+ * call will need to run TransactionIdIsCurrentTransactionId in addition to
+ * XidInMVCCSnapshot (but it would have to do the latter anyway).  In the old
+ * coding where we tried to set the hint bits as soon as possible, we instead
+ * did TransactionIdIsInProgress in each call --- to no avail, as long as the
+ * inserting/deleting transaction was still running --- which was more cycles
+ * and more contention on the PGXACT array.
+ */
+static bool
+HeapTupleSatisfiesMVCC(StorageTuple stup, Snapshot snapshot,
+					   Buffer buffer)
+{
+	HeapTuple htup = (HeapTuple)stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return false;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return false;
+			if (!XidInMVCCSnapshot(xvac, snapshot))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (XidInMVCCSnapshot(xvac, snapshot))
+					return false;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (HeapTupleHeaderGetCmin(tuple) >= snapshot->curcid)
+				return false;	/* inserted after scan started */
+
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return true;
+
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
+				return true;
+
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleGetUpdateXid(tuple);
+
+				/* not LOCKED_ONLY, so it has to have an xmax */
+				Assert(TransactionIdIsValid(xmax));
+
+				/* updating subtransaction must have aborted */
+				if (!TransactionIdIsCurrentTransactionId(xmax))
+					return true;
+				else if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+					return true;	/* updated after scan started */
+				else
+					return false;	/* updated before scan started */
+			}
+
+			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+			{
+				/* deleting subtransaction must have aborted */
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+				return true;
+			}
+
+			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+				return true;	/* deleted after scan started */
+			else
+				return false;	/* deleted before scan started */
+		}
+		else if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
+			return false;
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return false;
+		}
+	}
+	else
+	{
+		/* xmin is committed, but maybe not according to our snapshot */
+		if (!HeapTupleHeaderXminFrozen(tuple) &&
+			XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
+			return false;		/* treat as still in progress */
+	}
+
+	/* by here, the inserting transaction has committed */
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
+		return true;
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+		return true;
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		/* already checked above */
+		Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
+
+		xmax = HeapTupleGetUpdateXid(tuple);
+
+		/* not LOCKED_ONLY, so it has to have an xmax */
+		Assert(TransactionIdIsValid(xmax));
+
+		if (TransactionIdIsCurrentTransactionId(xmax))
+		{
+			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+				return true;	/* deleted after scan started */
+			else
+				return false;	/* deleted before scan started */
+		}
+		if (XidInMVCCSnapshot(xmax, snapshot))
+			return true;
+		if (TransactionIdDidCommit(xmax))
+			return false;		/* updating transaction committed */
+		/* it must have aborted or crashed */
+		return true;
+	}
+
+	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
+	{
+		if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+		{
+			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+				return true;	/* deleted after scan started */
+			else
+				return false;	/* deleted before scan started */
+		}
+
+		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
+			return true;
+
+		if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+						InvalidTransactionId);
+			return true;
+		}
+
+		/* xmax transaction committed */
+		SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+					HeapTupleHeaderGetRawXmax(tuple));
+	}
+	else
+	{
+		/* xmax is committed, but maybe not according to our snapshot */
+		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
+			return true;		/* treat as still in progress */
+	}
+
+	/* xmax transaction committed */
+
+	return false;
+}
+
+
+/*
+ * HeapTupleSatisfiesVacuum
+ *
+ *	Determine the status of tuples for VACUUM purposes.  Here, what
+ *	we mainly want to know is if a tuple is potentially visible to *any*
+ *	running transaction.  If so, it can't be removed yet by VACUUM.
+ *
+ * OldestXmin is a cutoff XID (obtained from GetOldestXmin()).  Tuples
+ * deleted by XIDs >= OldestXmin are deemed "recently dead"; they might
+ * still be visible to some open transaction, so we can't remove them,
+ * even if we see that the deleting transaction has committed.
+ */
+static HTSV_Result
+HeapTupleSatisfiesVacuum(StorageTuple stup, TransactionId OldestXmin,
+						 Buffer buffer)
+{
+	HeapTuple htup = (HeapTuple)stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	/*
+	 * Has inserting transaction committed?
+	 *
+	 * If the inserting transaction aborted, then the tuple was never visible
+	 * to any other transaction, so we can delete it immediately.
+	 */
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return HEAPTUPLE_DEAD;
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return HEAPTUPLE_DELETE_IN_PROGRESS;
+			if (TransactionIdIsInProgress(xvac))
+				return HEAPTUPLE_DELETE_IN_PROGRESS;
+			if (TransactionIdDidCommit(xvac))
+			{
+				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+							InvalidTransactionId);
+				return HEAPTUPLE_DEAD;
+			}
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						InvalidTransactionId);
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return HEAPTUPLE_INSERT_IN_PROGRESS;
+			if (TransactionIdIsInProgress(xvac))
+				return HEAPTUPLE_INSERT_IN_PROGRESS;
+			if (TransactionIdDidCommit(xvac))
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			else
+			{
+				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+							InvalidTransactionId);
+				return HEAPTUPLE_DEAD;
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return HEAPTUPLE_INSERT_IN_PROGRESS;
+			/* only locked? run infomask-only check first, for performance */
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask) ||
+				HeapTupleHeaderIsOnlyLocked(tuple))
+				return HEAPTUPLE_INSERT_IN_PROGRESS;
+			/* inserted and then deleted by same xact */
+			if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple)))
+				return HEAPTUPLE_DELETE_IN_PROGRESS;
+			/* deleting subtransaction must have aborted */
+			return HEAPTUPLE_INSERT_IN_PROGRESS;
+		}
+		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			/*
+			 * It'd be possible to discern between INSERT/DELETE in progress
+			 * here by looking at xmax - but that doesn't seem beneficial for
+			 * the majority of callers and even detrimental for some. We'd
+			 * rather have callers look at/wait for xmin than xmax. It's
+			 * always correct to return INSERT_IN_PROGRESS because that's
+			 * what's happening from the view of other backends.
+			 */
+			return HEAPTUPLE_INSERT_IN_PROGRESS;
+		}
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/*
+			 * Not in Progress, Not Committed, so either Aborted or crashed
+			 */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return HEAPTUPLE_DEAD;
+		}
+
+		/*
+		 * At this point the xmin is known committed, but we might not have
+		 * been able to set the hint bit yet; so we can no longer Assert that
+		 * it's set.
+		 */
+	}
+
+	/*
+	 * Okay, the inserter committed, so it was good at some point.  Now what
+	 * about the deleting transaction?
+	 */
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)
+		return HEAPTUPLE_LIVE;
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	{
+		/*
+		 * "Deleting" xact really only locked it, so the tuple is live in any
+		 * case.  However, we should make sure that either XMAX_COMMITTED or
+		 * XMAX_INVALID gets set once the xact is gone, to reduce the costs of
+		 * examining the tuple for future xacts.
+		 */
+		if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
+		{
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				/*
+				 * If it's a pre-pg_upgrade tuple, the multixact cannot
+				 * possibly be running; otherwise have to check.
+				 */
+				if (!HEAP_LOCKED_UPGRADED(tuple->t_infomask) &&
+					MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
+										 true))
+					return HEAPTUPLE_LIVE;
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
+			}
+			else
+			{
+				if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+					return HEAPTUPLE_LIVE;
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+			}
+		}
+
+		/*
+		 * We don't really care whether xmax did commit, abort or crash. We
+		 * know that xmax did lock the tuple, but it did not and will never
+		 * actually update it.
+		 */
+
+		return HEAPTUPLE_LIVE;
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+		{
+			/* already checked above */
+			Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
+
+			xmax = HeapTupleGetUpdateXid(tuple);
+
+			/* not LOCKED_ONLY, so it has to have an xmax */
+			Assert(TransactionIdIsValid(xmax));
+
+			if (TransactionIdIsInProgress(xmax))
+				return HEAPTUPLE_DELETE_IN_PROGRESS;
+			else if (TransactionIdDidCommit(xmax))
+				/* there are still lockers around -- can't return DEAD here */
+				return HEAPTUPLE_RECENTLY_DEAD;
+			/* updating transaction aborted */
+			return HEAPTUPLE_LIVE;
+		}
+
+		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED));
+
+		xmax = HeapTupleGetUpdateXid(tuple);
+
+		/* not LOCKED_ONLY, so it has to have an xmax */
+		Assert(TransactionIdIsValid(xmax));
+
+		/* multi is not running -- updating xact cannot be */
+		Assert(!TransactionIdIsInProgress(xmax));
+		if (TransactionIdDidCommit(xmax))
+		{
+			if (!TransactionIdPrecedes(xmax, OldestXmin))
+				return HEAPTUPLE_RECENTLY_DEAD;
+			else
+				return HEAPTUPLE_DEAD;
+		}
+
+		/*
+		 * Not in Progress, Not Committed, so either Aborted or crashed.
+		 * Remove the Xmax.
+		 */
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
+		return HEAPTUPLE_LIVE;
+	}
+
+	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
+	{
+		if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+			return HEAPTUPLE_DELETE_IN_PROGRESS;
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+						HeapTupleHeaderGetRawXmax(tuple));
+		else
+		{
+			/*
+			 * Not in Progress, Not Committed, so either Aborted or crashed
+			 */
+			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+						InvalidTransactionId);
+			return HEAPTUPLE_LIVE;
+		}
+
+		/*
+		 * At this point the xmax is known committed, but we might not have
+		 * been able to set the hint bit yet; so we can no longer Assert that
+		 * it's set.
+		 */
+	}
+
+	/*
+	 * Deleter committed, but perhaps it was recent enough that some open
+	 * transactions could still see the tuple.
+	 */
+	if (!TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin))
+		return HEAPTUPLE_RECENTLY_DEAD;
+
+	/* Otherwise, it's dead and removable */
+	return HEAPTUPLE_DEAD;
+}
+
+/*
+ * HeapTupleSatisfiesNonVacuumable
+ *
+ *     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.
+ */
+static bool
+HeapTupleSatisfiesNonVacuumable(StorageTuple htup, Snapshot snapshot,
+                                                               Buffer buffer)
+{
+	return HeapTupleSatisfiesVacuum(htup, snapshot->xmin, buffer)
+				!= HEAPTUPLE_DEAD;
+}
+
+/*
+ * 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 for known-committed
+ * XIDs which could not be ours anyway.
+ */
+static 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;
+}
+
+/*
+ * check whether the transaction id 'xid' is in the pre-sorted array 'xip'.
+ */
+static bool
+TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num)
+{
+	return bsearch(&xid, xip, num,
+				   sizeof(TransactionId), xidComparator) != NULL;
+}
+
+/*
+ * See the comments for HeapTupleSatisfiesMVCC for the semantics this function
+ * obeys.
+ *
+ * Only usable on tuples from catalog tables!
+ *
+ * We don't need to support HEAP_MOVED_(IN|OFF) for now because we only support
+ * reading catalog pages which couldn't have been created in an older version.
+ *
+ * We don't set any hint bits in here as it seems unlikely to be beneficial as
+ * those should already be set by normal access and it seems to be too
+ * dangerous to do so as the semantics of doing so during timetravel are more
+ * complicated than when dealing "only" with the present.
+ */
+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);
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	/* inserting transaction aborted */
+	if (HeapTupleHeaderXminInvalid(tuple))
+	{
+		Assert(!TransactionIdDidCommit(xmin));
+		return false;
+	}
+	/* check if it's one of our txids, toplevel is also in there */
+	else if (TransactionIdInArray(xmin, snapshot->subxip, snapshot->subxcnt))
+	{
+		bool		resolved;
+		CommandId	cmin = HeapTupleHeaderGetRawCommandId(tuple);
+		CommandId	cmax = InvalidCommandId;
+
+		/*
+		 * another transaction might have (tried to) delete this tuple or
+		 * cmin/cmax was stored in a combocid. So we need to lookup the actual
+		 * values externally.
+		 */
+		resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot,
+												 htup, buffer,
+												 &cmin, &cmax);
+
+		if (!resolved)
+			elog(ERROR, "could not resolve cmin/cmax of catalog tuple");
+
+		Assert(cmin != InvalidCommandId);
+
+		if (cmin >= snapshot->curcid)
+			return false;		/* inserted after scan started */
+		/* fall through */
+	}
+	/* committed before our xmin horizon. Do a normal visibility check. */
+	else if (TransactionIdPrecedes(xmin, snapshot->xmin))
+	{
+		Assert(!(HeapTupleHeaderXminCommitted(tuple) &&
+				 !TransactionIdDidCommit(xmin)));
+
+		/* check for hint bit first, consult clog afterwards */
+		if (!HeapTupleHeaderXminCommitted(tuple) &&
+			!TransactionIdDidCommit(xmin))
+			return false;
+		/* fall through */
+	}
+	/* beyond our xmax horizon, i.e. invisible */
+	else if (TransactionIdFollowsOrEquals(xmin, snapshot->xmax))
+	{
+		return false;
+	}
+	/* check if it's a committed transaction in [xmin, xmax) */
+	else if (TransactionIdInArray(xmin, snapshot->xip, snapshot->xcnt))
+	{
+		/* fall through */
+	}
+
+	/*
+	 * none of the above, i.e. between [xmin, xmax) but hasn't committed. I.e.
+	 * invisible.
+	 */
+	else
+	{
+		return false;
+	}
+
+	/* at this point we know xmin is visible, go on to check xmax */
+
+	/* xid invalid or aborted */
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)
+		return true;
+	/* locked tuples are always visible */
+	else if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+		return true;
+
+	/*
+	 * We can see multis here if we're looking at user tables or if somebody
+	 * SELECT ... FOR SHARE/UPDATE a system table.
+	 */
+	else if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		xmax = HeapTupleGetUpdateXid(tuple);
+	}
+
+	/* check if it's one of our txids, toplevel is also in there */
+	if (TransactionIdInArray(xmax, snapshot->subxip, snapshot->subxcnt))
+	{
+		bool		resolved;
+		CommandId	cmin;
+		CommandId	cmax = HeapTupleHeaderGetRawCommandId(tuple);
+
+		/* Lookup actual cmin/cmax values */
+		resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot,
+												 htup, buffer,
+												 &cmin, &cmax);
+
+		if (!resolved)
+			elog(ERROR, "could not resolve combocid to cmax");
+
+		Assert(cmax != InvalidCommandId);
+
+		if (cmax >= snapshot->curcid)
+			return true;		/* deleted after scan started */
+		else
+			return false;		/* deleted before scan started */
+	}
+	/* below xmin horizon, normal transaction state is valid */
+	else if (TransactionIdPrecedes(xmax, snapshot->xmin))
+	{
+		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED &&
+				 !TransactionIdDidCommit(xmax)));
+
+		/* check hint bit first */
+		if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+			return false;
+
+		/* check clog */
+		return !TransactionIdDidCommit(xmax);
+	}
+	/* above xmax horizon, we cannot possibly see the deleting transaction */
+	else if (TransactionIdFollowsOrEquals(xmax, snapshot->xmax))
+		return true;
+	/* xmax is between [xmin, xmax), check known committed array */
+	else if (TransactionIdInArray(xmax, snapshot->xip, snapshot->xcnt))
+		return false;
+	/* xmax is between [xmin, xmax), but known not to have committed yet */
+	else
+		return true;
+}
 
 Datum
 heapam_storage_handler(PG_FUNCTION_ARGS)
@@ -54,5 +1666,15 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
 
 
+	amroutine->snapshot_satisfies[MVCC_VISIBILITY] = HeapTupleSatisfiesMVCC;
+	amroutine->snapshot_satisfies[SELF_VISIBILITY] = HeapTupleSatisfiesSelf;
+	amroutine->snapshot_satisfies[ANY_VISIBILITY] = HeapTupleSatisfiesAny;
+	amroutine->snapshot_satisfies[TOAST_VISIBILITY] = HeapTupleSatisfiesToast;
+	amroutine->snapshot_satisfies[DIRTY_VISIBILITY] = HeapTupleSatisfiesDirty;
+	amroutine->snapshot_satisfies[HISTORIC_MVCC_VISIBILITY] = HeapTupleSatisfiesHistoricMVCC;
+	amroutine->snapshot_satisfies[NON_VACUUMABLE_VISIBILTY] = HeapTupleSatisfiesNonVacuumable;
+
+	amroutine->snapshot_satisfiesUpdate = HeapTupleSatisfiesUpdate;
+	amroutine->snapshot_satisfiesVacuum = HeapTupleSatisfiesVacuum;
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 52231ac..7425068 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/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index bd560e4..191f088 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -108,6 +108,7 @@
 #include "miscadmin.h"
 
 #include "access/heapam.h"
+#include "access/heapam_common.h"
 #include "access/heapam_xlog.h"
 #include "access/rewriteheap.h"
 #include "access/transam.h"
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 05d7da0..01321a2 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/catalog/index.c b/src/backend/catalog/index.c
index c7b2f03..0240df7 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2222,6 +2222,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	TransactionId OldestXmin;
 	BlockNumber root_blkno = InvalidBlockNumber;
 	OffsetNumber root_offsets[MaxHeapTuplesPerPage];
+	StorageAmRoutine *method;
 
 	/*
 	 * sanity checks
@@ -2277,6 +2278,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 */
@@ -2357,7 +2359,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 			 */
 			LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 
-			switch (HeapTupleSatisfiesVacuum(heapTuple, OldestXmin,
+			switch (method->snapshot_satisfiesVacuum(heapTuple, OldestXmin,
 											 scan->rs_cbuf))
 			{
 				case HEAPTUPLE_DEAD:
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 08fc18e..2611cca 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1069,7 +1069,7 @@ acquire_sample_rows(Relation onerel, int elevel,
 			targtuple.t_data = (HeapTupleHeader) PageGetItem(targpage, itemid);
 			targtuple.t_len = ItemIdGetLength(itemid);
 
-			switch (HeapTupleSatisfiesVacuum(&targtuple,
+			switch (onerel->rd_stamroutine->snapshot_satisfiesVacuum(&targtuple,
 											 OldestXmin,
 											 targbuffer))
 			{
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 48f1e6e..dbcc5bc 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 45b1859..dccda5a 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -975,7 +975,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:
 
@@ -2140,7 +2140,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 f7e55e0..60a6cb0 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -428,7 +428,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 49586a3..d1afda4 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -190,6 +190,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
  */
 static void
 ExecCheckHeapTupleVisible(EState *estate,
+						  Relation rel,
 						  HeapTuple tuple,
 						  Buffer buffer)
 {
@@ -201,7 +202,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
@@ -236,7 +237,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);
 }
 
@@ -1307,7 +1308,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 9c74a83..6a118d1 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 fba57a0..095d1ed 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 251a359..4fbad9f 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 c2891e6..5a6d216 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -289,7 +289,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 5a6e6fa..f17b1c5 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 294ab70..f823da0 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;
@@ -2040,7 +2040,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),
@@ -2137,7 +2137,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/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c
deleted file mode 100644
index bbac408..0000000
--- a/src/backend/utils/time/tqual.c
+++ /dev/null
@@ -1,1809 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * tqual.c
- *	  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
- * has now committed or aborted (and it is safe to set the hint bits).
- * If the hint bits are changed, MarkBufferDirtyHint is called on
- * the passed-in buffer.  The caller must hold not only a pin, but at least
- * shared buffer content lock on the buffer containing the tuple.
- *
- * NOTE: When using a non-MVCC snapshot, we must check
- * TransactionIdIsInProgress (which looks in the PGXACT array)
- * before TransactionIdDidCommit/TransactionIdDidAbort (which look in
- * pg_xact).  Otherwise we have a race condition: we might decide that a
- * just-committed transaction crashed, because none of the tests succeed.
- * xact.c is careful to record commit/abort in pg_xact before it unsets
- * MyPgXact->xid in the PGXACT array.  That fixes that problem, but it
- * also means there is a window where TransactionIdIsInProgress and
- * TransactionIdDidCommit will both return true.  If we check only
- * TransactionIdDidCommit, we could consider a tuple committed when a
- * later GetSnapshotData call will still think the originating transaction
- * is in progress, which leads to application-level inconsistency.  The
- * upshot is that we gotta check TransactionIdIsInProgress first in all
- * code paths, except for a few cases where we are looking at
- * subtransactions of our own main transaction and so there can't be any
- * race condition.
- *
- * When using an MVCC snapshot, we rely on XidInMVCCSnapshot rather than
- * TransactionIdIsInProgress, but the logic is otherwise the same: do not
- * check pg_xact until after deciding that the xact is no longer in progress.
- *
- *
- * Summary of visibility functions:
- *
- *	 HeapTupleSatisfiesMVCC()
- *		  visible to supplied snapshot, excludes current command
- *	 HeapTupleSatisfiesUpdate()
- *		  visible to instant snapshot, with user-supplied command
- *		  counter and more complex result
- *	 HeapTupleSatisfiesSelf()
- *		  visible to instant snapshot and current command
- *	 HeapTupleSatisfiesDirty()
- *		  like HeapTupleSatisfiesSelf(), but includes open transactions
- *	 HeapTupleSatisfiesVacuum()
- *		  visible to any running transaction, used by VACUUM
- *	 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};
-
-/* local functions */
-static 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);
-}
-
-/*
- * 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
- *		True iff heap tuple is valid "for itself".
- *
- *	Here, we consider the effects of:
- *		all committed transactions (as of the current instant)
- *		previous commands of this transaction
- *		changes made by the current command
- *
- * Note:
- *		Assumes heap tuple is valid.
- *
- * The satisfaction of "itself" requires the following:
- *
- * ((Xmin == my-transaction &&				the row was updated by the current transaction, and
- *		(Xmax is null						it was not deleted
- *		 [|| Xmax != my-transaction)])			[or it was deleted by another transaction]
- * ||
- *
- * (Xmin is committed &&					the row was modified by a committed transaction, and
- *		(Xmax is null ||					the row has not been deleted, or
- *			(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)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return false;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return false;
-			if (!TransactionIdIsInProgress(xvac))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (TransactionIdIsInProgress(xvac))
-					return false;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return true;
-
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
-				return true;
-
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleGetUpdateXid(tuple);
-
-				/* not LOCKED_ONLY, so it has to have an xmax */
-				Assert(TransactionIdIsValid(xmax));
-
-				/* updating subtransaction must have aborted */
-				if (!TransactionIdIsCurrentTransactionId(xmax))
-					return true;
-				else
-					return false;
-			}
-
-			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-			{
-				/* deleting subtransaction must have aborted */
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-				return true;
-			}
-
-			return false;
-		}
-		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
-			return false;
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return false;
-		}
-	}
-
-	/* by here, the inserting transaction has committed */
-
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
-		return true;
-
-	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-		return false;			/* updated by other */
-	}
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-
-		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 true;
-		if (TransactionIdDidCommit(xmax))
-			return false;
-		/* it must have aborted or crashed */
-		return true;
-	}
-
-	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-		return false;
-	}
-
-	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-		return true;
-
-	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		/* it must have aborted or crashed */
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return true;
-	}
-
-	/* xmax transaction committed */
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-	{
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return true;
-	}
-
-	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-				HeapTupleHeaderGetRawXmax(tuple));
-	return false;
-}
-
-/*
- * HeapTupleSatisfiesAny
- *		Dummy "satisfies" routine: any tuple satisfies SnapshotAny.
- */
-bool
-HeapTupleSatisfiesAny(HeapTuple htup, Snapshot snapshot, Buffer buffer)
-{
-	return true;
-}
-
-/*
- * HeapTupleSatisfiesToast
- *		True iff heap tuple is valid as a TOAST row.
- *
- * This is a simplified version that only checks for VACUUM moving conditions.
- * It's appropriate for TOAST usage because TOAST really doesn't want to do
- * its own time qual checks; if you can see the main table row that contains
- * a TOAST reference, you should be able to see the TOASTed value.  However,
- * vacuuming a TOAST table is independent of the main table, and in case such
- * a vacuum fails partway through, we'd better do this much checking.
- *
- * Among other things, this means you can't do UPDATEs of rows in a TOAST
- * table.
- */
-bool
-HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot,
-						Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return false;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return false;
-			if (!TransactionIdIsInProgress(xvac))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (TransactionIdIsInProgress(xvac))
-					return false;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-			}
-		}
-
-		/*
-		 * An invalid Xmin can be left behind by a speculative insertion that
-		 * is canceled by super-deleting the tuple.  This also applies to
-		 * TOAST tuples created during speculative insertion.
-		 */
-		else if (!TransactionIdIsValid(HeapTupleHeaderGetXmin(tuple)))
-			return false;
-	}
-
-	/* otherwise assume the tuple is valid for TOAST. */
-	return true;
-}
-
-/*
- * HeapTupleSatisfiesUpdate
- *
- *	This function returns a more detailed result code than most of the
- *	functions in this file, since UPDATE needs to know more than "is it
- *	visible?".  It also allows for user-supplied CommandId rather than
- *	relying on CurrentCommandId.
- *
- *	The possible return codes are:
- *
- *	HeapTupleInvisible: the tuple didn't exist at all when the scan started,
- *	e.g. it was created by a later CommandId.
- *
- *	HeapTupleMayBeUpdated: The tuple is valid and visible, so it may be
- *	updated.
- *
- *	HeapTupleSelfUpdated: The tuple was updated by the current transaction,
- *	after the current scan started.
- *
- *	HeapTupleUpdated: The tuple was updated by a committed transaction.
- *
- *	HeapTupleBeingUpdated: The tuple is being updated by an in-progress
- *	transaction other than the current transaction.  (Note: this includes
- *	the case where the tuple is share-locked by a MultiXact, even if the
- *	MultiXact includes the current transaction.  Callers that want to
- *	distinguish that case must test for it themselves.)
- */
-HTSU_Result
-HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
-						 Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return HeapTupleInvisible;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return HeapTupleInvisible;
-			if (!TransactionIdIsInProgress(xvac))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return HeapTupleInvisible;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (TransactionIdIsInProgress(xvac))
-					return HeapTupleInvisible;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return HeapTupleInvisible;
-				}
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (HeapTupleHeaderGetCmin(tuple) >= curcid)
-				return HeapTupleInvisible;	/* inserted after scan started */
-
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return HeapTupleMayBeUpdated;
-
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleHeaderGetRawXmax(tuple);
-
-				/*
-				 * Careful here: even though this tuple was created by our own
-				 * transaction, it might be locked by other transactions, if
-				 * the original version was key-share locked when we updated
-				 * it.
-				 */
-
-				if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-				{
-					if (MultiXactIdIsRunning(xmax, true))
-						return HeapTupleBeingUpdated;
-					else
-						return HeapTupleMayBeUpdated;
-				}
-
-				/*
-				 * If the locker is gone, then there is nothing of interest
-				 * left in this Xmax; otherwise, report the tuple as
-				 * locked/updated.
-				 */
-				if (!TransactionIdIsInProgress(xmax))
-					return HeapTupleMayBeUpdated;
-				return HeapTupleBeingUpdated;
-			}
-
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleGetUpdateXid(tuple);
-
-				/* not LOCKED_ONLY, so it has to have an xmax */
-				Assert(TransactionIdIsValid(xmax));
-
-				/* deleting subtransaction must have aborted */
-				if (!TransactionIdIsCurrentTransactionId(xmax))
-				{
-					if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
-											 false))
-						return HeapTupleBeingUpdated;
-					return HeapTupleMayBeUpdated;
-				}
-				else
-				{
-					if (HeapTupleHeaderGetCmax(tuple) >= curcid)
-						return HeapTupleSelfUpdated;	/* updated after scan
-														 * started */
-					else
-						return HeapTupleInvisible;	/* updated before scan
-													 * started */
-				}
-			}
-
-			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-			{
-				/* deleting subtransaction must have aborted */
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-				return HeapTupleMayBeUpdated;
-			}
-
-			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
-				return HeapTupleSelfUpdated;	/* updated after scan started */
-			else
-				return HeapTupleInvisible;	/* updated before scan started */
-		}
-		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
-			return HeapTupleInvisible;
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return HeapTupleInvisible;
-		}
-	}
-
-	/* by here, the inserting transaction has committed */
-
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
-		return HeapTupleMayBeUpdated;
-
-	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return HeapTupleMayBeUpdated;
-		return HeapTupleUpdated;	/* updated by other */
-	}
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		if (HEAP_LOCKED_UPGRADED(tuple->t_infomask))
-			return HeapTupleMayBeUpdated;
-
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-		{
-			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), true))
-				return HeapTupleBeingUpdated;
-
-			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
-			return HeapTupleMayBeUpdated;
-		}
-
-		xmax = HeapTupleGetUpdateXid(tuple);
-		if (!TransactionIdIsValid(xmax))
-		{
-			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
-				return HeapTupleBeingUpdated;
-		}
-
-		/* not LOCKED_ONLY, so it has to have an xmax */
-		Assert(TransactionIdIsValid(xmax));
-
-		if (TransactionIdIsCurrentTransactionId(xmax))
-		{
-			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
-				return HeapTupleSelfUpdated;	/* updated after scan started */
-			else
-				return HeapTupleInvisible;	/* updated before scan started */
-		}
-
-		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
-			return HeapTupleBeingUpdated;
-
-		if (TransactionIdDidCommit(xmax))
-			return HeapTupleUpdated;
-
-		/*
-		 * By here, the update in the Xmax is either aborted or crashed, but
-		 * what about the other members?
-		 */
-
-		if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
-		{
-			/*
-			 * There's no member, even just a locker, alive anymore, so we can
-			 * mark the Xmax as invalid.
-			 */
-			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-						InvalidTransactionId);
-			return HeapTupleMayBeUpdated;
-		}
-		else
-		{
-			/* There are lockers running */
-			return HeapTupleBeingUpdated;
-		}
-	}
-
-	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return HeapTupleBeingUpdated;
-		if (HeapTupleHeaderGetCmax(tuple) >= curcid)
-			return HeapTupleSelfUpdated;	/* updated after scan started */
-		else
-			return HeapTupleInvisible;	/* updated before scan started */
-	}
-
-	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-		return HeapTupleBeingUpdated;
-
-	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		/* it must have aborted or crashed */
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return HeapTupleMayBeUpdated;
-	}
-
-	/* xmax transaction committed */
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-	{
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return HeapTupleMayBeUpdated;
-	}
-
-	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-				HeapTupleHeaderGetRawXmax(tuple));
-	return HeapTupleUpdated;	/* updated by other */
-}
-
-/*
- * HeapTupleSatisfiesDirty
- *		True iff heap tuple is valid including effects of open transactions.
- *
- *	Here, we consider the effects of:
- *		all committed and in-progress transactions (as of the current instant)
- *		previous commands of this transaction
- *		changes made by the current command
- *
- * This is essentially like HeapTupleSatisfiesSelf as far as effects of
- * the current transaction and committed/aborted xacts are concerned.
- * However, we also include the effects of other xacts still in progress.
- *
- * A special hack is that the passed-in snapshot struct is used as an
- * output argument to return the xids of concurrent xacts that affected the
- * tuple.  snapshot->xmin is set to the tuple's xmin if that is another
- * transaction that's still in progress; or to InvalidTransactionId if the
- * tuple's xmin is committed good, committed dead, or my own xact.
- * Similarly for snapshot->xmax and the tuple's xmax.  If the tuple was
- * inserted speculatively, meaning that the inserter might still back down
- * on the insertion without aborting the whole transaction, the associated
- * token is also returned in snapshot->speculativeToken.
- */
-bool
-HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
-						Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	snapshot->xmin = snapshot->xmax = InvalidTransactionId;
-	snapshot->speculativeToken = 0;
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return false;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return false;
-			if (!TransactionIdIsInProgress(xvac))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (TransactionIdIsInProgress(xvac))
-					return false;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return true;
-
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
-				return true;
-
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleGetUpdateXid(tuple);
-
-				/* not LOCKED_ONLY, so it has to have an xmax */
-				Assert(TransactionIdIsValid(xmax));
-
-				/* updating subtransaction must have aborted */
-				if (!TransactionIdIsCurrentTransactionId(xmax))
-					return true;
-				else
-					return false;
-			}
-
-			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-			{
-				/* deleting subtransaction must have aborted */
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-				return true;
-			}
-
-			return false;
-		}
-		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			/*
-			 * Return the speculative token to caller.  Caller can worry about
-			 * xmax, since it requires a conclusively locked row version, and
-			 * a concurrent update to this tuple is a conflict of its
-			 * purposes.
-			 */
-			if (HeapTupleHeaderIsSpeculative(tuple))
-			{
-				snapshot->speculativeToken =
-					HeapTupleHeaderGetSpeculativeToken(tuple);
-
-				Assert(snapshot->speculativeToken != 0);
-			}
-
-			snapshot->xmin = HeapTupleHeaderGetRawXmin(tuple);
-			/* XXX shouldn't we fall through to look at xmax? */
-			return true;		/* in insertion by other */
-		}
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return false;
-		}
-	}
-
-	/* by here, the inserting transaction has committed */
-
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
-		return true;
-
-	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-		return false;			/* updated by other */
-	}
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-
-		xmax = HeapTupleGetUpdateXid(tuple);
-
-		/* not LOCKED_ONLY, so it has to have an xmax */
-		Assert(TransactionIdIsValid(xmax));
-
-		if (TransactionIdIsCurrentTransactionId(xmax))
-			return false;
-		if (TransactionIdIsInProgress(xmax))
-		{
-			snapshot->xmax = xmax;
-			return true;
-		}
-		if (TransactionIdDidCommit(xmax))
-			return false;
-		/* it must have aborted or crashed */
-		return true;
-	}
-
-	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-		return false;
-	}
-
-	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			snapshot->xmax = HeapTupleHeaderGetRawXmax(tuple);
-		return true;
-	}
-
-	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		/* it must have aborted or crashed */
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return true;
-	}
-
-	/* xmax transaction committed */
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-	{
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return true;
-	}
-
-	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-				HeapTupleHeaderGetRawXmax(tuple));
-	return false;				/* updated by other */
-}
-
-/*
- * HeapTupleSatisfiesMVCC
- *		True iff heap tuple is valid for the given MVCC snapshot.
- *
- *	Here, we consider the effects of:
- *		all transactions committed as of the time of the given snapshot
- *		previous commands of this transaction
- *
- *	Does _not_ include:
- *		transactions shown as in-progress by the snapshot
- *		transactions started after the snapshot was taken
- *		changes made by the current command
- *
- * Notice that here, we will not update the tuple status hint bits if the
- * inserting/deleting transaction is still running according to our snapshot,
- * even if in reality it's committed or aborted by now.  This is intentional.
- * Checking the true transaction state would require access to high-traffic
- * shared data structures, creating contention we'd rather do without, and it
- * would not change the result of our visibility check anyway.  The hint bits
- * will be updated by the first visitor that has a snapshot new enough to see
- * the inserting/deleting transaction as done.  In the meantime, the cost of
- * leaving the hint bits unset is basically that each HeapTupleSatisfiesMVCC
- * call will need to run TransactionIdIsCurrentTransactionId in addition to
- * XidInMVCCSnapshot (but it would have to do the latter anyway).  In the old
- * coding where we tried to set the hint bits as soon as possible, we instead
- * did TransactionIdIsInProgress in each call --- to no avail, as long as the
- * inserting/deleting transaction was still running --- which was more cycles
- * and more contention on the PGXACT array.
- */
-bool
-HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
-					   Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return false;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return false;
-			if (!XidInMVCCSnapshot(xvac, snapshot))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (XidInMVCCSnapshot(xvac, snapshot))
-					return false;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (HeapTupleHeaderGetCmin(tuple) >= snapshot->curcid)
-				return false;	/* inserted after scan started */
-
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return true;
-
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
-				return true;
-
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleGetUpdateXid(tuple);
-
-				/* not LOCKED_ONLY, so it has to have an xmax */
-				Assert(TransactionIdIsValid(xmax));
-
-				/* updating subtransaction must have aborted */
-				if (!TransactionIdIsCurrentTransactionId(xmax))
-					return true;
-				else if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
-					return true;	/* updated after scan started */
-				else
-					return false;	/* updated before scan started */
-			}
-
-			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-			{
-				/* deleting subtransaction must have aborted */
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-				return true;
-			}
-
-			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
-				return true;	/* deleted after scan started */
-			else
-				return false;	/* deleted before scan started */
-		}
-		else if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
-			return false;
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return false;
-		}
-	}
-	else
-	{
-		/* xmin is committed, but maybe not according to our snapshot */
-		if (!HeapTupleHeaderXminFrozen(tuple) &&
-			XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
-			return false;		/* treat as still in progress */
-	}
-
-	/* by here, the inserting transaction has committed */
-
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
-		return true;
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-		return true;
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		/* already checked above */
-		Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
-
-		xmax = HeapTupleGetUpdateXid(tuple);
-
-		/* not LOCKED_ONLY, so it has to have an xmax */
-		Assert(TransactionIdIsValid(xmax));
-
-		if (TransactionIdIsCurrentTransactionId(xmax))
-		{
-			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
-				return true;	/* deleted after scan started */
-			else
-				return false;	/* deleted before scan started */
-		}
-		if (XidInMVCCSnapshot(xmax, snapshot))
-			return true;
-		if (TransactionIdDidCommit(xmax))
-			return false;		/* updating transaction committed */
-		/* it must have aborted or crashed */
-		return true;
-	}
-
-	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
-	{
-		if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-		{
-			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
-				return true;	/* deleted after scan started */
-			else
-				return false;	/* deleted before scan started */
-		}
-
-		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
-			return true;
-
-		if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-						InvalidTransactionId);
-			return true;
-		}
-
-		/* xmax transaction committed */
-		SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-					HeapTupleHeaderGetRawXmax(tuple));
-	}
-	else
-	{
-		/* xmax is committed, but maybe not according to our snapshot */
-		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
-			return true;		/* treat as still in progress */
-	}
-
-	/* xmax transaction committed */
-
-	return false;
-}
-
-
-/*
- * HeapTupleSatisfiesVacuum
- *
- *	Determine the status of tuples for VACUUM purposes.  Here, what
- *	we mainly want to know is if a tuple is potentially visible to *any*
- *	running transaction.  If so, it can't be removed yet by VACUUM.
- *
- * OldestXmin is a cutoff XID (obtained from GetOldestXmin()).  Tuples
- * deleted by XIDs >= OldestXmin are deemed "recently dead"; they might
- * still be visible to some open transaction, so we can't remove them,
- * even if we see that the deleting transaction has committed.
- */
-HTSV_Result
-HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
-						 Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	/*
-	 * Has inserting transaction committed?
-	 *
-	 * If the inserting transaction aborted, then the tuple was never visible
-	 * to any other transaction, so we can delete it immediately.
-	 */
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return HEAPTUPLE_DEAD;
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return HEAPTUPLE_DELETE_IN_PROGRESS;
-			if (TransactionIdIsInProgress(xvac))
-				return HEAPTUPLE_DELETE_IN_PROGRESS;
-			if (TransactionIdDidCommit(xvac))
-			{
-				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-							InvalidTransactionId);
-				return HEAPTUPLE_DEAD;
-			}
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						InvalidTransactionId);
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return HEAPTUPLE_INSERT_IN_PROGRESS;
-			if (TransactionIdIsInProgress(xvac))
-				return HEAPTUPLE_INSERT_IN_PROGRESS;
-			if (TransactionIdDidCommit(xvac))
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			else
-			{
-				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-							InvalidTransactionId);
-				return HEAPTUPLE_DEAD;
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return HEAPTUPLE_INSERT_IN_PROGRESS;
-			/* only locked? run infomask-only check first, for performance */
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask) ||
-				HeapTupleHeaderIsOnlyLocked(tuple))
-				return HEAPTUPLE_INSERT_IN_PROGRESS;
-			/* inserted and then deleted by same xact */
-			if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple)))
-				return HEAPTUPLE_DELETE_IN_PROGRESS;
-			/* deleting subtransaction must have aborted */
-			return HEAPTUPLE_INSERT_IN_PROGRESS;
-		}
-		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			/*
-			 * It'd be possible to discern between INSERT/DELETE in progress
-			 * here by looking at xmax - but that doesn't seem beneficial for
-			 * the majority of callers and even detrimental for some. We'd
-			 * rather have callers look at/wait for xmin than xmax. It's
-			 * always correct to return INSERT_IN_PROGRESS because that's
-			 * what's happening from the view of other backends.
-			 */
-			return HEAPTUPLE_INSERT_IN_PROGRESS;
-		}
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/*
-			 * Not in Progress, Not Committed, so either Aborted or crashed
-			 */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return HEAPTUPLE_DEAD;
-		}
-
-		/*
-		 * At this point the xmin is known committed, but we might not have
-		 * been able to set the hint bit yet; so we can no longer Assert that
-		 * it's set.
-		 */
-	}
-
-	/*
-	 * Okay, the inserter committed, so it was good at some point.  Now what
-	 * about the deleting transaction?
-	 */
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)
-		return HEAPTUPLE_LIVE;
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-	{
-		/*
-		 * "Deleting" xact really only locked it, so the tuple is live in any
-		 * case.  However, we should make sure that either XMAX_COMMITTED or
-		 * XMAX_INVALID gets set once the xact is gone, to reduce the costs of
-		 * examining the tuple for future xacts.
-		 */
-		if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
-		{
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				/*
-				 * If it's a pre-pg_upgrade tuple, the multixact cannot
-				 * possibly be running; otherwise have to check.
-				 */
-				if (!HEAP_LOCKED_UPGRADED(tuple->t_infomask) &&
-					MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
-										 true))
-					return HEAPTUPLE_LIVE;
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
-			}
-			else
-			{
-				if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-					return HEAPTUPLE_LIVE;
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-			}
-		}
-
-		/*
-		 * We don't really care whether xmax did commit, abort or crash. We
-		 * know that xmax did lock the tuple, but it did not and will never
-		 * actually update it.
-		 */
-
-		return HEAPTUPLE_LIVE;
-	}
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
-		{
-			/* already checked above */
-			Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
-
-			xmax = HeapTupleGetUpdateXid(tuple);
-
-			/* not LOCKED_ONLY, so it has to have an xmax */
-			Assert(TransactionIdIsValid(xmax));
-
-			if (TransactionIdIsInProgress(xmax))
-				return HEAPTUPLE_DELETE_IN_PROGRESS;
-			else if (TransactionIdDidCommit(xmax))
-				/* there are still lockers around -- can't return DEAD here */
-				return HEAPTUPLE_RECENTLY_DEAD;
-			/* updating transaction aborted */
-			return HEAPTUPLE_LIVE;
-		}
-
-		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED));
-
-		xmax = HeapTupleGetUpdateXid(tuple);
-
-		/* not LOCKED_ONLY, so it has to have an xmax */
-		Assert(TransactionIdIsValid(xmax));
-
-		/* multi is not running -- updating xact cannot be */
-		Assert(!TransactionIdIsInProgress(xmax));
-		if (TransactionIdDidCommit(xmax))
-		{
-			if (!TransactionIdPrecedes(xmax, OldestXmin))
-				return HEAPTUPLE_RECENTLY_DEAD;
-			else
-				return HEAPTUPLE_DEAD;
-		}
-
-		/*
-		 * Not in Progress, Not Committed, so either Aborted or crashed.
-		 * Remove the Xmax.
-		 */
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
-		return HEAPTUPLE_LIVE;
-	}
-
-	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
-	{
-		if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-			return HEAPTUPLE_DELETE_IN_PROGRESS;
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-						HeapTupleHeaderGetRawXmax(tuple));
-		else
-		{
-			/*
-			 * Not in Progress, Not Committed, so either Aborted or crashed
-			 */
-			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-						InvalidTransactionId);
-			return HEAPTUPLE_LIVE;
-		}
-
-		/*
-		 * At this point the xmax is known committed, but we might not have
-		 * been able to set the hint bit yet; so we can no longer Assert that
-		 * it's set.
-		 */
-	}
-
-	/*
-	 * Deleter committed, but perhaps it was recent enough that some open
-	 * transactions could still see the tuple.
-	 */
-	if (!TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin))
-		return HEAPTUPLE_RECENTLY_DEAD;
-
-	/* Otherwise, it's dead and removable */
-	return HEAPTUPLE_DEAD;
-}
-
-
-/*
- * HeapTupleSatisfiesNonVacuumable
- *
- *	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.
- */
-bool
-HeapTupleSatisfiesNonVacuumable(HeapTuple 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 for known-committed
- * XIDs which could not be ours anyway.
- */
-static 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'.
- */
-static bool
-TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num)
-{
-	return bsearch(&xid, xip, num,
-				   sizeof(TransactionId), xidComparator) != NULL;
-}
-
-/*
- * See the comments for HeapTupleSatisfiesMVCC for the semantics this function
- * obeys.
- *
- * Only usable on tuples from catalog tables!
- *
- * We don't need to support HEAP_MOVED_(IN|OFF) for now because we only support
- * reading catalog pages which couldn't have been created in an older version.
- *
- * We don't set any hint bits in here as it seems unlikely to be beneficial as
- * those should already be set by normal access and it seems to be too
- * 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,
-							   Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-	TransactionId xmin = HeapTupleHeaderGetXmin(tuple);
-	TransactionId xmax = HeapTupleHeaderGetRawXmax(tuple);
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	/* inserting transaction aborted */
-	if (HeapTupleHeaderXminInvalid(tuple))
-	{
-		Assert(!TransactionIdDidCommit(xmin));
-		return false;
-	}
-	/* check if it's one of our txids, toplevel is also in there */
-	else if (TransactionIdInArray(xmin, snapshot->subxip, snapshot->subxcnt))
-	{
-		bool		resolved;
-		CommandId	cmin = HeapTupleHeaderGetRawCommandId(tuple);
-		CommandId	cmax = InvalidCommandId;
-
-		/*
-		 * another transaction might have (tried to) delete this tuple or
-		 * cmin/cmax was stored in a combocid. So we need to lookup the actual
-		 * values externally.
-		 */
-		resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot,
-												 htup, buffer,
-												 &cmin, &cmax);
-
-		if (!resolved)
-			elog(ERROR, "could not resolve cmin/cmax of catalog tuple");
-
-		Assert(cmin != InvalidCommandId);
-
-		if (cmin >= snapshot->curcid)
-			return false;		/* inserted after scan started */
-		/* fall through */
-	}
-	/* committed before our xmin horizon. Do a normal visibility check. */
-	else if (TransactionIdPrecedes(xmin, snapshot->xmin))
-	{
-		Assert(!(HeapTupleHeaderXminCommitted(tuple) &&
-				 !TransactionIdDidCommit(xmin)));
-
-		/* check for hint bit first, consult clog afterwards */
-		if (!HeapTupleHeaderXminCommitted(tuple) &&
-			!TransactionIdDidCommit(xmin))
-			return false;
-		/* fall through */
-	}
-	/* beyond our xmax horizon, i.e. invisible */
-	else if (TransactionIdFollowsOrEquals(xmin, snapshot->xmax))
-	{
-		return false;
-	}
-	/* check if it's a committed transaction in [xmin, xmax) */
-	else if (TransactionIdInArray(xmin, snapshot->xip, snapshot->xcnt))
-	{
-		/* fall through */
-	}
-
-	/*
-	 * none of the above, i.e. between [xmin, xmax) but hasn't committed. I.e.
-	 * invisible.
-	 */
-	else
-	{
-		return false;
-	}
-
-	/* at this point we know xmin is visible, go on to check xmax */
-
-	/* xid invalid or aborted */
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)
-		return true;
-	/* locked tuples are always visible */
-	else if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-		return true;
-
-	/*
-	 * We can see multis here if we're looking at user tables or if somebody
-	 * SELECT ... FOR SHARE/UPDATE a system table.
-	 */
-	else if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		xmax = HeapTupleGetUpdateXid(tuple);
-	}
-
-	/* check if it's one of our txids, toplevel is also in there */
-	if (TransactionIdInArray(xmax, snapshot->subxip, snapshot->subxcnt))
-	{
-		bool		resolved;
-		CommandId	cmin;
-		CommandId	cmax = HeapTupleHeaderGetRawCommandId(tuple);
-
-		/* Lookup actual cmin/cmax values */
-		resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot,
-												 htup, buffer,
-												 &cmin, &cmax);
-
-		if (!resolved)
-			elog(ERROR, "could not resolve combocid to cmax");
-
-		Assert(cmax != InvalidCommandId);
-
-		if (cmax >= snapshot->curcid)
-			return true;		/* deleted after scan started */
-		else
-			return false;		/* deleted before scan started */
-	}
-	/* below xmin horizon, normal transaction state is valid */
-	else if (TransactionIdPrecedes(xmax, snapshot->xmin))
-	{
-		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED &&
-				 !TransactionIdDidCommit(xmax)));
-
-		/* check hint bit first */
-		if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
-			return false;
-
-		/* check clog */
-		return !TransactionIdDidCommit(xmax);
-	}
-	/* above xmax horizon, we cannot possibly see the deleting transaction */
-	else if (TransactionIdFollowsOrEquals(xmax, snapshot->xmax))
-		return true;
-	/* xmax is between [xmin, xmax), check known committed array */
-	else if (TransactionIdInArray(xmax, snapshot->xip, snapshot->xcnt))
-		return false;
-	/* xmax is between [xmin, xmax), but known not to have committed yet */
-	else
-		return true;
-}
diff --git a/src/include/access/heapam_common.h b/src/include/access/heapam_common.h
new file mode 100644
index 0000000..ff63cf3
--- /dev/null
+++ b/src/include/access/heapam_common.h
@@ -0,0 +1,96 @@
+/*-------------------------------------------------------------------------
+ *
+ * heapam_shared.h
+ *	  POSTGRES heap access method definitions shared across
+ *	  server and heapam methods.
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/heapam_shared.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef HEAPAM_SHARED_H
+#define HEAPAM_SHARED_H
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/sdir.h"
+#include "access/skey.h"
+#include "access/transam.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "nodes/lockoptions.h"
+#include "nodes/primnodes.h"
+#include "storage/bufpage.h"
+#include "storage/bufmgr.h"
+#include "storage/lockdefs.h"
+#include "storage/lmgr.h"
+#include "utils/relcache.h"
+#include "utils/snapshot.h"
+
+
+/* in heap/heapam_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);
+
+/*
+ * 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							/* HEAPAM_H */
diff --git a/src/include/access/htup.h b/src/include/access/htup.h
index 6459435..5b0068a 100644
--- a/src/include/access/htup.h
+++ b/src/include/access/htup.h
@@ -29,11 +29,12 @@ typedef MinimalTupleData *MinimalTuple;
 typedef enum tuple_visibility_type
 {
 	MVCC_VISIBILITY = 0, 		/* HeapTupleSatisfiesMVCC */
-	SELF_VISIBILITY,				/* HeapTupleSatisfiesSelf */
+	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;
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 98b63fc..b8b823b 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -14,13 +14,13 @@
 #ifndef BUFMGR_H
 #define BUFMGR_H
 
+#include "access/storageamapi.h"
 #include "storage/block.h"
 #include "storage/buf.h"
 #include "storage/bufpage.h"
 #include "storage/relfilenode.h"
 #include "utils/relcache.h"
 #include "utils/snapmgr.h"
-#include "utils/tqual.h"
 
 typedef void *Block;
 
@@ -268,8 +268,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 bf51977..5752ee4 100644
--- a/src/include/utils/snapshot.h
+++ b/src/include/utils/snapshot.h
@@ -52,7 +52,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 de75e01..67a8b1e 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,48 +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))
-
-
-/* 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 void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
-					 uint16 infomask, TransactionId xid);
-extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
-
-/*
- * To avoid leaking too much knowledge about reorderbuffer implementation
- * details this is implemented in reorderbuffer.c not tqual.c.
- */
-struct HTAB;
-extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data,
-							  Snapshot snapshot,
-							  HeapTuple htup,
-							  Buffer buffer,
-							  CommandId *cmin, CommandId *cmax);
+#define HeapTupleSatisfiesVisibility(method, tuple, snapshot, buffer) \
+       (((method)->snapshot_satisfies[(snapshot)->visibility_type]) (tuple, snapshot, buffer))
 
 /*
  * We don't provide a static SnapshotDirty variable because it would be
@@ -91,14 +52,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))
 
 /*
@@ -106,8 +67,19 @@ 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))
 
+/*
+ * To avoid leaking too much knowledge about reorderbuffer implementation
+ * details this is implemented in reorderbuffer.c not tqual.c.
+ */
+struct HTAB;
+extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data,
+                                                         Snapshot snapshot,
+                                                         HeapTuple htup,
+                                                         Buffer buffer,
+                                                         CommandId *cmin, CommandId *cmax);
+
 #endif							/* TQUAL_H */
-- 
2.7.4.windows.1

0005-slot-hooks-are-added-to-storage-AM.patchapplication/octet-stream; name=0005-slot-hooks-are-added-to-storage-AM.patchDownload
From 881ab5f4043027496374d0d6f17bf400250dd3f1 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Wed, 30 Aug 2017 17:24:35 +1000
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_common.c  | 404 +++++++++++++++++++++++++++++++
 src/backend/access/heap/heapam_storage.c |   1 +
 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    |  26 +-
 src/backend/executor/execReplication.c   |  92 ++-----
 src/backend/executor/execTuples.c        | 269 +++++++++-----------
 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/heapam_common.h       |   2 +
 src/include/access/htup_details.h        |  15 +-
 src/include/executor/tuptable.h          |  54 +++--
 17 files changed, 644 insertions(+), 575 deletions(-)

diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 13ee528..5ed0f15 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"
@@ -1022,111 +1023,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.
  *		It is functionally equivalent to heap_getattr, but fetches of
@@ -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_common.c b/src/backend/access/heap/heapam_common.c
index 502f6db..6cb6c5b 100644
--- a/src/backend/access/heap/heapam_common.c
+++ b/src/backend/access/heap/heapam_common.c
@@ -159,4 +159,408 @@ HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin)
 	return TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin);
 }
 
+/*-----------------------
+ *
+ * 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;
+}
 
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 1bd4bfa..7d7ac75 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -1665,6 +1665,7 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 {
 	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
 
+	amroutine->slot_storageam = heapam_storage_slot_handler;
 
 	amroutine->snapshot_satisfies[MVCC_VISIBILITY] = HeapTupleSatisfiesMVCC;
 	amroutine->snapshot_satisfies[SELF_VISIBILITY] = HeapTupleSatisfiesSelf;
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index cfa3f05..8456bfd 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2695,7 +2695,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 e60210c..a0ec444 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 d2e0376..b440740 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 269c9e1..f891cd1 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2348,7 +2348,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;
@@ -2429,7 +2429,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;
@@ -2777,7 +2777,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;
@@ -2819,7 +2819,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 	if (newSlot != NULL)
 	{
 		slot = ExecFilterJunk(relinfo->ri_junkFilter, newSlot);
-		slottuple = ExecMaterializeSlot(slot);
+		slottuple = ExecHeapifySlot(slot);
 		newtuple = slottuple;
 	}
 
@@ -2928,7 +2928,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;
@@ -3960,14 +3960,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 bd8a15d..4775738 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -503,12 +503,12 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			int			attnum = op->d.var.attnum;
 
 			/* 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 */
 
-			*op->resvalue = heap_getsysattr(innerslot->tts_tuple, attnum,
-											innerslot->tts_tupleDescriptor,
+			*op->resvalue = slot_getattr(innerslot,
+											attnum,
 											op->resnull);
 
 			EEO_NEXT();
@@ -519,12 +519,12 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			int			attnum = op->d.var.attnum;
 
 			/* 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 */
-			*op->resvalue = heap_getsysattr(outerslot->tts_tuple, attnum,
-											outerslot->tts_tupleDescriptor,
+
+			*op->resvalue = slot_getattr(outerslot,
+											attnum,
 											op->resnull);
 
 			EEO_NEXT();
@@ -535,12 +535,12 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			int			attnum = op->d.var.attnum;
 
 			/* 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 */
 
-			*op->resvalue = heap_getsysattr(scanslot->tts_tuple, attnum,
-											scanslot->tts_tupleDescriptor,
+			*op->resvalue = slot_getattr(scanslot,
+											attnum,
 											op->resnull);
 
 			EEO_NEXT();
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 5a75e02..6700f0a 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,7 +226,8 @@ bool
 RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 						 TupleTableSlot *searchslot, TupleTableSlot *outslot)
 {
-	HeapTuple	scantuple;
+    TupleTableSlot *scanslot;
+    HeapTuple	scantuple;
 	HeapScanDesc scan;
 	SnapshotData snap;
 	TransactionId xwait;
@@ -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_update_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 51d2c5d..b7a2cbc 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -81,6 +81,7 @@
  */
 #include "postgres.h"
 
+#include "access/heapam_common.h"
 #include "access/htup_details.h"
 #include "access/tuptoaster.h"
 #include "funcapi.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;
 }
@@ -206,6 +206,54 @@ MakeSingleTupleTableSlot(TupleDesc tupdesc)
 }
 
 /* --------------------------------
+ *		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
  *
  *		Release a TupleTableSlot made with MakeSingleTupleTableSlot.
@@ -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,18 @@ 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 +459,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 +540,7 @@ ExecStoreAllNullTuple(TupleTableSlot *slot)
  *		however the "system columns" of the result will not be meaningful.
  * --------------------------------
  */
-HeapTuple
+StorageTuple
 ExecCopySlotTuple(TupleTableSlot *slot)
 {
 	/*
@@ -550,20 +549,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 +568,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 +598,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 +645,7 @@ MinimalTuple
 ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 {
 	MemoryContext oldContext;
+	MinimalTuple tup;
 
 	/*
 	 * sanity checks
@@ -659,11 +653,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 +664,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 +697,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 +717,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,19 +728,11 @@ 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
 	 * mustn't leave any dangling pass-by-reference datums in tts_values.
@@ -768,17 +741,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 20892d6..02f6c81 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 d1afda4..3b23df7 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -171,7 +171,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;
@@ -271,7 +271,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
@@ -403,7 +403,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -416,7 +416,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		newId = InvalidOid;
 	}
@@ -434,7 +434,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
@@ -743,7 +743,7 @@ ExecDelete(ModifyTableState *mtstate,
 		 */
 		if (slot->tts_isempty)
 			ExecStoreAllNullTuple(slot);
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
 	}
 	else
@@ -894,7 +894,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))
@@ -955,7 +955,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
@@ -974,7 +974,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW UPDATE Triggers */
@@ -988,7 +988,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
@@ -1004,7 +1004,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
@@ -1120,7 +1120,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 6afcd1a..81964d7 100644
--- a/src/backend/executor/tqueue.c
+++ b/src/backend/executor/tqueue.c
@@ -305,7 +305,7 @@ tqueueReceiveSlot(TupleTableSlot *slot, DestReceiver *self)
 	}
 
 	/* 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 bc6d824..a60be00 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -724,9 +724,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/heapam_common.h b/src/include/access/heapam_common.h
index ff63cf3..1fe15ed 100644
--- a/src/include/access/heapam_common.h
+++ b/src/include/access/heapam_common.h
@@ -40,6 +40,8 @@ extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
 					 uint16 infomask, TransactionId xid);
 extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
 extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin);
+typedef struct StorageSlotAmRoutine StorageSlotAmRoutine;
+extern StorageSlotAmRoutine* heapam_storage_slot_handler(void);
 
 /*
  * SetHintBits()
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index fa04a63..9539d67 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -21,6 +21,19 @@
 #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
  * a tuple, plus the size of the null-values bitmap (at 1 bit per column),
@@ -653,7 +666,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/executor/tuptable.h b/src/include/executor/tuptable.h
index 55f4cce..84dc293 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.7.4.windows.1

0006-Tuple-Insert-API-is-added-to-Storage-AM.patchapplication/octet-stream; name=0006-Tuple-Insert-API-is-added-to-Storage-AM.patchDownload
From d445b3b28a5b2102110d1344abba57954568a326 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Thu, 7 Sep 2017 14:48:24 +1000
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.

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/Makefile         |    2 +-
 src/backend/access/heap/heapam.c         | 2737 ++++--------------------------
 src/backend/access/heap/heapam_storage.c | 2153 ++++++++++++++++++++++-
 src/backend/access/heap/rewriteheap.c    |    5 +-
 src/backend/access/heap/storageam.c      |  306 ++++
 src/backend/access/heap/tuptoaster.c     |    8 +-
 src/backend/commands/copy.c              |   29 +-
 src/backend/commands/createas.c          |   18 +-
 src/backend/commands/matview.c           |   10 +-
 src/backend/commands/tablecmds.c         |    5 +-
 src/backend/commands/trigger.c           |   43 +-
 src/backend/executor/execMain.c          |  120 +-
 src/backend/executor/execReplication.c   |   35 +-
 src/backend/executor/nodeLockRows.c      |   39 +-
 src/backend/executor/nodeModifyTable.c   |  177 +-
 src/backend/executor/nodeTidscan.c       |   23 +-
 src/backend/utils/adt/tid.c              |    5 +-
 src/include/access/heapam.h              |   26 +-
 src/include/access/heapam_common.h       |  127 ++
 src/include/access/htup_details.h        |    1 +
 src/include/access/storageam.h           |   81 +
 src/include/access/storageamapi.h        |   21 +-
 src/include/commands/trigger.h           |    2 +-
 src/include/executor/executor.h          |    6 +-
 src/include/nodes/execnodes.h            |    4 +-
 26 files changed, 3290 insertions(+), 2717 deletions(-)
 create mode 100644 src/backend/access/heap/storageam.c
 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 5ed0f15..714c4d8 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -686,6 +686,30 @@ heap_copy_tuple_as_datum(HeapTuple tuple, TupleDesc tupleDesc)
 }
 
 /*
+ * 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,
  *		which are of the length indicated by tupleDescriptor->natts
diff --git a/src/backend/access/heap/Makefile b/src/backend/access/heap/Makefile
index e6bc18e..162736f 100644
--- a/src/backend/access/heap/Makefile
+++ b/src/backend/access/heap/Makefile
@@ -13,7 +13,7 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = heapam.o heapam_common.o heapam_storage.o hio.o \
-	pruneheap.o rewriteheap.o storageamapi.o \
+	pruneheap.o rewriteheap.o storageam.o storageamapi.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 c21d6f8..d20f211 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -94,8 +94,6 @@ static HeapScanDesc heap_beginscan_internal(Relation relation,
 						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,
-					TransactionId xid, CommandId cid, int options);
 static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 				Buffer newbuf, HeapTuple oldtup,
 				HeapTuple newtup, HeapTuple old_key_tup,
@@ -103,108 +101,17 @@ static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 static Bitmapset *HeapDetermineModifiedColumns(Relation relation,
 							 Bitmapset *interesting_cols,
 							 HeapTuple oldtup, HeapTuple newtup);
-static bool heap_acquire_tuplock(Relation relation, ItemPointer tid,
-					 LockTupleMode mode, LockWaitPolicy wait_policy,
-					 bool *have_tuple_lock);
-static void compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
-						  uint16 old_infomask2, TransactionId add_to_xmax,
-						  LockTupleMode mode, bool is_update,
-						  TransactionId *result_xmax, uint16 *result_infomask,
-						  uint16 *result_infomask2);
-static HTSU_Result heap_lock_updated_tuple(Relation rel, HeapTuple tuple,
-						ItemPointer ctid, TransactionId xid,
-						LockTupleMode mode);
 static void GetMultiXactIdHintBits(MultiXactId multi, uint16 *new_infomask,
 					   uint16 *new_infomask2);
 static TransactionId MultiXactIdGetUpdateXid(TransactionId xmax,
 						uint16 t_infomask);
-static bool DoesMultiXactIdConflict(MultiXactId multi, uint16 infomask,
-						LockTupleMode lockmode);
-static void MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
-				Relation rel, ItemPointer ctid, XLTW_Oper oper,
-				int *remaining);
-static bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
-						   uint16 infomask, Relation rel, int *remaining);
-static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
+static bool Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
+				   uint16 infomask, bool nowait,
+				   Relation rel, ItemPointer ctid, XLTW_Oper oper,
+				   int *remaining);
 static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_modified,
 					   bool *copy);
 
-
-/*
- * Each tuple lock mode has a corresponding heavyweight lock, and one or two
- * corresponding MultiXactStatuses (one to merely lock tuples, another one to
- * update them).  This table (and the macros below) helps us determine the
- * heavyweight lock mode and MultiXactStatus values to use for any particular
- * tuple lock strength.
- *
- * Don't look at lockstatus/updstatus directly!  Use get_mxact_status_for_lock
- * instead.
- */
-static const struct
-{
-	LOCKMODE	hwlock;
-	int			lockstatus;
-	int			updstatus;
-}
-
-			tupleLockExtraInfo[MaxLockTupleMode + 1] =
-{
-	{							/* LockTupleKeyShare */
-		AccessShareLock,
-		MultiXactStatusForKeyShare,
-		-1						/* KeyShare does not allow updating tuples */
-	},
-	{							/* LockTupleShare */
-		RowShareLock,
-		MultiXactStatusForShare,
-		-1						/* Share does not allow updating tuples */
-	},
-	{							/* LockTupleNoKeyExclusive */
-		ExclusiveLock,
-		MultiXactStatusForNoKeyUpdate,
-		MultiXactStatusNoKeyUpdate
-	},
-	{							/* LockTupleExclusive */
-		AccessExclusiveLock,
-		MultiXactStatusForUpdate,
-		MultiXactStatusUpdate
-	}
-};
-
-/* Get the LOCKMODE for a given MultiXactStatus */
-#define LOCKMODE_from_mxstatus(status) \
-			(tupleLockExtraInfo[TUPLOCK_from_mxstatus((status))].hwlock)
-
-/*
- * Acquire heavyweight locks on tuples, using a LockTupleMode strength value.
- * This is more readable than having every caller translate it to lock.h's
- * LOCKMODE.
- */
-#define LockTupleTuplock(rel, tup, mode) \
-	LockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
-#define UnlockTupleTuplock(rel, tup, mode) \
-	UnlockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
-#define ConditionalLockTupleTuplock(rel, tup, mode) \
-	ConditionalLockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
-
-/*
- * This table maps tuple lock strength values for each particular
- * MultiXactStatus value.
- */
-static const int MultiXactStatusLock[MaxMultiXactStatus + 1] =
-{
-	LockTupleKeyShare,			/* ForKeyShare */
-	LockTupleShare,				/* ForShare */
-	LockTupleNoKeyExclusive,	/* ForNoKeyUpdate */
-	LockTupleExclusive,			/* ForUpdate */
-	LockTupleNoKeyExclusive,	/* NoKeyUpdate */
-	LockTupleExclusive			/* Update */
-};
-
-/* Get the LockTupleMode for a given MultiXactStatus */
-#define TUPLOCK_from_mxstatus(status) \
-			(MultiXactStatusLock[(status)])
-
 /* ----------------------------------------------------------------
  *						 heap support routines
  * ----------------------------------------------------------------
@@ -1838,158 +1745,6 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 }
 
 /*
- *	heap_fetch		- retrieve tuple with given tid
- *
- * On entry, tuple->t_self is the TID to fetch.  We pin the buffer holding
- * the tuple, fill in the remaining fields of *tuple, and check the tuple
- * against the specified snapshot.
- *
- * If successful (tuple found and passes snapshot time qual), then *userbuf
- * is set to the buffer holding the tuple and TRUE is returned.  The caller
- * must unpin the buffer when done with the tuple.
- *
- * If the tuple is not found (ie, item number references a deleted slot),
- * then tuple->t_data is set to NULL and FALSE is returned.
- *
- * If the tuple is found but fails the time qual check, then FALSE is returned
- * but tuple->t_data is left pointing to the tuple.
- *
- * keep_buf determines what is done with the buffer in the FALSE-result cases.
- * When the caller specifies keep_buf = true, we retain the pin on the buffer
- * and return it in *userbuf (so the caller must eventually unpin it); when
- * keep_buf = false, the pin is released and *userbuf is set to InvalidBuffer.
- *
- * stats_relation is the relation to charge the heap_fetch operation against
- * for statistical purposes.  (This could be the heap rel itself, an
- * associated index, or NULL to not count the fetch at all.)
- *
- * heap_fetch does not follow HOT chains: only the exact TID requested will
- * be fetched.
- *
- * It is somewhat inconsistent that we ereport() on invalid block number but
- * return false on invalid item number.  There are a couple of reasons though.
- * One is that the caller can relatively easily check the block number for
- * validity, but cannot check the item number without reading the page
- * himself.  Another is that when we are following a t_ctid link, we can be
- * reasonably confident that the page number is valid (since VACUUM shouldn't
- * truncate off the destination page without having killed the referencing
- * tuple first), but the item number might well not be good.
- */
-bool
-heap_fetch(Relation relation,
-		   Snapshot snapshot,
-		   HeapTuple tuple,
-		   Buffer *userbuf,
-		   bool keep_buf,
-		   Relation stats_relation)
-{
-	ItemPointer tid = &(tuple->t_self);
-	ItemId		lp;
-	Buffer		buffer;
-	Page		page;
-	OffsetNumber offnum;
-	bool		valid;
-
-	/*
-	 * Fetch and pin the appropriate page of the relation.
-	 */
-	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
-
-	/*
-	 * Need share lock on buffer to examine tuple commit status.
-	 */
-	LockBuffer(buffer, BUFFER_LOCK_SHARE);
-	page = BufferGetPage(buffer);
-	TestForOldSnapshot(snapshot, relation, page);
-
-	/*
-	 * We'd better check for out-of-range offnum in case of VACUUM since the
-	 * TID was obtained.
-	 */
-	offnum = ItemPointerGetOffsetNumber(tid);
-	if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
-	{
-		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-		if (keep_buf)
-			*userbuf = buffer;
-		else
-		{
-			ReleaseBuffer(buffer);
-			*userbuf = InvalidBuffer;
-		}
-		tuple->t_data = NULL;
-		return false;
-	}
-
-	/*
-	 * get the item line pointer corresponding to the requested tid
-	 */
-	lp = PageGetItemId(page, offnum);
-
-	/*
-	 * Must check for deleted tuple.
-	 */
-	if (!ItemIdIsNormal(lp))
-	{
-		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-		if (keep_buf)
-			*userbuf = buffer;
-		else
-		{
-			ReleaseBuffer(buffer);
-			*userbuf = InvalidBuffer;
-		}
-		tuple->t_data = NULL;
-		return false;
-	}
-
-	/*
-	 * fill in *tuple fields
-	 */
-	tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	tuple->t_len = ItemIdGetLength(lp);
-	tuple->t_tableOid = RelationGetRelid(relation);
-
-	/*
-	 * check time qualification of tuple, then release lock
-	 */
-	valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, tuple, snapshot, buffer);
-
-	if (valid)
-		PredicateLockTuple(relation, tuple, snapshot);
-
-	CheckForSerializableConflictOut(valid, relation, tuple, buffer, snapshot);
-
-	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-
-	if (valid)
-	{
-		/*
-		 * All checks passed, so return the tuple as valid. Caller is now
-		 * responsible for releasing the buffer.
-		 */
-		*userbuf = buffer;
-
-		/* Count the successful fetch against appropriate rel, if any */
-		if (stats_relation != NULL)
-			pgstat_count_heap_fetch(stats_relation);
-
-		return true;
-	}
-
-	/* Tuple failed time qual, but maybe caller wants to see it anyway. */
-	if (keep_buf)
-		*userbuf = buffer;
-	else
-	{
-		ReleaseBuffer(buffer);
-		*userbuf = InvalidBuffer;
-	}
-
-	return false;
-}
-
-/*
  *	heap_hot_search_buffer	- search HOT chain for tuple satisfying snapshot
  *
  * On entry, *tid is the TID of a tuple (either a simple tuple, or the root
@@ -2172,130 +1927,6 @@ heap_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot,
 	return result;
 }
 
-/*
- *	heap_get_latest_tid -  get the latest tid of a specified tuple
- *
- * Actually, this gets the latest version that is visible according to
- * the passed snapshot.  You can pass SnapshotDirty to get the very latest,
- * possibly uncommitted version.
- *
- * *tid is both an input and an output parameter: it is updated to
- * show the latest version of the row.  Note that it will not be changed
- * if no version of the row passes the snapshot test.
- */
-void
-heap_get_latest_tid(Relation relation,
-					Snapshot snapshot,
-					ItemPointer tid)
-{
-	BlockNumber blk;
-	ItemPointerData ctid;
-	TransactionId priorXmax;
-
-	/* this is to avoid Assert failures on bad input */
-	if (!ItemPointerIsValid(tid))
-		return;
-
-	/*
-	 * Since this can be called with user-supplied TID, don't trust the input
-	 * too much.  (RelationGetNumberOfBlocks is an expensive check, so we
-	 * don't check t_ctid links again this way.  Note that it would not do to
-	 * call it just once and save the result, either.)
-	 */
-	blk = ItemPointerGetBlockNumber(tid);
-	if (blk >= RelationGetNumberOfBlocks(relation))
-		elog(ERROR, "block number %u is out of range for relation \"%s\"",
-			 blk, RelationGetRelationName(relation));
-
-	/*
-	 * Loop to chase down t_ctid links.  At top of loop, ctid is the tuple we
-	 * need to examine, and *tid is the TID we will return if ctid turns out
-	 * to be bogus.
-	 *
-	 * Note that we will loop until we reach the end of the t_ctid chain.
-	 * Depending on the snapshot passed, there might be at most one visible
-	 * version of the row, but we don't try to optimize for that.
-	 */
-	ctid = *tid;
-	priorXmax = InvalidTransactionId;	/* cannot check first XMIN */
-	for (;;)
-	{
-		Buffer		buffer;
-		Page		page;
-		OffsetNumber offnum;
-		ItemId		lp;
-		HeapTupleData tp;
-		bool		valid;
-
-		/*
-		 * Read, pin, and lock the page.
-		 */
-		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&ctid));
-		LockBuffer(buffer, BUFFER_LOCK_SHARE);
-		page = BufferGetPage(buffer);
-		TestForOldSnapshot(snapshot, relation, page);
-
-		/*
-		 * Check for bogus item number.  This is not treated as an error
-		 * condition because it can happen while following a t_ctid link. We
-		 * just assume that the prior tid is OK and return it unchanged.
-		 */
-		offnum = ItemPointerGetOffsetNumber(&ctid);
-		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-		lp = PageGetItemId(page, offnum);
-		if (!ItemIdIsNormal(lp))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-
-		/* OK to access the tuple */
-		tp.t_self = ctid;
-		tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-		tp.t_len = ItemIdGetLength(lp);
-		tp.t_tableOid = RelationGetRelid(relation);
-
-		/*
-		 * After following a t_ctid link, we might arrive at an unrelated
-		 * tuple.  Check for XMIN match.
-		 */
-		if (TransactionIdIsValid(priorXmax) &&
-			!TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(tp.t_data)))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-
-		/*
-		 * Check time qualification of tuple; if visible, set it as the new
-		 * result candidate.
-		 */
-		valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, &tp, snapshot, buffer);
-		CheckForSerializableConflictOut(valid, relation, &tp, buffer, snapshot);
-		if (valid)
-			*tid = ctid;
-
-		/*
-		 * If there's a valid t_ctid link, follow it, else we're done.
-		 */
-		if ((tp.t_data->t_infomask & HEAP_XMAX_INVALID) ||
-			HeapTupleHeaderIsOnlyLocked(tp.t_data) ||
-			ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-
-		ctid = tp.t_data->t_ctid;
-		priorXmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
-		UnlockReleaseBuffer(buffer);
-	}							/* end of loop */
-}
-
 
 /*
  * UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends
@@ -2313,7 +1944,7 @@ heap_get_latest_tid(Relation relation,
  *
  * Note this is not allowed for tuples whose xmax is a multixact.
  */
-static void
+void
 UpdateXmaxHintBits(HeapTupleHeader tuple, Buffer buffer, TransactionId xid)
 {
 	Assert(TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple), xid));
@@ -2596,7 +2227,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
  * tuple if not. Note that in any case, the header fields are also set in
  * the original tuple.
  */
-static HeapTuple
+HeapTuple
 heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
 					CommandId cid, int options)
 {
@@ -2664,412 +2295,110 @@ heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
 }
 
 /*
- *	heap_multi_insert	- insert multiple tuple into a heap
+ *	simple_heap_insert - insert a tuple
+ *
+ * Currently, this routine differs from heap_insert only in supplying
+ * a default command ID and not allowing access to the speedup options.
  *
- * This is like heap_insert(), but inserts multiple tuples in one operation.
- * That's faster than calling heap_insert() in a loop, because when multiple
- * tuples can be inserted on a single page, we can write just a single WAL
- * record covering all of them, and only need to lock/unlock the page once.
+ * This should be used rather than using heap_insert directly in most places
+ * where we are modifying system catalogs.
+ */
+Oid
+simple_heap_insert(Relation relation, HeapTuple tup)
+{
+	return heap_insert(relation, tup, GetCurrentCommandId(true), 0, NULL);
+}
+
+/*
+ * Given infomask/infomask2, compute the bits that must be saved in the
+ * "infobits" field of xl_heap_delete, xl_heap_update, xl_heap_lock,
+ * xl_heap_lock_updated WAL records.
  *
- * Note: this leaks memory into the current memory context. You can create a
- * temporary context before calling this, if that's a problem.
+ * See fix_infomask_from_infobits.
  */
-void
-heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
-				  CommandId cid, int options, BulkInsertState bistate)
+uint8
+compute_infobits(uint16 infomask, uint16 infomask2)
+{
+	return
+		((infomask & HEAP_XMAX_IS_MULTI) != 0 ? XLHL_XMAX_IS_MULTI : 0) |
+		((infomask & HEAP_XMAX_LOCK_ONLY) != 0 ? XLHL_XMAX_LOCK_ONLY : 0) |
+		((infomask & HEAP_XMAX_EXCL_LOCK) != 0 ? XLHL_XMAX_EXCL_LOCK : 0) |
+	/* note we ignore HEAP_XMAX_SHR_LOCK here */
+		((infomask & HEAP_XMAX_KEYSHR_LOCK) != 0 ? XLHL_XMAX_KEYSHR_LOCK : 0) |
+		((infomask2 & HEAP_KEYS_UPDATED) != 0 ?
+		 XLHL_KEYS_UPDATED : 0);
+}
+
+
+
+/*
+ *	heap_delete - delete a tuple
+ *
+ * NB: do not call this directly unless you are prepared to deal with
+ * concurrent-update conditions.  Use simple_heap_delete instead.
+ *
+ *	relation - table to be modified (caller must hold suitable lock)
+ *	tid - TID of tuple to be deleted
+ *	cid - delete command ID (used for visibility test, and stored into
+ *		cmax if successful)
+ *	crosscheck - if not InvalidSnapshot, also check tuple against this
+ *	wait - true if should wait for any conflicting update to commit/abort
+ *	hufd - output parameter, filled in failure cases (see below)
+ *
+ * Normal, successful return value is HeapTupleMayBeUpdated, which
+ * actually means we did delete it.  Failure return codes are
+ * HeapTupleSelfUpdated, HeapTupleUpdated, or HeapTupleBeingUpdated
+ * (the last only possible if wait == false).
+ *
+ * In the failure cases, 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.
+ */
+HTSU_Result
+heap_delete(Relation relation, ItemPointer tid,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			HeapUpdateFailureData *hufd)
 {
+	HTSU_Result result;
 	TransactionId xid = GetCurrentTransactionId();
-	HeapTuple  *heaptuples;
-	int			i;
-	int			ndone;
-	char	   *scratch = NULL;
+	ItemId		lp;
+	HeapTupleData tp;
 	Page		page;
-	bool		needwal;
-	Size		saveFreeSpace;
-	bool		need_tuple_data = RelationIsLogicallyLogged(relation);
-	bool		need_cids = RelationIsAccessibleInLogicalDecoding(relation);
-
-	needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation);
-	saveFreeSpace = RelationGetTargetPageFreeSpace(relation,
-												   HEAP_DEFAULT_FILLFACTOR);
+	BlockNumber block;
+	Buffer		buffer;
+	Buffer		vmbuffer = InvalidBuffer;
+	TransactionId new_xmax;
+	uint16		new_infomask,
+				new_infomask2;
+	bool		have_tuple_lock = false;
+	bool		iscombo;
+	bool		all_visible_cleared = false;
+	HeapTuple	old_key_tuple = NULL;	/* replica identity of the tuple */
+	bool		old_key_copied = false;
 
-	/* Toast and set header data in all the tuples */
-	heaptuples = palloc(ntuples * sizeof(HeapTuple));
-	for (i = 0; i < ntuples; i++)
-		heaptuples[i] = heap_prepare_insert(relation, tuples[i],
-											xid, cid, options);
+	Assert(ItemPointerIsValid(tid));
 
 	/*
-	 * Allocate some memory to use for constructing the WAL record. Using
-	 * palloc() within a critical section is not safe, so we allocate this
-	 * beforehand.
+	 * Forbid this during a parallel operation, lest it allocate a combocid.
+	 * Other workers might need that combocid for visibility checks, and we
+	 * have no provision for broadcasting it to them.
 	 */
-	if (needwal)
-		scratch = palloc(BLCKSZ);
+	if (IsInParallelMode())
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+				 errmsg("cannot delete tuples during a parallel operation")));
+
+	block = ItemPointerGetBlockNumber(tid);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
 
 	/*
-	 * We're about to do the actual inserts -- but check for conflict first,
-	 * to minimize the possibility of having to roll back work we've just
-	 * done.
-	 *
-	 * A check here does not definitively prevent a serialization anomaly;
-	 * that check MUST be done at least past the point of acquiring an
-	 * exclusive buffer content lock on every buffer that will be affected,
-	 * and MAY be done after all inserts are reflected in the buffers and
-	 * those locks are released; otherwise there race condition.  Since
-	 * multiple buffers can be locked and unlocked in the loop below, and it
-	 * would not be feasible to identify and lock all of those buffers before
-	 * the loop, we must do a final check at the end.
-	 *
-	 * The check here could be omitted with no loss of correctness; it is
-	 * present strictly as an optimization.
-	 *
-	 * For heap inserts, we only need to check for table-level SSI locks. Our
-	 * new tuples can't possibly conflict with existing tuple locks, and heap
-	 * page locks are only consolidated versions of tuple locks; they do not
-	 * lock "gaps" as index page locks do.  So we don't need to specify a
-	 * buffer when making the call, which makes for a faster check.
-	 */
-	CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
-
-	ndone = 0;
-	while (ndone < ntuples)
-	{
-		Buffer		buffer;
-		Buffer		vmbuffer = InvalidBuffer;
-		bool		all_visible_cleared = false;
-		int			nthispage;
-
-		CHECK_FOR_INTERRUPTS();
-
-		/*
-		 * Find buffer where at least the next tuple will fit.  If the page is
-		 * all-visible, this will also pin the requisite visibility map page.
-		 */
-		buffer = RelationGetBufferForTuple(relation, heaptuples[ndone]->t_len,
-										   InvalidBuffer, options, bistate,
-										   &vmbuffer, NULL);
-		page = BufferGetPage(buffer);
-
-		/* NO EREPORT(ERROR) from here till changes are logged */
-		START_CRIT_SECTION();
-
-		/*
-		 * RelationGetBufferForTuple has ensured that the first tuple fits.
-		 * Put that on the page, and then as many other tuples as fit.
-		 */
-		RelationPutHeapTuple(relation, buffer, heaptuples[ndone], false);
-		for (nthispage = 1; ndone + nthispage < ntuples; nthispage++)
-		{
-			HeapTuple	heaptup = heaptuples[ndone + nthispage];
-
-			if (PageGetHeapFreeSpace(page) < MAXALIGN(heaptup->t_len) + saveFreeSpace)
-				break;
-
-			RelationPutHeapTuple(relation, buffer, heaptup, false);
-
-			/*
-			 * We don't use heap_multi_insert for catalog tuples yet, but
-			 * better be prepared...
-			 */
-			if (needwal && need_cids)
-				log_heap_new_cid(relation, heaptup);
-		}
-
-		if (PageIsAllVisible(page))
-		{
-			all_visible_cleared = true;
-			PageClearAllVisible(page);
-			visibilitymap_clear(relation,
-								BufferGetBlockNumber(buffer),
-								vmbuffer, VISIBILITYMAP_VALID_BITS);
-		}
-
-		/*
-		 * XXX Should we set PageSetPrunable on this page ? See heap_insert()
-		 */
-
-		MarkBufferDirty(buffer);
-
-		/* XLOG stuff */
-		if (needwal)
-		{
-			XLogRecPtr	recptr;
-			xl_heap_multi_insert *xlrec;
-			uint8		info = XLOG_HEAP2_MULTI_INSERT;
-			char	   *tupledata;
-			int			totaldatalen;
-			char	   *scratchptr = scratch;
-			bool		init;
-			int			bufflags = 0;
-
-			/*
-			 * If the page was previously empty, we can reinit the page
-			 * instead of restoring the whole thing.
-			 */
-			init = (ItemPointerGetOffsetNumber(&(heaptuples[ndone]->t_self)) == FirstOffsetNumber &&
-					PageGetMaxOffsetNumber(page) == FirstOffsetNumber + nthispage - 1);
-
-			/* allocate xl_heap_multi_insert struct from the scratch area */
-			xlrec = (xl_heap_multi_insert *) scratchptr;
-			scratchptr += SizeOfHeapMultiInsert;
-
-			/*
-			 * Allocate offsets array. Unless we're reinitializing the page,
-			 * in that case the tuples are stored in order starting at
-			 * FirstOffsetNumber and we don't need to store the offsets
-			 * explicitly.
-			 */
-			if (!init)
-				scratchptr += nthispage * sizeof(OffsetNumber);
-
-			/* the rest of the scratch space is used for tuple data */
-			tupledata = scratchptr;
-
-			xlrec->flags = all_visible_cleared ? XLH_INSERT_ALL_VISIBLE_CLEARED : 0;
-			xlrec->ntuples = nthispage;
-
-			/*
-			 * Write out an xl_multi_insert_tuple and the tuple data itself
-			 * for each tuple.
-			 */
-			for (i = 0; i < nthispage; i++)
-			{
-				HeapTuple	heaptup = heaptuples[ndone + i];
-				xl_multi_insert_tuple *tuphdr;
-				int			datalen;
-
-				if (!init)
-					xlrec->offsets[i] = ItemPointerGetOffsetNumber(&heaptup->t_self);
-				/* xl_multi_insert_tuple needs two-byte alignment. */
-				tuphdr = (xl_multi_insert_tuple *) SHORTALIGN(scratchptr);
-				scratchptr = ((char *) tuphdr) + SizeOfMultiInsertTuple;
-
-				tuphdr->t_infomask2 = heaptup->t_data->t_infomask2;
-				tuphdr->t_infomask = heaptup->t_data->t_infomask;
-				tuphdr->t_hoff = heaptup->t_data->t_hoff;
-
-				/* write bitmap [+ padding] [+ oid] + data */
-				datalen = heaptup->t_len - SizeofHeapTupleHeader;
-				memcpy(scratchptr,
-					   (char *) heaptup->t_data + SizeofHeapTupleHeader,
-					   datalen);
-				tuphdr->datalen = datalen;
-				scratchptr += datalen;
-			}
-			totaldatalen = scratchptr - tupledata;
-			Assert((scratchptr - scratch) < BLCKSZ);
-
-			if (need_tuple_data)
-				xlrec->flags |= XLH_INSERT_CONTAINS_NEW_TUPLE;
-
-			/*
-			 * Signal that this is the last xl_heap_multi_insert record
-			 * emitted by this call to heap_multi_insert(). Needed for logical
-			 * decoding so it knows when to cleanup temporary data.
-			 */
-			if (ndone + nthispage == ntuples)
-				xlrec->flags |= XLH_INSERT_LAST_IN_MULTI;
-
-			if (init)
-			{
-				info |= XLOG_HEAP_INIT_PAGE;
-				bufflags |= REGBUF_WILL_INIT;
-			}
-
-			/*
-			 * If we're doing logical decoding, include the new tuple data
-			 * even if we take a full-page image of the page.
-			 */
-			if (need_tuple_data)
-				bufflags |= REGBUF_KEEP_DATA;
-
-			XLogBeginInsert();
-			XLogRegisterData((char *) xlrec, tupledata - scratch);
-			XLogRegisterBuffer(0, buffer, REGBUF_STANDARD | bufflags);
-
-			XLogRegisterBufData(0, tupledata, totaldatalen);
-
-			/* filtering by origin on a row level is much more efficient */
-			XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
-
-			recptr = XLogInsert(RM_HEAP2_ID, info);
-
-			PageSetLSN(page, recptr);
-		}
-
-		END_CRIT_SECTION();
-
-		UnlockReleaseBuffer(buffer);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
-
-		ndone += nthispage;
-	}
-
-	/*
-	 * We're done with the actual inserts.  Check for conflicts again, to
-	 * ensure that all rw-conflicts in to these inserts are detected.  Without
-	 * this final check, a sequential scan of the heap may have locked the
-	 * table after the "before" check, missing one opportunity to detect the
-	 * conflict, and then scanned the table before the new tuples were there,
-	 * missing the other chance to detect the conflict.
-	 *
-	 * For heap inserts, we only need to check for table-level SSI locks. Our
-	 * new tuples can't possibly conflict with existing tuple locks, and heap
-	 * page locks are only consolidated versions of tuple locks; they do not
-	 * lock "gaps" as index page locks do.  So we don't need to specify a
-	 * buffer when making the call.
-	 */
-	CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
-
-	/*
-	 * If tuples are cachable, mark them for invalidation from the caches in
-	 * case we abort.  Note it is OK to do this after releasing the buffer,
-	 * because the heaptuples data structure is all in local memory, not in
-	 * the shared buffer.
-	 */
-	if (IsCatalogRelation(relation))
-	{
-		for (i = 0; i < ntuples; i++)
-			CacheInvalidateHeapTuple(relation, heaptuples[i], NULL);
-	}
-
-	/*
-	 * Copy t_self fields back to the caller's original tuples. This does
-	 * nothing for untoasted tuples (tuples[i] == heaptuples[i)], but it's
-	 * probably faster to always copy than check.
-	 */
-	for (i = 0; i < ntuples; i++)
-		tuples[i]->t_self = heaptuples[i]->t_self;
-
-	pgstat_count_heap_insert(relation, ntuples);
-}
-
-/*
- *	simple_heap_insert - insert a tuple
- *
- * Currently, this routine differs from heap_insert only in supplying
- * a default command ID and not allowing access to the speedup options.
- *
- * This should be used rather than using heap_insert directly in most places
- * where we are modifying system catalogs.
- */
-Oid
-simple_heap_insert(Relation relation, HeapTuple tup)
-{
-	return heap_insert(relation, tup, GetCurrentCommandId(true), 0, NULL);
-}
-
-/*
- * Given infomask/infomask2, compute the bits that must be saved in the
- * "infobits" field of xl_heap_delete, xl_heap_update, xl_heap_lock,
- * xl_heap_lock_updated WAL records.
- *
- * See fix_infomask_from_infobits.
- */
-static uint8
-compute_infobits(uint16 infomask, uint16 infomask2)
-{
-	return
-		((infomask & HEAP_XMAX_IS_MULTI) != 0 ? XLHL_XMAX_IS_MULTI : 0) |
-		((infomask & HEAP_XMAX_LOCK_ONLY) != 0 ? XLHL_XMAX_LOCK_ONLY : 0) |
-		((infomask & HEAP_XMAX_EXCL_LOCK) != 0 ? XLHL_XMAX_EXCL_LOCK : 0) |
-	/* note we ignore HEAP_XMAX_SHR_LOCK here */
-		((infomask & HEAP_XMAX_KEYSHR_LOCK) != 0 ? XLHL_XMAX_KEYSHR_LOCK : 0) |
-		((infomask2 & HEAP_KEYS_UPDATED) != 0 ?
-		 XLHL_KEYS_UPDATED : 0);
-}
-
-/*
- * Given two versions of the same t_infomask for a tuple, compare them and
- * return whether the relevant status for a tuple Xmax has changed.  This is
- * used after a buffer lock has been released and reacquired: we want to ensure
- * that the tuple state continues to be the same it was when we previously
- * examined it.
- *
- * Note the Xmax field itself must be compared separately.
- */
-static inline bool
-xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask)
-{
-	const uint16 interesting =
-	HEAP_XMAX_IS_MULTI | HEAP_XMAX_LOCK_ONLY | HEAP_LOCK_MASK;
-
-	if ((new_infomask & interesting) != (old_infomask & interesting))
-		return true;
-
-	return false;
-}
-
-/*
- *	heap_delete - delete a tuple
- *
- * NB: do not call this directly unless you are prepared to deal with
- * concurrent-update conditions.  Use simple_heap_delete instead.
- *
- *	relation - table to be modified (caller must hold suitable lock)
- *	tid - TID of tuple to be deleted
- *	cid - delete command ID (used for visibility test, and stored into
- *		cmax if successful)
- *	crosscheck - if not InvalidSnapshot, also check tuple against this
- *	wait - true if should wait for any conflicting update to commit/abort
- *	hufd - output parameter, filled in failure cases (see below)
- *
- * Normal, successful return value is HeapTupleMayBeUpdated, which
- * actually means we did delete it.  Failure return codes are
- * HeapTupleSelfUpdated, HeapTupleUpdated, or HeapTupleBeingUpdated
- * (the last only possible if wait == false).
- *
- * In the failure cases, 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.
- */
-HTSU_Result
-heap_delete(Relation relation, ItemPointer tid,
-			CommandId cid, Snapshot crosscheck, bool wait,
-			HeapUpdateFailureData *hufd)
-{
-	HTSU_Result result;
-	TransactionId xid = GetCurrentTransactionId();
-	ItemId		lp;
-	HeapTupleData tp;
-	Page		page;
-	BlockNumber block;
-	Buffer		buffer;
-	Buffer		vmbuffer = InvalidBuffer;
-	TransactionId new_xmax;
-	uint16		new_infomask,
-				new_infomask2;
-	bool		have_tuple_lock = false;
-	bool		iscombo;
-	bool		all_visible_cleared = false;
-	HeapTuple	old_key_tuple = NULL;	/* replica identity of the tuple */
-	bool		old_key_copied = false;
-
-	Assert(ItemPointerIsValid(tid));
-
-	/*
-	 * Forbid this during a parallel operation, lest it allocate a combocid.
-	 * Other workers might need that combocid for visibility checks, and we
-	 * have no provision for broadcasting it to them.
-	 */
-	if (IsInParallelMode())
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
-				 errmsg("cannot delete tuples during a parallel operation")));
-
-	block = ItemPointerGetBlockNumber(tid);
-	buffer = ReadBuffer(relation, block);
-	page = BufferGetPage(buffer);
-
-	/*
-	 * Before locking the buffer, pin the visibility map page if it appears to
-	 * be necessary.  Since we haven't got the lock yet, someone else might be
-	 * in the middle of changing this, so we'll need to recheck after we have
-	 * the lock.
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
 	 */
 	if (PageIsAllVisible(page))
 		visibilitymap_pin(relation, block, &vmbuffer);
@@ -4504,7 +3833,7 @@ simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup)
 /*
  * Return the MultiXactStatus corresponding to the given tuple lock mode.
  */
-static MultiXactStatus
+MultiXactStatus
 get_mxact_status_for_lock(LockTupleMode mode, bool is_update)
 {
 	int			retval;
@@ -4522,724 +3851,34 @@ get_mxact_status_for_lock(LockTupleMode mode, bool is_update)
 }
 
 /*
- *	heap_lock_tuple - lock a tuple in shared or exclusive mode
- *
- * Note that this acquires a buffer pin, which the caller must release.
- *
- * Input parameters:
- *	relation: relation containing tuple (caller must hold suitable lock)
- *	tuple->t_self: TID of tuple to lock (rest of struct need not be valid)
- *	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
- *	follow_updates: if true, follow the update chain to also lock descendant
- *		tuples.
- *
- * Output parameters:
- *	*tuple: all fields filled in
- *	*buffer: set to buffer holding tuple (pinned but not locked at exit)
- *	*hufd: filled in failure cases (see below)
- *
- * 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
+ * Acquire heavyweight lock on the given tuple, in preparation for acquiring
+ * its normal, Xmax-based tuple lock.
  *
- * 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.
+ * have_tuple_lock is an input and output parameter: on input, it indicates
+ * whether the lock has previously been acquired (and this function does
+ * nothing in that case).  If this function returns success, have_tuple_lock
+ * has been flipped to true.
  *
- * See README.tuplock for a thorough explanation of this mechanism.
+ * Returns false if it was unable to obtain the lock; this can only happen if
+ * wait_policy is Skip.
  */
-HTSU_Result
-heap_lock_tuple(Relation relation, HeapTuple tuple,
-				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
-				bool follow_updates,
-				Buffer *buffer, HeapUpdateFailureData *hufd)
+bool
+heap_acquire_tuplock(Relation relation, ItemPointer tid, LockTupleMode mode,
+					 LockWaitPolicy wait_policy, bool *have_tuple_lock)
 {
-	HTSU_Result result;
-	ItemPointer tid = &(tuple->t_self);
-	ItemId		lp;
-	Page		page;
-	Buffer		vmbuffer = InvalidBuffer;
-	BlockNumber block;
-	TransactionId xid,
-				xmax;
-	uint16		old_infomask,
-				new_infomask,
-				new_infomask2;
-	bool		first_time = true;
-	bool		have_tuple_lock = false;
-	bool		cleared_all_frozen = false;
-
-	*buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
-	block = ItemPointerGetBlockNumber(tid);
-
-	/*
-	 * Before locking the buffer, pin the visibility map page if it appears to
-	 * be necessary.  Since we haven't got the lock yet, someone else might be
-	 * in the middle of changing this, so we'll need to recheck after we have
-	 * the lock.
-	 */
-	if (PageIsAllVisible(BufferGetPage(*buffer)))
-		visibilitymap_pin(relation, block, &vmbuffer);
-
-	LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-	page = BufferGetPage(*buffer);
-	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
-	Assert(ItemIdIsNormal(lp));
+	if (*have_tuple_lock)
+		return true;
 
-	tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	tuple->t_len = ItemIdGetLength(lp);
-	tuple->t_tableOid = RelationGetRelid(relation);
+	switch (wait_policy)
+	{
+		case LockWaitBlock:
+			LockTupleTuplock(relation, tid, mode);
+			break;
 
-l3:
-	result = relation->rd_stamroutine->snapshot_satisfiesUpdate(tuple, cid, *buffer);
-
-	if (result == HeapTupleInvisible)
-	{
-		/*
-		 * This is possible, but only when locking a tuple for ON CONFLICT
-		 * UPDATE.  We return this value here rather than throwing an error in
-		 * order to give that case the opportunity to throw a more specific
-		 * error.
-		 */
-		result = HeapTupleInvisible;
-		goto out_locked;
-	}
-	else if (result == HeapTupleBeingUpdated || result == HeapTupleUpdated)
-	{
-		TransactionId xwait;
-		uint16		infomask;
-		uint16		infomask2;
-		bool		require_sleep;
-		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);
-
-		LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
-
-		/*
-		 * If any subtransaction of the current top transaction already holds
-		 * a lock as strong as or stronger than what we're requesting, we
-		 * effectively hold the desired lock already.  We *must* succeed
-		 * without trying to take the tuple lock, else we will deadlock
-		 * against anyone wanting to acquire a stronger lock.
-		 *
-		 * Note we only do this the first time we loop on the HTSU result;
-		 * there is no point in testing in subsequent passes, because
-		 * evidently our own transaction cannot have acquired a new lock after
-		 * the first time we checked.
-		 */
-		if (first_time)
-		{
-			first_time = false;
-
-			if (infomask & HEAP_XMAX_IS_MULTI)
-			{
-				int			i;
-				int			nmembers;
-				MultiXactMember *members;
-
-				/*
-				 * We don't need to allow old multixacts here; if that had
-				 * been the case, HeapTupleSatisfiesUpdate would have returned
-				 * MayBeUpdated and we wouldn't be here.
-				 */
-				nmembers =
-					GetMultiXactIdMembers(xwait, &members, false,
-										  HEAP_XMAX_IS_LOCKED_ONLY(infomask));
-
-				for (i = 0; i < nmembers; i++)
-				{
-					/* only consider members of our own transaction */
-					if (!TransactionIdIsCurrentTransactionId(members[i].xid))
-						continue;
-
-					if (TUPLOCK_from_mxstatus(members[i].status) >= mode)
-					{
-						pfree(members);
-						result = HeapTupleMayBeUpdated;
-						goto out_unlocked;
-					}
-				}
-
-				if (members)
-					pfree(members);
-			}
-			else if (TransactionIdIsCurrentTransactionId(xwait))
-			{
-				switch (mode)
-				{
-					case LockTupleKeyShare:
-						Assert(HEAP_XMAX_IS_KEYSHR_LOCKED(infomask) ||
-							   HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
-							   HEAP_XMAX_IS_EXCL_LOCKED(infomask));
-						result = HeapTupleMayBeUpdated;
-						goto out_unlocked;
-					case LockTupleShare:
-						if (HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
-							HEAP_XMAX_IS_EXCL_LOCKED(infomask))
-						{
-							result = HeapTupleMayBeUpdated;
-							goto out_unlocked;
-						}
-						break;
-					case LockTupleNoKeyExclusive:
-						if (HEAP_XMAX_IS_EXCL_LOCKED(infomask))
-						{
-							result = HeapTupleMayBeUpdated;
-							goto out_unlocked;
-						}
-						break;
-					case LockTupleExclusive:
-						if (HEAP_XMAX_IS_EXCL_LOCKED(infomask) &&
-							infomask2 & HEAP_KEYS_UPDATED)
-						{
-							result = HeapTupleMayBeUpdated;
-							goto out_unlocked;
-						}
-						break;
-				}
-			}
-		}
-
-		/*
-		 * Initially assume that we will have to wait for the locking
-		 * transaction(s) to finish.  We check various cases below in which
-		 * this can be turned off.
-		 */
-		require_sleep = true;
-		if (mode == LockTupleKeyShare)
-		{
-			/*
-			 * If we're requesting KeyShare, and there's no update present, we
-			 * don't need to wait.  Even if there is an update, we can still
-			 * continue if the key hasn't been modified.
-			 *
-			 * However, if there are updates, we need to walk the update chain
-			 * to mark future versions of the row as locked, too.  That way,
-			 * if somebody deletes that future version, we're protected
-			 * against the key going away.  This locking of future versions
-			 * could block momentarily, if a concurrent transaction is
-			 * deleting a key; or it could return a value to the effect that
-			 * the transaction deleting the key has already committed.  So we
-			 * do this before re-locking the buffer; otherwise this would be
-			 * prone to deadlocks.
-			 *
-			 * Note that the TID we're locking was grabbed before we unlocked
-			 * the buffer.  For it to change while we're not looking, the
-			 * other properties we're testing for below after re-locking the
-			 * buffer would also change, in which case we would restart this
-			 * loop above.
-			 */
-			if (!(infomask2 & HEAP_KEYS_UPDATED))
-			{
-				bool		updated;
-
-				updated = !HEAP_XMAX_IS_LOCKED_ONLY(infomask);
-
-				/*
-				 * If there are updates, follow the update chain; bail out if
-				 * that cannot be done.
-				 */
-				if (follow_updates && updated)
-				{
-					HTSU_Result res;
-
-					res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
-												  GetCurrentTransactionId(),
-												  mode);
-					if (res != HeapTupleMayBeUpdated)
-					{
-						result = res;
-						/* recovery code expects to have buffer lock held */
-						LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-						goto failed;
-					}
-				}
-
-				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-				/*
-				 * Make sure it's still an appropriate lock, else start over.
-				 * Also, if it wasn't updated before we released the lock, but
-				 * is updated now, we start over too; the reason is that we
-				 * 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) ||
-					 !updated))
-					goto l3;
-
-				/* Things look okay, so we can skip sleeping */
-				require_sleep = false;
-
-				/*
-				 * Note we allow Xmax to change here; other updaters/lockers
-				 * could have modified it before we grabbed the buffer lock.
-				 * However, this is not a problem, because with the recheck we
-				 * just did we ensure that they still don't conflict with the
-				 * lock we want.
-				 */
-			}
-		}
-		else if (mode == LockTupleShare)
-		{
-			/*
-			 * If we're requesting Share, we can similarly avoid sleeping if
-			 * there's no update and no exclusive lock present.
-			 */
-			if (HEAP_XMAX_IS_LOCKED_ONLY(infomask) &&
-				!HEAP_XMAX_IS_EXCL_LOCKED(infomask))
-			{
-				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-				/*
-				 * 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))
-					goto l3;
-				require_sleep = false;
-			}
-		}
-		else if (mode == LockTupleNoKeyExclusive)
-		{
-			/*
-			 * If we're requesting NoKeyExclusive, we might also be able to
-			 * avoid sleeping; just ensure that there no conflicting lock
-			 * already acquired.
-			 */
-			if (infomask & HEAP_XMAX_IS_MULTI)
-			{
-				if (!DoesMultiXactIdConflict((MultiXactId) xwait, infomask,
-											 mode))
-				{
-					/*
-					 * No conflict, but if the xmax changed under us 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),
-											 xwait))
-						goto l3;
-
-					/* otherwise, we're good */
-					require_sleep = false;
-				}
-			}
-			else if (HEAP_XMAX_IS_KEYSHR_LOCKED(infomask))
-			{
-				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-				/* if the xmax changed in the meantime, start over */
-				if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
-					!TransactionIdEquals(
-										 HeapTupleHeaderGetRawXmax(tuple->t_data),
-										 xwait))
-					goto l3;
-				/* otherwise, we're good */
-				require_sleep = false;
-			}
-		}
-
-		/*
-		 * As a check independent from those above, we can also avoid sleeping
-		 * if the current transaction is the sole locker of the tuple.  Note
-		 * that the strength of the lock already held is irrelevant; this is
-		 * not about recording the lock in Xmax (which will be done regardless
-		 * of this optimization, below).  Also, note that the cases where we
-		 * hold a lock stronger than we are requesting are already handled
-		 * above by not doing anything.
-		 *
-		 * Note we only deal with the non-multixact case here; MultiXactIdWait
-		 * is well equipped to deal with this situation on its own.
-		 */
-		if (require_sleep && !(infomask & HEAP_XMAX_IS_MULTI) &&
-			TransactionIdIsCurrentTransactionId(xwait))
-		{
-			/* ... 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),
-									 xwait))
-				goto l3;
-			Assert(HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask));
-			require_sleep = false;
-		}
-
-		/*
-		 * Time to sleep on the other transaction/multixact, if necessary.
-		 *
-		 * If the other transaction is an update that's already committed,
-		 * then sleeping cannot possibly do any good: if we're required to
-		 * sleep, get out to raise an error instead.
-		 *
-		 * By here, we either have already acquired the buffer exclusive lock,
-		 * 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)
-		{
-			LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-			goto failed;
-		}
-		else if (require_sleep)
-		{
-			/*
-			 * Acquire tuple lock to establish our priority for the tuple, or
-			 * die trying.  LockTuple will release us when we are next-in-line
-			 * for the tuple.  We must do this even if we are share-locking.
-			 *
-			 * If we are forced to "start over" below, we keep the tuple lock;
-			 * this arranges that we stay at the head of the line while
-			 * rechecking tuple state.
-			 */
-			if (!heap_acquire_tuplock(relation, tid, mode, wait_policy,
-									  &have_tuple_lock))
-			{
-				/*
-				 * This can only happen if wait_policy is Skip and the lock
-				 * couldn't be obtained.
-				 */
-				result = HeapTupleWouldBlock;
-				/* recovery code expects to have buffer lock held */
-				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-				goto failed;
-			}
-
-			if (infomask & HEAP_XMAX_IS_MULTI)
-			{
-				MultiXactStatus status = get_mxact_status_for_lock(mode, false);
-
-				/* We only ever lock tuples, never update them */
-				if (status >= MultiXactStatusNoKeyUpdate)
-					elog(ERROR, "invalid lock mode in heap_lock_tuple");
-
-				/* wait for multixact to end, or die trying  */
-				switch (wait_policy)
-				{
-					case LockWaitBlock:
-						MultiXactIdWait((MultiXactId) xwait, status, infomask,
-										relation, &tuple->t_self, XLTW_Lock, NULL);
-						break;
-					case LockWaitSkip:
-						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
-														status, infomask, relation,
-														NULL))
-						{
-							result = HeapTupleWouldBlock;
-							/* recovery code expects to have buffer lock held */
-							LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-							goto failed;
-						}
-						break;
-					case LockWaitError:
-						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
-														status, infomask, relation,
-														NULL))
-							ereport(ERROR,
-									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-									 errmsg("could not obtain lock on row in relation \"%s\"",
-											RelationGetRelationName(relation))));
-
-						break;
-				}
-
-				/*
-				 * Of course, the multixact might not be done here: if we're
-				 * requesting a light lock mode, other transactions with light
-				 * locks could still be alive, as well as locks owned by our
-				 * own xact or other subxacts of this backend.  We need to
-				 * preserve the surviving MultiXact members.  Note that it
-				 * isn't absolutely necessary in the latter case, but doing so
-				 * is simpler.
-				 */
-			}
-			else
-			{
-				/* wait for regular transaction to end, or die trying */
-				switch (wait_policy)
-				{
-					case LockWaitBlock:
-						XactLockTableWait(xwait, relation, &tuple->t_self,
-										  XLTW_Lock);
-						break;
-					case LockWaitSkip:
-						if (!ConditionalXactLockTableWait(xwait))
-						{
-							result = HeapTupleWouldBlock;
-							/* recovery code expects to have buffer lock held */
-							LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-							goto failed;
-						}
-						break;
-					case LockWaitError:
-						if (!ConditionalXactLockTableWait(xwait))
-							ereport(ERROR,
-									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-									 errmsg("could not obtain lock on row in relation \"%s\"",
-											RelationGetRelationName(relation))));
-						break;
-				}
-			}
-
-			/* if there are updates, follow the update chain */
-			if (follow_updates && !HEAP_XMAX_IS_LOCKED_ONLY(infomask))
-			{
-				HTSU_Result res;
-
-				res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
-											  GetCurrentTransactionId(),
-											  mode);
-				if (res != HeapTupleMayBeUpdated)
-				{
-					result = res;
-					/* recovery code expects to have buffer lock held */
-					LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-					goto failed;
-				}
-			}
-
-			LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-			/*
-			 * xwait is done, but if xwait had just locked the tuple then some
-			 * 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),
-									 xwait))
-				goto l3;
-
-			if (!(infomask & HEAP_XMAX_IS_MULTI))
-			{
-				/*
-				 * Otherwise check if it committed or aborted.  Note we cannot
-				 * be here if the tuple was only locked by somebody who didn't
-				 * conflict with us; that would have been handled above.  So
-				 * that transaction must necessarily be gone by now.  But
-				 * don't check for this in the multixact case, because some
-				 * locker transactions might still be running.
-				 */
-				UpdateXmaxHintBits(tuple->t_data, *buffer, xwait);
-			}
-		}
-
-		/* By here, we're certain that we hold buffer exclusive lock again */
-
-		/*
-		 * We may lock if previous xmax aborted, or if it committed but only
-		 * locked the tuple without updating it; or if we didn't have to wait
-		 * 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))
-			result = HeapTupleMayBeUpdated;
-		else
-			result = HeapTupleUpdated;
-	}
-
-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);
-		if (result == HeapTupleSelfUpdated)
-			hufd->cmax = HeapTupleHeaderGetCmax(tuple->t_data);
-		else
-			hufd->cmax = InvalidCommandId;
-		goto out_locked;
-	}
-
-	/*
-	 * If we didn't pin the visibility map page and the page has become all
-	 * visible while we were busy locking the buffer, or during some
-	 * subsequent window during which we had it unlocked, we'll have to unlock
-	 * and re-lock, to avoid holding the buffer lock across I/O.  That's a bit
-	 * unfortunate, especially since we'll now have to recheck whether the
-	 * tuple has been locked or updated under us, but hopefully it won't
-	 * happen very often.
-	 */
-	if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
-	{
-		LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
-		visibilitymap_pin(relation, block, &vmbuffer);
-		LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-		goto l3;
-	}
-
-	xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
-	old_infomask = tuple->t_data->t_infomask;
-
-	/*
-	 * If this is the first possibly-multixact-able operation in the current
-	 * transaction, set my per-backend OldestMemberMXactId setting. We can be
-	 * certain that the transaction will never become a member of any older
-	 * MultiXactIds than that.  (We have to do this even if we end up just
-	 * using our own TransactionId below, since some other backend could
-	 * incorporate our XID into a MultiXact immediately afterwards.)
-	 */
-	MultiXactIdSetOldestMember();
-
-	/*
-	 * Compute the new xmax and infomask to store into the tuple.  Note we do
-	 * 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,
-							  GetCurrentTransactionId(), mode, false,
-							  &xid, &new_infomask, &new_infomask2);
-
-	START_CRIT_SECTION();
-
-	/*
-	 * Store transaction information of xact locking the tuple.
-	 *
-	 * Note: Cmax is meaningless in this context, so don't set it; this avoids
-	 * possibly generating a useless combo CID.  Moreover, if we're locking a
-	 * previously updated tuple, it's important to preserve the Cmax.
-	 *
-	 * 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;
-	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
-		HeapTupleHeaderClearHotUpdated(tuple->t_data);
-	HeapTupleHeaderSetXmax(tuple->t_data, xid);
-
-	/*
-	 * Make sure there is no forward chain link in t_ctid.  Note that in the
-	 * cases where the tuple has been updated, we must not overwrite t_ctid,
-	 * because it was set by the updater.  Moreover, if the tuple has been
-	 * updated, we need to follow the update chain to lock the new versions of
-	 * the tuple as well.
-	 */
-	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
-		tuple->t_data->t_ctid = *tid;
-
-	/* Clear only the all-frozen bit on visibility map if needed */
-	if (PageIsAllVisible(page) &&
-		visibilitymap_clear(relation, block, vmbuffer,
-							VISIBILITYMAP_ALL_FROZEN))
-		cleared_all_frozen = true;
-
-
-	MarkBufferDirty(*buffer);
-
-	/*
-	 * XLOG stuff.  You might think that we don't need an XLOG record because
-	 * there is no state change worth restoring after a crash.  You would be
-	 * wrong however: we have just written either a TransactionId or a
-	 * MultiXactId that may never have been seen on disk before, and we need
-	 * to make sure that there are XLOG entries covering those ID numbers.
-	 * Else the same IDs might be re-used after a crash, which would be
-	 * disastrous if this page made it to disk before the crash.  Essentially
-	 * we have to enforce the WAL log-before-data rule even in this case.
-	 * (Also, in a PITR log-shipping or 2PC environment, we have to have XLOG
-	 * entries for everything anyway.)
-	 */
-	if (RelationNeedsWAL(relation))
-	{
-		xl_heap_lock xlrec;
-		XLogRecPtr	recptr;
-
-		XLogBeginInsert();
-		XLogRegisterBuffer(0, *buffer, REGBUF_STANDARD);
-
-		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
-		xlrec.locking_xid = xid;
-		xlrec.infobits_set = compute_infobits(new_infomask,
-											  tuple->t_data->t_infomask2);
-		xlrec.flags = cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
-		XLogRegisterData((char *) &xlrec, SizeOfHeapLock);
-
-		/* we don't decode row locks atm, so no need to log the origin */
-
-		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_LOCK);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	result = HeapTupleMayBeUpdated;
-
-out_locked:
-	LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
-
-out_unlocked:
-	if (BufferIsValid(vmbuffer))
-		ReleaseBuffer(vmbuffer);
-
-	/*
-	 * Don't update the visibility map here. Locking a tuple doesn't change
-	 * visibility info.
-	 */
-
-	/*
-	 * Now that we have successfully marked the tuple as locked, we can
-	 * release the lmgr tuple lock, if we had it.
-	 */
-	if (have_tuple_lock)
-		UnlockTupleTuplock(relation, tid, mode);
-
-	return result;
-}
-
-/*
- * Acquire heavyweight lock on the given tuple, in preparation for acquiring
- * its normal, Xmax-based tuple lock.
- *
- * have_tuple_lock is an input and output parameter: on input, it indicates
- * whether the lock has previously been acquired (and this function does
- * nothing in that case).  If this function returns success, have_tuple_lock
- * has been flipped to true.
- *
- * Returns false if it was unable to obtain the lock; this can only happen if
- * wait_policy is Skip.
- */
-static bool
-heap_acquire_tuplock(Relation relation, ItemPointer tid, LockTupleMode mode,
-					 LockWaitPolicy wait_policy, bool *have_tuple_lock)
-{
-	if (*have_tuple_lock)
-		return true;
-
-	switch (wait_policy)
-	{
-		case LockWaitBlock:
-			LockTupleTuplock(relation, tid, mode);
-			break;
-
-		case LockWaitSkip:
-			if (!ConditionalLockTupleTuplock(relation, tid, mode))
-				return false;
-			break;
+		case LockWaitSkip:
+			if (!ConditionalLockTupleTuplock(relation, tid, mode))
+				return false;
+			break;
 
 		case LockWaitError:
 			if (!ConditionalLockTupleTuplock(relation, tid, mode))
@@ -5272,7 +3911,7 @@ heap_acquire_tuplock(Relation relation, ItemPointer tid, LockTupleMode mode,
  * window, but it's still possible to end up creating an unnecessary
  * MultiXactId.  Fortunately this is harmless.
  */
-static void
+void
 compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
 						  uint16 old_infomask2, TransactionId add_to_xmax,
 						  LockTupleMode mode, bool is_update,
@@ -5319,916 +3958,226 @@ l5:
 					break;
 				case LockTupleNoKeyExclusive:
 					new_xmax = add_to_xmax;
-					new_infomask |= HEAP_XMAX_EXCL_LOCK;
-					break;
-				case LockTupleExclusive:
-					new_xmax = add_to_xmax;
-					new_infomask |= HEAP_XMAX_EXCL_LOCK;
-					new_infomask2 |= HEAP_KEYS_UPDATED;
-					break;
-				default:
-					new_xmax = InvalidTransactionId;	/* silence compiler */
-					elog(ERROR, "invalid lock mode");
-			}
-		}
-	}
-	else if (old_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		MultiXactStatus new_status;
-
-		/*
-		 * Currently we don't allow XMAX_COMMITTED to be set for multis, so
-		 * cross-check.
-		 */
-		Assert(!(old_infomask & HEAP_XMAX_COMMITTED));
-
-		/*
-		 * A multixact together with LOCK_ONLY set but neither lock bit set
-		 * (i.e. a pg_upgraded share locked tuple) cannot possibly be running
-		 * anymore.  This check is critical for databases upgraded by
-		 * pg_upgrade; both MultiXactIdIsRunning and MultiXactIdExpand assume
-		 * that such multis are never passed.
-		 */
-		if (HEAP_LOCKED_UPGRADED(old_infomask))
-		{
-			old_infomask &= ~HEAP_XMAX_IS_MULTI;
-			old_infomask |= HEAP_XMAX_INVALID;
-			goto l5;
-		}
-
-		/*
-		 * If the XMAX is already a MultiXactId, then we need to expand it to
-		 * include add_to_xmax; but if all the members were lockers and are
-		 * all gone, we can do away with the IS_MULTI bit and just set
-		 * add_to_xmax as the only locker/updater.  If all lockers are gone
-		 * and we have an updater that aborted, we can also do without a
-		 * multi.
-		 *
-		 * The cost of doing GetMultiXactIdMembers would be paid by
-		 * MultiXactIdExpand if we weren't to do this, so this check is not
-		 * incurring extra work anyhow.
-		 */
-		if (!MultiXactIdIsRunning(xmax, HEAP_XMAX_IS_LOCKED_ONLY(old_infomask)))
-		{
-			if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) ||
-				!TransactionIdDidCommit(MultiXactIdGetUpdateXid(xmax,
-																old_infomask)))
-			{
-				/*
-				 * Reset these bits and restart; otherwise fall through to
-				 * create a new multi below.
-				 */
-				old_infomask &= ~HEAP_XMAX_IS_MULTI;
-				old_infomask |= HEAP_XMAX_INVALID;
-				goto l5;
-			}
-		}
-
-		new_status = get_mxact_status_for_lock(mode, is_update);
-
-		new_xmax = MultiXactIdExpand((MultiXactId) xmax, add_to_xmax,
-									 new_status);
-		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
-	}
-	else if (old_infomask & HEAP_XMAX_COMMITTED)
-	{
-		/*
-		 * It's a committed update, so we need to preserve him as updater of
-		 * the tuple.
-		 */
-		MultiXactStatus status;
-		MultiXactStatus new_status;
-
-		if (old_infomask2 & HEAP_KEYS_UPDATED)
-			status = MultiXactStatusUpdate;
-		else
-			status = MultiXactStatusNoKeyUpdate;
-
-		new_status = get_mxact_status_for_lock(mode, is_update);
-
-		/*
-		 * since it's not running, it's obviously impossible for the old
-		 * updater to be identical to the current one, so we need not check
-		 * for that case as we do in the block above.
-		 */
-		new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
-		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
-	}
-	else if (TransactionIdIsInProgress(xmax))
-	{
-		/*
-		 * If the XMAX is a valid, in-progress TransactionId, then we need to
-		 * create a new MultiXactId that includes both the old locker or
-		 * updater and our own TransactionId.
-		 */
-		MultiXactStatus new_status;
-		MultiXactStatus old_status;
-		LockTupleMode old_mode;
-
-		if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
-		{
-			if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
-				old_status = MultiXactStatusForKeyShare;
-			else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
-				old_status = MultiXactStatusForShare;
-			else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
-			{
-				if (old_infomask2 & HEAP_KEYS_UPDATED)
-					old_status = MultiXactStatusForUpdate;
-				else
-					old_status = MultiXactStatusForNoKeyUpdate;
-			}
-			else
-			{
-				/*
-				 * LOCK_ONLY can be present alone only when a page has been
-				 * upgraded by pg_upgrade.  But in that case,
-				 * TransactionIdIsInProgress() should have returned false.  We
-				 * assume it's no longer locked in this case.
-				 */
-				elog(WARNING, "LOCK_ONLY found for Xid in progress %u", xmax);
-				old_infomask |= HEAP_XMAX_INVALID;
-				old_infomask &= ~HEAP_XMAX_LOCK_ONLY;
-				goto l5;
-			}
-		}
-		else
-		{
-			/* it's an update, but which kind? */
-			if (old_infomask2 & HEAP_KEYS_UPDATED)
-				old_status = MultiXactStatusUpdate;
-			else
-				old_status = MultiXactStatusNoKeyUpdate;
-		}
-
-		old_mode = TUPLOCK_from_mxstatus(old_status);
-
-		/*
-		 * If the lock to be acquired is for the same TransactionId as the
-		 * existing lock, there's an optimization possible: consider only the
-		 * strongest of both locks as the only one present, and restart.
-		 */
-		if (xmax == add_to_xmax)
-		{
-			/*
-			 * Note that it's not possible for the original tuple to be
-			 * updated: we wouldn't be here because the tuple would have been
-			 * invisible and we wouldn't try to update it.  As a subtlety,
-			 * this code can also run when traversing an update chain to lock
-			 * future versions of a tuple.  But we wouldn't be here either,
-			 * because the add_to_xmax would be different from the original
-			 * updater.
-			 */
-			Assert(HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
-
-			/* acquire the strongest of both */
-			if (mode < old_mode)
-				mode = old_mode;
-			/* mustn't touch is_update */
-
-			old_infomask |= HEAP_XMAX_INVALID;
-			goto l5;
-		}
-
-		/* otherwise, just fall back to creating a new multixact */
-		new_status = get_mxact_status_for_lock(mode, is_update);
-		new_xmax = MultiXactIdCreate(xmax, old_status,
-									 add_to_xmax, new_status);
-		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
-	}
-	else if (!HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) &&
-			 TransactionIdDidCommit(xmax))
-	{
-		/*
-		 * It's a committed update, so we gotta preserve him as updater of the
-		 * tuple.
-		 */
-		MultiXactStatus status;
-		MultiXactStatus new_status;
-
-		if (old_infomask2 & HEAP_KEYS_UPDATED)
-			status = MultiXactStatusUpdate;
-		else
-			status = MultiXactStatusNoKeyUpdate;
-
-		new_status = get_mxact_status_for_lock(mode, is_update);
-
-		/*
-		 * since it's not running, it's obviously impossible for the old
-		 * updater to be identical to the current one, so we need not check
-		 * for that case as we do in the block above.
-		 */
-		new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
-		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
-	}
-	else
-	{
-		/*
-		 * Can get here iff the locking/updating transaction was running when
-		 * the infomask was extracted from the tuple, but finished before
-		 * TransactionIdIsInProgress got to run.  Deal with it as if there was
-		 * no locker at all in the first place.
-		 */
-		old_infomask |= HEAP_XMAX_INVALID;
-		goto l5;
-	}
-
-	*result_infomask = new_infomask;
-	*result_infomask2 = new_infomask2;
-	*result_xmax = new_xmax;
-}
-
-/*
- * Subroutine for heap_lock_updated_tuple_rec.
- *
- * Given a hypothetical multixact status held by the transaction identified
- * with the given xid, does the current transaction need to wait, fail, or can
- * it continue if it wanted to acquire a lock of the given mode?  "needwait"
- * is set to true if waiting is necessary; if it can continue, then
- * HeapTupleMayBeUpdated is returned.  If the lock is already held by the
- * current transaction, return HeapTupleSelfUpdated.  In case of a conflict
- * with another transaction, a different HeapTupleSatisfiesUpdate return code
- * is returned.
- *
- * The held status is said to be hypothetical because it might correspond to a
- * lock held by a single Xid, i.e. not a real MultiXactId; we express it this
- * way for simplicity of API.
- */
-static HTSU_Result
-test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid,
-						   LockTupleMode mode, bool *needwait)
-{
-	MultiXactStatus wantedstatus;
-
-	*needwait = false;
-	wantedstatus = get_mxact_status_for_lock(mode, false);
-
-	/*
-	 * Note: we *must* check TransactionIdIsInProgress before
-	 * TransactionIdDidAbort/Commit; see comment at top of tqual.c for an
-	 * explanation.
-	 */
-	if (TransactionIdIsCurrentTransactionId(xid))
-	{
-		/*
-		 * The tuple has already been locked by our own transaction.  This is
-		 * very rare but can happen if multiple transactions are trying to
-		 * lock an ancient version of the same tuple.
-		 */
-		return HeapTupleSelfUpdated;
-	}
-	else if (TransactionIdIsInProgress(xid))
-	{
-		/*
-		 * If the locking transaction is running, what we do depends on
-		 * whether the lock modes conflict: if they do, then we must wait for
-		 * it to finish; otherwise we can fall through to lock this tuple
-		 * version without waiting.
-		 */
-		if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
-								LOCKMODE_from_mxstatus(wantedstatus)))
-		{
-			*needwait = true;
-		}
-
-		/*
-		 * If we set needwait above, then this value doesn't matter;
-		 * otherwise, this value signals to caller that it's okay to proceed.
-		 */
-		return HeapTupleMayBeUpdated;
-	}
-	else if (TransactionIdDidAbort(xid))
-		return HeapTupleMayBeUpdated;
-	else if (TransactionIdDidCommit(xid))
-	{
-		/*
-		 * The other transaction committed.  If it was only a locker, then the
-		 * lock is completely gone now and we can return success; but if it
-		 * was an update, then what we do depends on whether the two lock
-		 * modes conflict.  If they conflict, then we must report error to
-		 * caller. But if they don't, we can fall through to allow the current
-		 * transaction to lock the tuple.
-		 *
-		 * Note: the reason we worry about ISUPDATE here is because as soon as
-		 * a transaction ends, all its locks are gone and meaningless, and
-		 * thus we can ignore them; whereas its updates persist.  In the
-		 * TransactionIdIsInProgress case, above, we don't need to check
-		 * because we know the lock is still "alive" and thus a conflict needs
-		 * always be checked.
-		 */
-		if (!ISUPDATE_from_mxstatus(status))
-			return HeapTupleMayBeUpdated;
-
-		if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
-								LOCKMODE_from_mxstatus(wantedstatus)))
-			/* bummer */
-			return HeapTupleUpdated;
-
-		return HeapTupleMayBeUpdated;
-	}
-
-	/* Not in progress, not aborted, not committed -- must have crashed */
-	return HeapTupleMayBeUpdated;
-}
-
-
-/*
- * Recursive part of heap_lock_updated_tuple
- *
- * Fetch the tuple pointed to by tid in rel, and mark it as locked by the given
- * xid with the given mode; if this tuple is updated, recurse to lock the new
- * version as well.
- */
-static HTSU_Result
-heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid,
-							LockTupleMode mode)
-{
-	HTSU_Result result;
-	ItemPointerData tupid;
-	HeapTupleData mytup;
-	Buffer		buf;
-	uint16		new_infomask,
-				new_infomask2,
-				old_infomask,
-				old_infomask2;
-	TransactionId xmax,
-				new_xmax;
-	TransactionId priorXmax = InvalidTransactionId;
-	bool		cleared_all_frozen = false;
-	Buffer		vmbuffer = InvalidBuffer;
-	BlockNumber block;
-
-	ItemPointerCopy(tid, &tupid);
-
-	for (;;)
-	{
-		new_infomask = 0;
-		new_xmax = InvalidTransactionId;
-		block = ItemPointerGetBlockNumber(&tupid);
-		ItemPointerCopy(&tupid, &(mytup.t_self));
-
-		if (!heap_fetch(rel, SnapshotAny, &mytup, &buf, false, NULL))
-		{
-			/*
-			 * if we fail to find the updated version of the tuple, it's
-			 * because it was vacuumed/pruned away after its creator
-			 * transaction aborted.  So behave as if we got to the end of the
-			 * chain, and there's no further tuple to lock: return success to
-			 * caller.
-			 */
-			return HeapTupleMayBeUpdated;
-		}
-
-l4:
-		CHECK_FOR_INTERRUPTS();
-
-		/*
-		 * Before locking the buffer, pin the visibility map page if it
-		 * appears to be necessary.  Since we haven't got the lock yet,
-		 * someone else might be in the middle of changing this, so we'll need
-		 * to recheck after we have the lock.
-		 */
-		if (PageIsAllVisible(BufferGetPage(buf)))
-			visibilitymap_pin(rel, block, &vmbuffer);
-		else
-			vmbuffer = InvalidBuffer;
-
-		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
-
-		/*
-		 * If we didn't pin the visibility map page and the page has become
-		 * all visible while we were busy locking the buffer, we'll have to
-		 * unlock and re-lock, to avoid holding the buffer lock across I/O.
-		 * That's a bit unfortunate, but hopefully shouldn't happen often.
-		 */
-		if (vmbuffer == InvalidBuffer && PageIsAllVisible(BufferGetPage(buf)))
-		{
-			LockBuffer(buf, BUFFER_LOCK_UNLOCK);
-			visibilitymap_pin(rel, block, &vmbuffer);
-			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
-		}
-
-		/*
-		 * Check the tuple XMIN against prior XMAX, if any.  If we reached the
-		 * end of the chain, we're done, so return success.
-		 */
-		if (TransactionIdIsValid(priorXmax) &&
-			!TransactionIdEquals(HeapTupleHeaderGetXmin(mytup.t_data),
-								 priorXmax))
-		{
-			result = HeapTupleMayBeUpdated;
-			goto out_locked;
-		}
-
-		/*
-		 * Also check Xmin: if this tuple was created by an aborted
-		 * (sub)transaction, then we already locked the last live one in the
-		 * chain, thus we're done, so return success.
-		 */
-		if (TransactionIdDidAbort(HeapTupleHeaderGetXmin(mytup.t_data)))
-		{
-			UnlockReleaseBuffer(buf);
-			return HeapTupleMayBeUpdated;
-		}
-
-		old_infomask = mytup.t_data->t_infomask;
-		old_infomask2 = mytup.t_data->t_infomask2;
-		xmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
-
-		/*
-		 * If this tuple version has been updated or locked by some concurrent
-		 * transaction(s), what we do depends on whether our lock mode
-		 * conflicts with what those other transactions hold, and also on the
-		 * status of them.
-		 */
-		if (!(old_infomask & HEAP_XMAX_INVALID))
-		{
-			TransactionId rawxmax;
-			bool		needwait;
-
-			rawxmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
-			if (old_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				int			nmembers;
-				int			i;
-				MultiXactMember *members;
-
-				/*
-				 * We don't need a test for pg_upgrade'd tuples: this is only
-				 * applied to tuples after the first in an update chain.  Said
-				 * first tuple in the chain may well be locked-in-9.2-and-
-				 * pg_upgraded, but that one was already locked by our caller,
-				 * not us; and any subsequent ones cannot be because our
-				 * caller must necessarily have obtained a snapshot later than
-				 * the pg_upgrade itself.
-				 */
-				Assert(!HEAP_LOCKED_UPGRADED(mytup.t_data->t_infomask));
-
-				nmembers = GetMultiXactIdMembers(rawxmax, &members, false,
-												 HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
-				for (i = 0; i < nmembers; i++)
-				{
-					result = test_lockmode_for_conflict(members[i].status,
-														members[i].xid,
-														mode, &needwait);
-
-					/*
-					 * If the tuple was already locked by ourselves in a
-					 * previous iteration of this (say heap_lock_tuple was
-					 * forced to restart the locking loop because of a change
-					 * in xmax), then we hold the lock already on this tuple
-					 * version and we don't need to do anything; and this is
-					 * not an error condition either.  We just need to skip
-					 * this tuple and continue locking the next version in the
-					 * update chain.
-					 */
-					if (result == HeapTupleSelfUpdated)
-					{
-						pfree(members);
-						goto next;
-					}
-
-					if (needwait)
-					{
-						LockBuffer(buf, BUFFER_LOCK_UNLOCK);
-						XactLockTableWait(members[i].xid, rel,
-										  &mytup.t_self,
-										  XLTW_LockUpdated);
-						pfree(members);
-						goto l4;
-					}
-					if (result != HeapTupleMayBeUpdated)
-					{
-						pfree(members);
-						goto out_locked;
-					}
-				}
-				if (members)
-					pfree(members);
-			}
-			else
-			{
-				MultiXactStatus status;
-
-				/*
-				 * For a non-multi Xmax, we first need to compute the
-				 * corresponding MultiXactStatus by using the infomask bits.
-				 */
-				if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
-				{
-					if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
-						status = MultiXactStatusForKeyShare;
-					else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
-						status = MultiXactStatusForShare;
-					else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
-					{
-						if (old_infomask2 & HEAP_KEYS_UPDATED)
-							status = MultiXactStatusForUpdate;
-						else
-							status = MultiXactStatusForNoKeyUpdate;
-					}
-					else
-					{
-						/*
-						 * LOCK_ONLY present alone (a pg_upgraded tuple marked
-						 * as share-locked in the old cluster) shouldn't be
-						 * seen in the middle of an update chain.
-						 */
-						elog(ERROR, "invalid lock status in tuple");
-					}
-				}
-				else
-				{
-					/* it's an update, but which kind? */
-					if (old_infomask2 & HEAP_KEYS_UPDATED)
-						status = MultiXactStatusUpdate;
-					else
-						status = MultiXactStatusNoKeyUpdate;
-				}
-
-				result = test_lockmode_for_conflict(status, rawxmax, mode,
-													&needwait);
-
-				/*
-				 * If the tuple was already locked by ourselves in a previous
-				 * iteration of this (say heap_lock_tuple was forced to
-				 * restart the locking loop because of a change in xmax), then
-				 * we hold the lock already on this tuple version and we don't
-				 * need to do anything; and this is not an error condition
-				 * either.  We just need to skip this tuple and continue
-				 * locking the next version in the update chain.
-				 */
-				if (result == HeapTupleSelfUpdated)
-					goto next;
-
-				if (needwait)
-				{
-					LockBuffer(buf, BUFFER_LOCK_UNLOCK);
-					XactLockTableWait(rawxmax, rel, &mytup.t_self,
-									  XLTW_LockUpdated);
-					goto l4;
-				}
-				if (result != HeapTupleMayBeUpdated)
-				{
-					goto out_locked;
-				}
-			}
-		}
-
-		/* compute the new Xmax and infomask values for the tuple ... */
-		compute_new_xmax_infomask(xmax, old_infomask, mytup.t_data->t_infomask2,
-								  xid, mode, false,
-								  &new_xmax, &new_infomask, &new_infomask2);
-
-		if (PageIsAllVisible(BufferGetPage(buf)) &&
-			visibilitymap_clear(rel, block, vmbuffer,
-								VISIBILITYMAP_ALL_FROZEN))
-			cleared_all_frozen = true;
-
-		START_CRIT_SECTION();
-
-		/* ... and set them */
-		HeapTupleHeaderSetXmax(mytup.t_data, new_xmax);
-		mytup.t_data->t_infomask &= ~HEAP_XMAX_BITS;
-		mytup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
-		mytup.t_data->t_infomask |= new_infomask;
-		mytup.t_data->t_infomask2 |= new_infomask2;
-
-		MarkBufferDirty(buf);
-
-		/* XLOG stuff */
-		if (RelationNeedsWAL(rel))
-		{
-			xl_heap_lock_updated xlrec;
-			XLogRecPtr	recptr;
-			Page		page = BufferGetPage(buf);
-
-			XLogBeginInsert();
-			XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
-
-			xlrec.offnum = ItemPointerGetOffsetNumber(&mytup.t_self);
-			xlrec.xmax = new_xmax;
-			xlrec.infobits_set = compute_infobits(new_infomask, new_infomask2);
-			xlrec.flags =
-				cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
-
-			XLogRegisterData((char *) &xlrec, SizeOfHeapLockUpdated);
-
-			recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_LOCK_UPDATED);
-
-			PageSetLSN(page, recptr);
-		}
-
-		END_CRIT_SECTION();
-
-next:
-		/* if we find the end of update chain, we're done. */
-		if (mytup.t_data->t_infomask & HEAP_XMAX_INVALID ||
-			ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid) ||
-			HeapTupleHeaderIsOnlyLocked(mytup.t_data))
-		{
-			result = HeapTupleMayBeUpdated;
-			goto out_locked;
-		}
-
-		/* tail recursion */
-		priorXmax = HeapTupleHeaderGetUpdateXid(mytup.t_data);
-		ItemPointerCopy(&(mytup.t_data->t_ctid), &tupid);
-		UnlockReleaseBuffer(buf);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
-	}
-
-	result = HeapTupleMayBeUpdated;
-
-out_locked:
-	UnlockReleaseBuffer(buf);
-
-	if (vmbuffer != InvalidBuffer)
-		ReleaseBuffer(vmbuffer);
-
-	return result;
-
-}
-
-/*
- * heap_lock_updated_tuple
- *		Follow update chain when locking an updated tuple, acquiring locks (row
- *		marks) on the updated versions.
- *
- * The initial tuple is assumed to be already locked.
- *
- * This function doesn't check visibility, it just unconditionally marks the
- * tuple(s) as locked.  If any tuple in the updated chain is being deleted
- * concurrently (or updated with the key being modified), sleep until the
- * transaction doing it is finished.
- *
- * Note that we don't acquire heavyweight tuple locks on the tuples we walk
- * when we have to wait for other transactions to release them, as opposed to
- * what heap_lock_tuple does.  The reason is that having more than one
- * transaction walking the chain is probably uncommon enough that risk of
- * starvation is not likely: one of the preconditions for being here is that
- * the snapshot in use predates the update that created this tuple (because we
- * started at an earlier version of the tuple), but at the same time such a
- * transaction cannot be using repeatable read or serializable isolation
- * levels, because that would lead to a serializability failure.
- */
-static HTSU_Result
-heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid,
-						TransactionId xid, LockTupleMode mode)
-{
-	if (!ItemPointerEquals(&tuple->t_self, ctid))
-	{
-		/*
-		 * If this is the first possibly-multixact-able operation in the
-		 * current transaction, set my per-backend OldestMemberMXactId
-		 * setting. We can be certain that the transaction will never become a
-		 * member of any older MultiXactIds than that.  (We have to do this
-		 * even if we end up just using our own TransactionId below, since
-		 * some other backend could incorporate our XID into a MultiXact
-		 * immediately afterwards.)
-		 */
-		MultiXactIdSetOldestMember();
-
-		return heap_lock_updated_tuple_rec(rel, ctid, xid, mode);
-	}
-
-	/* nothing to lock */
-	return HeapTupleMayBeUpdated;
-}
-
-/*
- *	heap_finish_speculative - mark speculative insertion as successful
- *
- * To successfully finish a speculative insertion we have to clear speculative
- * token from tuple.  To do so the t_ctid field, which will contain a
- * speculative token value, is modified in place to point to the tuple itself,
- * which is characteristic of a newly inserted ordinary tuple.
- *
- * NB: It is not ok to commit without either finishing or aborting a
- * speculative insertion.  We could treat speculative tuples of committed
- * transactions implicitly as completed, but then we would have to be prepared
- * to deal with speculative tokens on committed tuples.  That wouldn't be
- * difficult - no-one looks at the ctid field of a tuple with invalid xmax -
- * but clearing the token at completion isn't very expensive either.
- * An explicit confirmation WAL record also makes logical decoding simpler.
- */
-void
-heap_finish_speculative(Relation relation, HeapTuple tuple)
-{
-	Buffer		buffer;
-	Page		page;
-	OffsetNumber offnum;
-	ItemId		lp = NULL;
-	HeapTupleHeader htup;
-
-	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&(tuple->t_self)));
-	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
-	page = (Page) BufferGetPage(buffer);
-
-	offnum = ItemPointerGetOffsetNumber(&(tuple->t_self));
-	if (PageGetMaxOffsetNumber(page) >= offnum)
-		lp = PageGetItemId(page, offnum);
-
-	if (PageGetMaxOffsetNumber(page) < offnum || !ItemIdIsNormal(lp))
-		elog(ERROR, "invalid lp");
-
-	htup = (HeapTupleHeader) PageGetItem(page, lp);
-
-	/* SpecTokenOffsetNumber should be distinguishable from any real offset */
-	StaticAssertStmt(MaxOffsetNumber < SpecTokenOffsetNumber,
-					 "invalid speculative token constant");
-
-	/* NO EREPORT(ERROR) from here till changes are logged */
-	START_CRIT_SECTION();
-
-	Assert(HeapTupleHeaderIsSpeculative(tuple->t_data));
-
-	MarkBufferDirty(buffer);
-
-	/*
-	 * Replace the speculative insertion token with a real t_ctid, pointing to
-	 * itself like it does on regular tuples.
-	 */
-	htup->t_ctid = tuple->t_self;
-
-	/* XLOG stuff */
-	if (RelationNeedsWAL(relation))
-	{
-		xl_heap_confirm xlrec;
-		XLogRecPtr	recptr;
-
-		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
-
-		XLogBeginInsert();
-
-		/* We want the same filtering on this as on a plain insert */
-		XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
-
-		XLogRegisterData((char *) &xlrec, SizeOfHeapConfirm);
-		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
-
-		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_CONFIRM);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	UnlockReleaseBuffer(buffer);
-}
-
-/*
- *	heap_abort_speculative - kill a speculatively inserted tuple
- *
- * Marks a tuple that was speculatively inserted in the same command as dead,
- * by setting its xmin as invalid.  That makes it immediately appear as dead
- * to all transactions, including our own.  In particular, it makes
- * HeapTupleSatisfiesDirty() regard the tuple as dead, so that another backend
- * inserting a duplicate key value won't unnecessarily wait for our whole
- * transaction to finish (it'll just wait for our speculative insertion to
- * finish).
- *
- * Killing the tuple prevents "unprincipled deadlocks", which are deadlocks
- * that arise due to a mutual dependency that is not user visible.  By
- * definition, unprincipled deadlocks cannot be prevented by the user
- * reordering lock acquisition in client code, because the implementation level
- * lock acquisitions are not under the user's direct control.  If speculative
- * inserters did not take this precaution, then under high concurrency they
- * could deadlock with each other, which would not be acceptable.
- *
- * This is somewhat redundant with heap_delete, but we prefer to have a
- * dedicated routine with stripped down requirements.  Note that this is also
- * used to delete the TOAST tuples created during speculative insertion.
- *
- * This routine does not affect logical decoding as it only looks at
- * confirmation records.
- */
-void
-heap_abort_speculative(Relation relation, HeapTuple tuple)
-{
-	TransactionId xid = GetCurrentTransactionId();
-	ItemPointer tid = &(tuple->t_self);
-	ItemId		lp;
-	HeapTupleData tp;
-	Page		page;
-	BlockNumber block;
-	Buffer		buffer;
-
-	Assert(ItemPointerIsValid(tid));
-
-	block = ItemPointerGetBlockNumber(tid);
-	buffer = ReadBuffer(relation, block);
-	page = BufferGetPage(buffer);
-
-	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
-
-	/*
-	 * Page can't be all visible, we just inserted into it, and are still
-	 * running.
-	 */
-	Assert(!PageIsAllVisible(page));
-
-	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
-	Assert(ItemIdIsNormal(lp));
-
-	tp.t_tableOid = RelationGetRelid(relation);
-	tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	tp.t_len = ItemIdGetLength(lp);
-	tp.t_self = *tid;
-
-	/*
-	 * Sanity check that the tuple really is a speculatively inserted tuple,
-	 * inserted by us.
-	 */
-	if (tp.t_data->t_choice.t_heap.t_xmin != xid)
-		elog(ERROR, "attempted to kill a tuple inserted by another transaction");
-	if (!(IsToastRelation(relation) || HeapTupleHeaderIsSpeculative(tp.t_data)))
-		elog(ERROR, "attempted to kill a non-speculative tuple");
-	Assert(!HeapTupleHeaderIsHeapOnly(tp.t_data));
+					new_infomask |= HEAP_XMAX_EXCL_LOCK;
+					break;
+				case LockTupleExclusive:
+					new_xmax = add_to_xmax;
+					new_infomask |= HEAP_XMAX_EXCL_LOCK;
+					new_infomask2 |= HEAP_KEYS_UPDATED;
+					break;
+				default:
+					new_xmax = InvalidTransactionId;	/* silence compiler */
+					elog(ERROR, "invalid lock mode");
+			}
+		}
+	}
+	else if (old_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		MultiXactStatus new_status;
 
-	/*
-	 * No need to check for serializable conflicts here.  There is never a
-	 * need for a combocid, either.  No need to extract replica identity, or
-	 * do anything special with infomask bits.
-	 */
+		/*
+		 * Currently we don't allow XMAX_COMMITTED to be set for multis, so
+		 * cross-check.
+		 */
+		Assert(!(old_infomask & HEAP_XMAX_COMMITTED));
 
-	START_CRIT_SECTION();
+		/*
+		 * A multixact together with LOCK_ONLY set but neither lock bit set
+		 * (i.e. a pg_upgraded share locked tuple) cannot possibly be running
+		 * anymore.  This check is critical for databases upgraded by
+		 * pg_upgrade; both MultiXactIdIsRunning and MultiXactIdExpand assume
+		 * that such multis are never passed.
+		 */
+		if (HEAP_LOCKED_UPGRADED(old_infomask))
+		{
+			old_infomask &= ~HEAP_XMAX_IS_MULTI;
+			old_infomask |= HEAP_XMAX_INVALID;
+			goto l5;
+		}
 
-	/*
-	 * The tuple will become DEAD immediately.  Flag that this page
-	 * immediately is a candidate for pruning by setting xmin to
-	 * RecentGlobalXmin.  That's not pretty, but it doesn't seem worth
-	 * inventing a nicer API for this.
-	 */
-	Assert(TransactionIdIsValid(RecentGlobalXmin));
-	PageSetPrunable(page, RecentGlobalXmin);
+		/*
+		 * If the XMAX is already a MultiXactId, then we need to expand it to
+		 * include add_to_xmax; but if all the members were lockers and are
+		 * all gone, we can do away with the IS_MULTI bit and just set
+		 * add_to_xmax as the only locker/updater.  If all lockers are gone
+		 * and we have an updater that aborted, we can also do without a
+		 * multi.
+		 *
+		 * The cost of doing GetMultiXactIdMembers would be paid by
+		 * MultiXactIdExpand if we weren't to do this, so this check is not
+		 * incurring extra work anyhow.
+		 */
+		if (!MultiXactIdIsRunning(xmax, HEAP_XMAX_IS_LOCKED_ONLY(old_infomask)))
+		{
+			if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) ||
+				!TransactionIdDidCommit(MultiXactIdGetUpdateXid(xmax,
+																old_infomask)))
+			{
+				/*
+				 * Reset these bits and restart; otherwise fall through to
+				 * create a new multi below.
+				 */
+				old_infomask &= ~HEAP_XMAX_IS_MULTI;
+				old_infomask |= HEAP_XMAX_INVALID;
+				goto l5;
+			}
+		}
 
-	/* store transaction information of xact deleting the tuple */
-	tp.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
-	tp.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+		new_status = get_mxact_status_for_lock(mode, is_update);
 
-	/*
-	 * Set the tuple header xmin to InvalidTransactionId.  This makes the
-	 * tuple immediately invisible everyone.  (In particular, to any
-	 * transactions waiting on the speculative token, woken up later.)
-	 */
-	HeapTupleHeaderSetXmin(tp.t_data, InvalidTransactionId);
+		new_xmax = MultiXactIdExpand((MultiXactId) xmax, add_to_xmax,
+									 new_status);
+		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
+	}
+	else if (old_infomask & HEAP_XMAX_COMMITTED)
+	{
+		/*
+		 * It's a committed update, so we need to preserve him as updater of
+		 * the tuple.
+		 */
+		MultiXactStatus status;
+		MultiXactStatus new_status;
 
-	/* Clear the speculative insertion token too */
-	tp.t_data->t_ctid = tp.t_self;
+		if (old_infomask2 & HEAP_KEYS_UPDATED)
+			status = MultiXactStatusUpdate;
+		else
+			status = MultiXactStatusNoKeyUpdate;
 
-	MarkBufferDirty(buffer);
+		new_status = get_mxact_status_for_lock(mode, is_update);
 
-	/*
-	 * XLOG stuff
-	 *
-	 * The WAL records generated here match heap_delete().  The same recovery
-	 * routines are used.
-	 */
-	if (RelationNeedsWAL(relation))
+		/*
+		 * since it's not running, it's obviously impossible for the old
+		 * updater to be identical to the current one, so we need not check
+		 * for that case as we do in the block above.
+		 */
+		new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
+		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
+	}
+	else if (TransactionIdIsInProgress(xmax))
 	{
-		xl_heap_delete xlrec;
-		XLogRecPtr	recptr;
+		/*
+		 * If the XMAX is a valid, in-progress TransactionId, then we need to
+		 * create a new MultiXactId that includes both the old locker or
+		 * updater and our own TransactionId.
+		 */
+		MultiXactStatus new_status;
+		MultiXactStatus old_status;
+		LockTupleMode old_mode;
 
-		xlrec.flags = XLH_DELETE_IS_SUPER;
-		xlrec.infobits_set = compute_infobits(tp.t_data->t_infomask,
-											  tp.t_data->t_infomask2);
-		xlrec.offnum = ItemPointerGetOffsetNumber(&tp.t_self);
-		xlrec.xmax = xid;
+		if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
+		{
+			if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
+				old_status = MultiXactStatusForKeyShare;
+			else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
+				old_status = MultiXactStatusForShare;
+			else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
+			{
+				if (old_infomask2 & HEAP_KEYS_UPDATED)
+					old_status = MultiXactStatusForUpdate;
+				else
+					old_status = MultiXactStatusForNoKeyUpdate;
+			}
+			else
+			{
+				/*
+				 * LOCK_ONLY can be present alone only when a page has been
+				 * upgraded by pg_upgrade.  But in that case,
+				 * TransactionIdIsInProgress() should have returned false.  We
+				 * assume it's no longer locked in this case.
+				 */
+				elog(WARNING, "LOCK_ONLY found for Xid in progress %u", xmax);
+				old_infomask |= HEAP_XMAX_INVALID;
+				old_infomask &= ~HEAP_XMAX_LOCK_ONLY;
+				goto l5;
+			}
+		}
+		else
+		{
+			/* it's an update, but which kind? */
+			if (old_infomask2 & HEAP_KEYS_UPDATED)
+				old_status = MultiXactStatusUpdate;
+			else
+				old_status = MultiXactStatusNoKeyUpdate;
+		}
 
-		XLogBeginInsert();
-		XLogRegisterData((char *) &xlrec, SizeOfHeapDelete);
-		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
+		old_mode = TUPLOCK_from_mxstatus(old_status);
+
+		/*
+		 * If the lock to be acquired is for the same TransactionId as the
+		 * existing lock, there's an optimization possible: consider only the
+		 * strongest of both locks as the only one present, and restart.
+		 */
+		if (xmax == add_to_xmax)
+		{
+			/*
+			 * Note that it's not possible for the original tuple to be
+			 * updated: we wouldn't be here because the tuple would have been
+			 * invisible and we wouldn't try to update it.  As a subtlety,
+			 * this code can also run when traversing an update chain to lock
+			 * future versions of a tuple.  But we wouldn't be here either,
+			 * because the add_to_xmax would be different from the original
+			 * updater.
+			 */
+			Assert(HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
 
-		/* No replica identity & replication origin logged */
+			/* acquire the strongest of both */
+			if (mode < old_mode)
+				mode = old_mode;
+			/* mustn't touch is_update */
 
-		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_DELETE);
+			old_infomask |= HEAP_XMAX_INVALID;
+			goto l5;
+		}
 
-		PageSetLSN(page, recptr);
+		/* otherwise, just fall back to creating a new multixact */
+		new_status = get_mxact_status_for_lock(mode, is_update);
+		new_xmax = MultiXactIdCreate(xmax, old_status,
+									 add_to_xmax, new_status);
+		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
 	}
+	else if (!HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) &&
+			 TransactionIdDidCommit(xmax))
+	{
+		/*
+		 * It's a committed update, so we gotta preserve him as updater of the
+		 * tuple.
+		 */
+		MultiXactStatus status;
+		MultiXactStatus new_status;
 
-	END_CRIT_SECTION();
+		if (old_infomask2 & HEAP_KEYS_UPDATED)
+			status = MultiXactStatusUpdate;
+		else
+			status = MultiXactStatusNoKeyUpdate;
 
-	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+		new_status = get_mxact_status_for_lock(mode, is_update);
 
-	if (HeapTupleHasExternal(&tp))
+		/*
+		 * since it's not running, it's obviously impossible for the old
+		 * updater to be identical to the current one, so we need not check
+		 * for that case as we do in the block above.
+		 */
+		new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
+		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
+	}
+	else
 	{
-		Assert(!IsToastRelation(relation));
-		toast_delete(relation, &tp, true);
+		/*
+		 * Can get here iff the locking/updating transaction was running when
+		 * the infomask was extracted from the tuple, but finished before
+		 * TransactionIdIsInProgress got to run.  Deal with it as if there was
+		 * no locker at all in the first place.
+		 */
+		old_infomask |= HEAP_XMAX_INVALID;
+		goto l5;
 	}
 
-	/*
-	 * Never need to mark tuple for invalidation, since catalogs don't support
-	 * speculative insertion
-	 */
+	*result_infomask = new_infomask;
+	*result_infomask2 = new_infomask2;
+	*result_xmax = new_xmax;
+}
 
-	/* Now we can release the buffer */
-	ReleaseBuffer(buffer);
 
-	/* count deletion, as we counted the insertion too */
-	pgstat_count_heap_delete(relation);
-}
 
 /*
  * heap_inplace_update - update a tuple "in place" (ie, overwrite it)
@@ -6993,7 +4942,7 @@ HeapTupleGetUpdateXid(HeapTupleHeader tuple)
  *
  * The passed infomask pairs up with the given multixact in the tuple header.
  */
-static bool
+bool
 DoesMultiXactIdConflict(MultiXactId multi, uint16 infomask,
 						LockTupleMode lockmode)
 {
@@ -7160,7 +5109,7 @@ Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
  * We return (in *remaining, if not NULL) the number of members that are still
  * running, including any (non-aborted) subtransactions of our own transaction.
  */
-static void
+void
 MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
 				Relation rel, ItemPointer ctid, XLTW_Oper oper,
 				int *remaining)
@@ -7182,7 +5131,7 @@ MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
  * We return (in *remaining, if not NULL) the number of members that are still
  * running, including any (non-aborted) subtransactions of our own transaction.
  */
-static bool
+bool
 ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
 						   uint16 infomask, Relation rel, int *remaining)
 {
@@ -7744,7 +5693,7 @@ log_heap_update(Relation reln, Buffer oldbuf,
  * This is only used in wal_level >= WAL_LEVEL_LOGICAL, and only for catalog
  * tuples.
  */
-static XLogRecPtr
+XLogRecPtr
 log_heap_new_cid(Relation relation, HeapTuple tup)
 {
 	xl_heap_new_cid xlrec;
@@ -9121,46 +7070,6 @@ heap2_redo(XLogReaderState *record)
 }
 
 /*
- *	heap_sync		- sync a heap, for use when no WAL has been written
- *
- * This forces the heap contents (including TOAST heap if any) down to disk.
- * If we skipped using WAL, and WAL is otherwise needed, we must force the
- * relation down to disk before it's safe to commit the transaction.  This
- * requires writing out any dirty buffers and then doing a forced fsync.
- *
- * Indexes are not touched.  (Currently, index operations associated with
- * the commands that use this are WAL-logged and so do not need fsync.
- * That behavior might change someday, but in any case it's likely that
- * any fsync decisions required would be per-index and hence not appropriate
- * to be done here.)
- */
-void
-heap_sync(Relation rel)
-{
-	/* non-WAL-logged tables never need fsync */
-	if (!RelationNeedsWAL(rel))
-		return;
-
-	/* main heap */
-	FlushRelationBuffers(rel);
-	/* FlushRelationBuffers will have opened rd_smgr */
-	smgrimmedsync(rel->rd_smgr, MAIN_FORKNUM);
-
-	/* FSM is not critical, don't bother syncing it */
-
-	/* toast heap, if any */
-	if (OidIsValid(rel->rd_rel->reltoastrelid))
-	{
-		Relation	toastrel;
-
-		toastrel = heap_open(rel->rd_rel->reltoastrelid, AccessShareLock);
-		FlushRelationBuffers(toastrel);
-		smgrimmedsync(toastrel->rd_smgr, MAIN_FORKNUM);
-		heap_close(toastrel, AccessShareLock);
-	}
-}
-
-/*
  * Mask a heap page before performing consistency checks on it.
  */
 void
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 7d7ac75..a0e3272 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -50,7 +50,13 @@
 
 /* local functions */
 static bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
-
+static bool heap_fetch(Relation relation,
+					   ItemPointer tid,
+					   Snapshot snapshot,
+					   HeapTuple tuple,
+					   Buffer *userbuf,
+					   bool keep_buf,
+					   Relation stats_relation);
 /*-------------------------------------------------------------------------
  *
  * POSTGRES "time qualification" code, ie, tuple visibility rules.
@@ -1660,10 +1666,2149 @@ HeapTupleSatisfiesHistoricMVCC(StorageTuple stup, Snapshot snapshot,
 		return true;
 }
 
-Datum
-heapam_storage_handler(PG_FUNCTION_ARGS)
+
+/*
+ *	heap_fetch		- retrieve tuple with given tid
+ *
+ * On entry, tuple->t_self is the TID to fetch.  We pin the buffer holding
+ * the tuple, fill in the remaining fields of *tuple, and check the tuple
+ * against the specified snapshot.
+ *
+ * If successful (tuple found and passes snapshot time qual), then *userbuf
+ * is set to the buffer holding the tuple and TRUE is returned.  The caller
+ * must unpin the buffer when done with the tuple.
+ *
+ * If the tuple is not found (ie, item number references a deleted slot),
+ * then tuple->t_data is set to NULL and FALSE is returned.
+ *
+ * If the tuple is found but fails the time qual check, then FALSE is returned
+ * but tuple->t_data is left pointing to the tuple.
+ *
+ * keep_buf determines what is done with the buffer in the FALSE-result cases.
+ * When the caller specifies keep_buf = true, we retain the pin on the buffer
+ * and return it in *userbuf (so the caller must eventually unpin it); when
+ * keep_buf = false, the pin is released and *userbuf is set to InvalidBuffer.
+ *
+ * stats_relation is the relation to charge the heap_fetch operation against
+ * for statistical purposes.  (This could be the heap rel itself, an
+ * associated index, or NULL to not count the fetch at all.)
+ *
+ * heap_fetch does not follow HOT chains: only the exact TID requested will
+ * be fetched.
+ *
+ * It is somewhat inconsistent that we ereport() on invalid block number but
+ * return false on invalid item number.  There are a couple of reasons though.
+ * One is that the caller can relatively easily check the block number for
+ * validity, but cannot check the item number without reading the page
+ * himself.  Another is that when we are following a t_ctid link, we can be
+ * reasonably confident that the page number is valid (since VACUUM shouldn't
+ * truncate off the destination page without having killed the referencing
+ * tuple first), but the item number might well not be good.
+ */
+static bool
+heap_fetch(Relation relation,
+		   ItemPointer tid,
+		   Snapshot snapshot,
+		   HeapTuple tuple,
+		   Buffer *userbuf,
+		   bool keep_buf,
+		   Relation stats_relation)
 {
-	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
+	ItemId		lp;
+	Buffer		buffer;
+	Page		page;
+	OffsetNumber offnum;
+	bool		valid;
+
+	/*
+	 * Fetch and pin the appropriate page of the relation.
+	 */
+	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
+
+	/*
+	 * Need share lock on buffer to examine tuple commit status.
+	 */
+	LockBuffer(buffer, BUFFER_LOCK_SHARE);
+	page = BufferGetPage(buffer);
+	TestForOldSnapshot(snapshot, relation, page);
+
+	/*
+	 * We'd better check for out-of-range offnum in case of VACUUM since the
+	 * TID was obtained.
+	 */
+	offnum = ItemPointerGetOffsetNumber(tid);
+	if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
+	{
+		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+		if (keep_buf)
+			*userbuf = buffer;
+		else
+		{
+			ReleaseBuffer(buffer);
+			*userbuf = InvalidBuffer;
+		}
+		return false;
+	}
+
+	/*
+	 * get the item line pointer corresponding to the requested tid
+	 */
+	lp = PageGetItemId(page, offnum);
+
+	/*
+	 * Must check for deleted tuple.
+	 */
+	if (!ItemIdIsNormal(lp))
+	{
+		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+		if (keep_buf)
+			*userbuf = buffer;
+		else
+		{
+			ReleaseBuffer(buffer);
+			*userbuf = InvalidBuffer;
+		}
+		return false;
+	}
+
+	/*
+	 * 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);
+
+	/*
+	 * check time qualification of tuple, then release lock
+	 */
+	valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, tuple, snapshot, buffer);
+
+	if (valid)
+		PredicateLockTuple(relation, tuple, snapshot);
+
+	CheckForSerializableConflictOut(valid, relation, tuple, buffer, snapshot);
+
+	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+
+	if (valid)
+	{
+		/*
+		 * All checks passed, so return the tuple as valid. Caller is now
+		 * responsible for releasing the buffer.
+		 */
+		*userbuf = buffer;
+
+		/* Count the successful fetch against appropriate rel, if any */
+		if (stats_relation != NULL)
+			pgstat_count_heap_fetch(stats_relation);
+
+		return true;
+	}
+
+	/* Tuple failed time qual, but maybe caller wants to see it anyway. */
+	if (keep_buf)
+		*userbuf = buffer;
+	else
+	{
+		ReleaseBuffer(buffer);
+		*userbuf = InvalidBuffer;
+	}
+
+	return false;
+}
+
+
+
+
+
+/* ----------------------------------------------------------------
+ *				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)
+{
+	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);
+
+	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,
+				   CommandId cid, Snapshot crosscheck, bool wait,
+				   HeapUpdateFailureData *hufd, LockTupleMode *lockmode)
+{
+	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);
+
+	return result;
+}
+
+static void
+heapam_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);
+
+	offnum = ItemPointerGetOffsetNumber(&(tuple->t_self));
+	if (PageGetMaxOffsetNumber(page) >= offnum)
+		lp = PageGetItemId(page, offnum);
+
+	if (PageGetMaxOffsetNumber(page) < offnum || !ItemIdIsNormal(lp))
+		elog(ERROR, "invalid lp");
+
+	htup = (HeapTupleHeader) PageGetItem(page, lp);
+
+	/* SpecTokenOffsetNumber should be distinguishable from any real offset */
+	StaticAssertStmt(MaxOffsetNumber < SpecTokenOffsetNumber,
+					 "invalid speculative token constant");
+
+	/* NO EREPORT(ERROR) from here till changes are logged */
+	START_CRIT_SECTION();
+
+	Assert(HeapTupleHeaderIsSpeculative(tuple->t_data));
+
+	MarkBufferDirty(buffer);
+
+	/*
+	 * Replace the speculative insertion token with a real t_ctid, pointing to
+	 * itself like it does on regular tuples.
+	 */
+	htup->t_ctid = tuple->t_self;
+
+	/* XLOG stuff */
+	if (RelationNeedsWAL(relation))
+	{
+		xl_heap_confirm xlrec;
+		XLogRecPtr	recptr;
+
+		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
+
+		XLogBeginInsert();
+
+		/* We want the same filtering on this as on a plain insert */
+		XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
+
+		XLogRegisterData((char *) &xlrec, SizeOfHeapConfirm);
+		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
+
+		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_CONFIRM);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(buffer);
+	slot->tts_speculativeToken = 0;
+}
+
+static void
+heapam_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;
+	HeapTupleData tp;
+	Page		page;
+	BlockNumber block;
+	Buffer		buffer;
+
+	/*Assert(slot->tts_speculativeToken != 0); This needs some update in toast */
+	Assert(ItemPointerIsValid(tid));
+
+	block = ItemPointerGetBlockNumber(tid);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
+
+	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+
+	/*
+	 * Page can't be all visible, we just inserted into it, and are still
+	 * running.
+	 */
+	Assert(!PageIsAllVisible(page));
+
+	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
+	Assert(ItemIdIsNormal(lp));
+
+	tp.t_tableOid = RelationGetRelid(relation);
+	tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	tp.t_len = ItemIdGetLength(lp);
+	tp.t_self = *tid;
+
+	/*
+	 * Sanity check that the tuple really is a speculatively inserted tuple,
+	 * inserted by us.
+	 */
+	if (tp.t_data->t_choice.t_heap.t_xmin != xid)
+		elog(ERROR, "attempted to kill a tuple inserted by another transaction");
+	if (!(IsToastRelation(relation) || HeapTupleHeaderIsSpeculative(tp.t_data)))
+		elog(ERROR, "attempted to kill a non-speculative tuple");
+	Assert(!HeapTupleHeaderIsHeapOnly(tp.t_data));
+
+	/*
+	 * No need to check for serializable conflicts here.  There is never a
+	 * need for a combocid, either.  No need to extract replica identity, or
+	 * do anything special with infomask bits.
+	 */
+
+	START_CRIT_SECTION();
+
+	/*
+	 * The tuple will become DEAD immediately.  Flag that this page
+	 * immediately is a candidate for pruning by setting xmin to
+	 * RecentGlobalXmin.  That's not pretty, but it doesn't seem worth
+	 * inventing a nicer API for this.
+	 */
+	Assert(TransactionIdIsValid(RecentGlobalXmin));
+	PageSetPrunable(page, RecentGlobalXmin);
+
+	/* store transaction information of xact deleting the tuple */
+	tp.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+	tp.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+
+	/*
+	 * Set the tuple header xmin to InvalidTransactionId.  This makes the
+	 * tuple immediately invisible everyone.  (In particular, to any
+	 * transactions waiting on the speculative token, woken up later.)
+	 */
+	HeapTupleHeaderSetXmin(tp.t_data, InvalidTransactionId);
+
+	/* Clear the speculative insertion token too */
+	tp.t_data->t_ctid = tp.t_self;
+
+	MarkBufferDirty(buffer);
+
+	/*
+	 * XLOG stuff
+	 *
+	 * The WAL records generated here match heap_delete().  The same recovery
+	 * routines are used.
+	 */
+	if (RelationNeedsWAL(relation))
+	{
+		xl_heap_delete xlrec;
+		XLogRecPtr	recptr;
+
+		xlrec.flags = XLH_DELETE_IS_SUPER;
+		xlrec.infobits_set = compute_infobits(tp.t_data->t_infomask,
+											  tp.t_data->t_infomask2);
+		xlrec.offnum = ItemPointerGetOffsetNumber(&tp.t_self);
+		xlrec.xmax = xid;
+
+		XLogBeginInsert();
+		XLogRegisterData((char *) &xlrec, SizeOfHeapDelete);
+		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
+
+		/* No replica identity & replication origin logged */
+
+		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_DELETE);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+
+	if (HeapTupleHasExternal(&tp))
+	{
+		Assert(!IsToastRelation(relation));
+		toast_delete(relation, &tp, true);
+	}
+
+	/*
+	 * Never need to mark tuple for invalidation, since catalogs don't support
+	 * speculative insertion
+	 */
+
+	/* Now we can release the buffer */
+	ReleaseBuffer(buffer);
+
+	/* count deletion, as we counted the insertion too */
+	pgstat_count_heap_delete(relation);
+	slot->tts_speculativeToken = 0;
+}
+
+/*
+ *	heapam_multi_insert	- insert multiple tuple into a heap
+ *
+ * This is like heap_insert(), but inserts multiple tuples in one operation.
+ * That's faster than calling heap_insert() in a loop, because when multiple
+ * tuples can be inserted on a single page, we can write just a single WAL
+ * record covering all of them, and only need to lock/unlock the page once.
+ *
+ * Note: this leaks memory into the current memory context. You can create a
+ * temporary context before calling this, if that's a problem.
+ */
+static void
+heapam_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
+				  CommandId cid, int options, BulkInsertState bistate)
+{
+	TransactionId xid = GetCurrentTransactionId();
+	HeapTuple  *heaptuples;
+	int			i;
+	int			ndone;
+	char	   *scratch = NULL;
+	Page		page;
+	bool		needwal;
+	Size		saveFreeSpace;
+	bool		need_tuple_data = RelationIsLogicallyLogged(relation);
+	bool		need_cids = RelationIsAccessibleInLogicalDecoding(relation);
+
+	needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation);
+	saveFreeSpace = RelationGetTargetPageFreeSpace(relation,
+												   HEAP_DEFAULT_FILLFACTOR);
+
+	/* Toast and set header data in all the tuples */
+	heaptuples = palloc(ntuples * sizeof(HeapTuple));
+	for (i = 0; i < ntuples; i++)
+		heaptuples[i] = heap_prepare_insert(relation, tuples[i],
+											xid, cid, options);
+
+	/*
+	 * Allocate some memory to use for constructing the WAL record. Using
+	 * palloc() within a critical section is not safe, so we allocate this
+	 * beforehand.
+	 */
+	if (needwal)
+		scratch = palloc(BLCKSZ);
+
+	/*
+	 * We're about to do the actual inserts -- but check for conflict first,
+	 * to minimize the possibility of having to roll back work we've just
+	 * done.
+	 *
+	 * A check here does not definitively prevent a serialization anomaly;
+	 * that check MUST be done at least past the point of acquiring an
+	 * exclusive buffer content lock on every buffer that will be affected,
+	 * and MAY be done after all inserts are reflected in the buffers and
+	 * those locks are released; otherwise there race condition.  Since
+	 * multiple buffers can be locked and unlocked in the loop below, and it
+	 * would not be feasible to identify and lock all of those buffers before
+	 * the loop, we must do a final check at the end.
+	 *
+	 * The check here could be omitted with no loss of correctness; it is
+	 * present strictly as an optimization.
+	 *
+	 * For heap inserts, we only need to check for table-level SSI locks. Our
+	 * new tuples can't possibly conflict with existing tuple locks, and heap
+	 * page locks are only consolidated versions of tuple locks; they do not
+	 * lock "gaps" as index page locks do.  So we don't need to specify a
+	 * buffer when making the call, which makes for a faster check.
+	 */
+	CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
+
+	ndone = 0;
+	while (ndone < ntuples)
+	{
+		Buffer		buffer;
+		Buffer		vmbuffer = InvalidBuffer;
+		bool		all_visible_cleared = false;
+		int			nthispage;
+
+		CHECK_FOR_INTERRUPTS();
+
+		/*
+		 * Find buffer where at least the next tuple will fit.  If the page is
+		 * all-visible, this will also pin the requisite visibility map page.
+		 */
+		buffer = RelationGetBufferForTuple(relation, heaptuples[ndone]->t_len,
+										   InvalidBuffer, options, bistate,
+										   &vmbuffer, NULL);
+		page = BufferGetPage(buffer);
+
+		/* NO EREPORT(ERROR) from here till changes are logged */
+		START_CRIT_SECTION();
+
+		/*
+		 * RelationGetBufferForTuple has ensured that the first tuple fits.
+		 * Put that on the page, and then as many other tuples as fit.
+		 */
+		RelationPutHeapTuple(relation, buffer, heaptuples[ndone], false);
+		for (nthispage = 1; ndone + nthispage < ntuples; nthispage++)
+		{
+			HeapTuple	heaptup = heaptuples[ndone + nthispage];
+
+			if (PageGetHeapFreeSpace(page) < MAXALIGN(heaptup->t_len) + saveFreeSpace)
+				break;
+
+			RelationPutHeapTuple(relation, buffer, heaptup, false);
+
+			/*
+			 * We don't use heap_multi_insert for catalog tuples yet, but
+			 * better be prepared...
+			 */
+			if (needwal && need_cids)
+				log_heap_new_cid(relation, heaptup);
+		}
+
+		if (PageIsAllVisible(page))
+		{
+			all_visible_cleared = true;
+			PageClearAllVisible(page);
+			visibilitymap_clear(relation,
+								BufferGetBlockNumber(buffer),
+								vmbuffer, VISIBILITYMAP_VALID_BITS);
+		}
+
+		/*
+		 * XXX Should we set PageSetPrunable on this page ? See heap_insert()
+		 */
+
+		MarkBufferDirty(buffer);
+
+		/* XLOG stuff */
+		if (needwal)
+		{
+			XLogRecPtr	recptr;
+			xl_heap_multi_insert *xlrec;
+			uint8		info = XLOG_HEAP2_MULTI_INSERT;
+			char	   *tupledata;
+			int			totaldatalen;
+			char	   *scratchptr = scratch;
+			bool		init;
+			int			bufflags = 0;
+
+			/*
+			 * If the page was previously empty, we can reinit the page
+			 * instead of restoring the whole thing.
+			 */
+			init = (ItemPointerGetOffsetNumber(&(heaptuples[ndone]->t_self)) == FirstOffsetNumber &&
+					PageGetMaxOffsetNumber(page) == FirstOffsetNumber + nthispage - 1);
+
+			/* allocate xl_heap_multi_insert struct from the scratch area */
+			xlrec = (xl_heap_multi_insert *) scratchptr;
+			scratchptr += SizeOfHeapMultiInsert;
+
+			/*
+			 * Allocate offsets array. Unless we're reinitializing the page,
+			 * in that case the tuples are stored in order starting at
+			 * FirstOffsetNumber and we don't need to store the offsets
+			 * explicitly.
+			 */
+			if (!init)
+				scratchptr += nthispage * sizeof(OffsetNumber);
+
+			/* the rest of the scratch space is used for tuple data */
+			tupledata = scratchptr;
+
+			xlrec->flags = all_visible_cleared ? XLH_INSERT_ALL_VISIBLE_CLEARED : 0;
+			xlrec->ntuples = nthispage;
+
+			/*
+			 * Write out an xl_multi_insert_tuple and the tuple data itself
+			 * for each tuple.
+			 */
+			for (i = 0; i < nthispage; i++)
+			{
+				HeapTuple	heaptup = heaptuples[ndone + i];
+				xl_multi_insert_tuple *tuphdr;
+				int			datalen;
+
+				if (!init)
+					xlrec->offsets[i] = ItemPointerGetOffsetNumber(&heaptup->t_self);
+				/* xl_multi_insert_tuple needs two-byte alignment. */
+				tuphdr = (xl_multi_insert_tuple *) SHORTALIGN(scratchptr);
+				scratchptr = ((char *) tuphdr) + SizeOfMultiInsertTuple;
+
+				tuphdr->t_infomask2 = heaptup->t_data->t_infomask2;
+				tuphdr->t_infomask = heaptup->t_data->t_infomask;
+				tuphdr->t_hoff = heaptup->t_data->t_hoff;
+
+				/* write bitmap [+ padding] [+ oid] + data */
+				datalen = heaptup->t_len - SizeofHeapTupleHeader;
+				memcpy(scratchptr,
+					   (char *) heaptup->t_data + SizeofHeapTupleHeader,
+					   datalen);
+				tuphdr->datalen = datalen;
+				scratchptr += datalen;
+			}
+			totaldatalen = scratchptr - tupledata;
+			Assert((scratchptr - scratch) < BLCKSZ);
+
+			if (need_tuple_data)
+				xlrec->flags |= XLH_INSERT_CONTAINS_NEW_TUPLE;
+
+			/*
+			 * Signal that this is the last xl_heap_multi_insert record
+			 * emitted by this call to heap_multi_insert(). Needed for logical
+			 * decoding so it knows when to cleanup temporary data.
+			 */
+			if (ndone + nthispage == ntuples)
+				xlrec->flags |= XLH_INSERT_LAST_IN_MULTI;
+
+			if (init)
+			{
+				info |= XLOG_HEAP_INIT_PAGE;
+				bufflags |= REGBUF_WILL_INIT;
+			}
+
+			/*
+			 * If we're doing logical decoding, include the new tuple data
+			 * even if we take a full-page image of the page.
+			 */
+			if (need_tuple_data)
+				bufflags |= REGBUF_KEEP_DATA;
+
+			XLogBeginInsert();
+			XLogRegisterData((char *) xlrec, tupledata - scratch);
+			XLogRegisterBuffer(0, buffer, REGBUF_STANDARD | bufflags);
+
+			XLogRegisterBufData(0, tupledata, totaldatalen);
+
+			/* filtering by origin on a row level is much more efficient */
+			XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
+
+			recptr = XLogInsert(RM_HEAP2_ID, info);
+
+			PageSetLSN(page, recptr);
+		}
+
+		END_CRIT_SECTION();
+
+		UnlockReleaseBuffer(buffer);
+		if (vmbuffer != InvalidBuffer)
+			ReleaseBuffer(vmbuffer);
+
+		ndone += nthispage;
+	}
+
+	/*
+	 * We're done with the actual inserts.  Check for conflicts again, to
+	 * ensure that all rw-conflicts in to these inserts are detected.  Without
+	 * this final check, a sequential scan of the heap may have locked the
+	 * table after the "before" check, missing one opportunity to detect the
+	 * conflict, and then scanned the table before the new tuples were there,
+	 * missing the other chance to detect the conflict.
+	 *
+	 * For heap inserts, we only need to check for table-level SSI locks. Our
+	 * new tuples can't possibly conflict with existing tuple locks, and heap
+	 * page locks are only consolidated versions of tuple locks; they do not
+	 * lock "gaps" as index page locks do.  So we don't need to specify a
+	 * buffer when making the call.
+	 */
+	CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
+
+	/*
+	 * If tuples are cachable, mark them for invalidation from the caches in
+	 * case we abort.  Note it is OK to do this after releasing the buffer,
+	 * because the heaptuples data structure is all in local memory, not in
+	 * the shared buffer.
+	 */
+	if (IsCatalogRelation(relation))
+	{
+		for (i = 0; i < ntuples; i++)
+			CacheInvalidateHeapTuple(relation, heaptuples[i], NULL);
+	}
+
+	/*
+	 * Copy t_self fields back to the caller's original tuples. This does
+	 * nothing for untoasted tuples (tuples[i] == heaptuples[i)], but it's
+	 * probably faster to always copy than check.
+	 */
+	for (i = 0; i < ntuples; i++)
+		tuples[i]->t_self = heaptuples[i]->t_self;
+
+	pgstat_count_heap_insert(relation, ntuples);
+}
+
+/*
+ * Subroutine for heap_lock_updated_tuple_rec.
+ *
+ * Given a hypothetical multixact status held by the transaction identified
+ * with the given xid, does the current transaction need to wait, fail, or can
+ * it continue if it wanted to acquire a lock of the given mode?  "needwait"
+ * is set to true if waiting is necessary; if it can continue, then
+ * HeapTupleMayBeUpdated is returned.  If the lock is already held by the
+ * current transaction, return HeapTupleSelfUpdated.  In case of a conflict
+ * with another transaction, a different HeapTupleSatisfiesUpdate return code
+ * is returned.
+ *
+ * The held status is said to be hypothetical because it might correspond to a
+ * lock held by a single Xid, i.e. not a real MultiXactId; we express it this
+ * way for simplicity of API.
+ */
+static HTSU_Result
+test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid,
+						   LockTupleMode mode, bool *needwait)
+{
+	MultiXactStatus wantedstatus;
+
+	*needwait = false;
+	wantedstatus = get_mxact_status_for_lock(mode, false);
+
+	/*
+	 * Note: we *must* check TransactionIdIsInProgress before
+	 * TransactionIdDidAbort/Commit; see comment at top of tqual.c for an
+	 * explanation.
+	 */
+	if (TransactionIdIsCurrentTransactionId(xid))
+	{
+		/*
+		 * The tuple has already been locked by our own transaction.  This is
+		 * very rare but can happen if multiple transactions are trying to
+		 * lock an ancient version of the same tuple.
+		 */
+		return HeapTupleSelfUpdated;
+	}
+	else if (TransactionIdIsInProgress(xid))
+	{
+		/*
+		 * If the locking transaction is running, what we do depends on
+		 * whether the lock modes conflict: if they do, then we must wait for
+		 * it to finish; otherwise we can fall through to lock this tuple
+		 * version without waiting.
+		 */
+		if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
+								LOCKMODE_from_mxstatus(wantedstatus)))
+		{
+			*needwait = true;
+		}
+
+		/*
+		 * If we set needwait above, then this value doesn't matter;
+		 * otherwise, this value signals to caller that it's okay to proceed.
+		 */
+		return HeapTupleMayBeUpdated;
+	}
+	else if (TransactionIdDidAbort(xid))
+		return HeapTupleMayBeUpdated;
+	else if (TransactionIdDidCommit(xid))
+	{
+		/*
+		 * The other transaction committed.  If it was only a locker, then the
+		 * lock is completely gone now and we can return success; but if it
+		 * was an update, then what we do depends on whether the two lock
+		 * modes conflict.  If they conflict, then we must report error to
+		 * caller. But if they don't, we can fall through to allow the current
+		 * transaction to lock the tuple.
+		 *
+		 * Note: the reason we worry about ISUPDATE here is because as soon as
+		 * a transaction ends, all its locks are gone and meaningless, and
+		 * thus we can ignore them; whereas its updates persist.  In the
+		 * TransactionIdIsInProgress case, above, we don't need to check
+		 * because we know the lock is still "alive" and thus a conflict needs
+		 * always be checked.
+		 */
+		if (!ISUPDATE_from_mxstatus(status))
+			return HeapTupleMayBeUpdated;
+
+		if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
+								LOCKMODE_from_mxstatus(wantedstatus)))
+			/* bummer */
+			return HeapTupleUpdated;
+
+		return HeapTupleMayBeUpdated;
+	}
+
+	/* Not in progress, not aborted, not committed -- must have crashed */
+	return HeapTupleMayBeUpdated;
+}
+
+
+/*
+ * Recursive part of heap_lock_updated_tuple
+ *
+ * Fetch the tuple pointed to by tid in rel, and mark it as locked by the given
+ * xid with the given mode; if this tuple is updated, recurse to lock the new
+ * version as well.
+ */
+static HTSU_Result
+heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid,
+							LockTupleMode mode)
+{
+	HTSU_Result result;
+	ItemPointerData tupid;
+	HeapTupleData 	mytup;
+	Buffer		buf;
+	uint16		new_infomask,
+				new_infomask2,
+				old_infomask,
+				old_infomask2;
+	TransactionId xmax,
+				new_xmax;
+	TransactionId priorXmax = InvalidTransactionId;
+	bool		cleared_all_frozen = false;
+	Buffer		vmbuffer = InvalidBuffer;
+	BlockNumber block;
+
+	ItemPointerCopy(tid, &tupid);
+
+	for (;;)
+	{
+		new_infomask = 0;
+		new_xmax = InvalidTransactionId;
+		block = ItemPointerGetBlockNumber(&tupid);
+
+		if (!heap_fetch(rel, &tupid, SnapshotAny, &mytup, &buf, false, NULL))
+		{
+			/*
+			 * if we fail to find the updated version of the tuple, it's
+			 * because it was vacuumed/pruned away after its creator
+			 * transaction aborted.  So behave as if we got to the end of the
+			 * chain, and there's no further tuple to lock: return success to
+			 * caller.
+			 */
+			return HeapTupleMayBeUpdated;
+		}
+
+l4:
+		CHECK_FOR_INTERRUPTS();
+
+		/*
+		 * Before locking the buffer, pin the visibility map page if it
+		 * appears to be necessary.  Since we haven't got the lock yet,
+		 * someone else might be in the middle of changing this, so we'll need
+		 * to recheck after we have the lock.
+		 */
+		if (PageIsAllVisible(BufferGetPage(buf)))
+			visibilitymap_pin(rel, block, &vmbuffer);
+		else
+			vmbuffer = InvalidBuffer;
+
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+		/*
+		 * If we didn't pin the visibility map page and the page has become
+		 * all visible while we were busy locking the buffer, we'll have to
+		 * unlock and re-lock, to avoid holding the buffer lock across I/O.
+		 * That's a bit unfortunate, but hopefully shouldn't happen often.
+		 */
+		if (vmbuffer == InvalidBuffer && PageIsAllVisible(BufferGetPage(buf)))
+		{
+			LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+			visibilitymap_pin(rel, block, &vmbuffer);
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+		}
+
+		/*
+		 * Check the tuple XMIN against prior XMAX, if any.  If we reached the
+		 * end of the chain, we're done, so return success.
+		 */
+		if (TransactionIdIsValid(priorXmax) &&
+			!TransactionIdEquals(HeapTupleHeaderGetXmin(mytup.t_data),
+								 priorXmax))
+		{
+			result = HeapTupleMayBeUpdated;
+			goto out_locked;
+		}
+
+		/*
+		 * Also check Xmin: if this tuple was created by an aborted
+		 * (sub)transaction, then we already locked the last live one in the
+		 * chain, thus we're done, so return success.
+		 */
+		if (TransactionIdDidAbort(HeapTupleHeaderGetXmin(mytup.t_data)))
+		{
+			UnlockReleaseBuffer(buf);
+			return HeapTupleMayBeUpdated;
+		}
+
+		old_infomask = mytup.t_data->t_infomask;
+		old_infomask2 = mytup.t_data->t_infomask2;
+		xmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
+
+		/*
+		 * If this tuple version has been updated or locked by some concurrent
+		 * transaction(s), what we do depends on whether our lock mode
+		 * conflicts with what those other transactions hold, and also on the
+		 * status of them.
+		 */
+		if (!(old_infomask & HEAP_XMAX_INVALID))
+		{
+			TransactionId rawxmax;
+			bool		needwait;
+
+			rawxmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
+			if (old_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				int			nmembers;
+				int			i;
+				MultiXactMember *members;
+
+				/*
+				 * We don't need a test for pg_upgrade'd tuples: this is only
+				 * applied to tuples after the first in an update chain.  Said
+				 * first tuple in the chain may well be locked-in-9.2-and-
+				 * pg_upgraded, but that one was already locked by our caller,
+				 * not us; and any subsequent ones cannot be because our
+				 * caller must necessarily have obtained a snapshot later than
+				 * the pg_upgrade itself.
+				 */
+				Assert(!HEAP_LOCKED_UPGRADED(mytup.t_data->t_infomask));
+
+				nmembers = GetMultiXactIdMembers(rawxmax, &members, false,
+												 HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
+				for (i = 0; i < nmembers; i++)
+				{
+					result = test_lockmode_for_conflict(members[i].status,
+														members[i].xid,
+														mode, &needwait);
+
+					/*
+					 * If the tuple was already locked by ourselves in a
+					 * previous iteration of this (say heap_lock_tuple was
+					 * forced to restart the locking loop because of a change
+					 * in xmax), then we hold the lock already on this tuple
+					 * version and we don't need to do anything; and this is
+					 * not an error condition either.  We just need to skip
+					 * this tuple and continue locking the next version in the
+					 * update chain.
+					 */
+					if (result == HeapTupleSelfUpdated)
+					{
+						pfree(members);
+						goto next;
+					}
+
+					if (needwait)
+					{
+						LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+						XactLockTableWait(members[i].xid, rel,
+										  &mytup.t_self,
+										  XLTW_LockUpdated);
+						pfree(members);
+						goto l4;
+					}
+					if (result != HeapTupleMayBeUpdated)
+					{
+						pfree(members);
+						goto out_locked;
+					}
+				}
+				if (members)
+					pfree(members);
+			}
+			else
+			{
+				MultiXactStatus status;
+
+				/*
+				 * For a non-multi Xmax, we first need to compute the
+				 * corresponding MultiXactStatus by using the infomask bits.
+				 */
+				if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
+				{
+					if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
+						status = MultiXactStatusForKeyShare;
+					else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
+						status = MultiXactStatusForShare;
+					else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
+					{
+						if (old_infomask2 & HEAP_KEYS_UPDATED)
+							status = MultiXactStatusForUpdate;
+						else
+							status = MultiXactStatusForNoKeyUpdate;
+					}
+					else
+					{
+						/*
+						 * LOCK_ONLY present alone (a pg_upgraded tuple marked
+						 * as share-locked in the old cluster) shouldn't be
+						 * seen in the middle of an update chain.
+						 */
+						elog(ERROR, "invalid lock status in tuple");
+					}
+				}
+				else
+				{
+					/* it's an update, but which kind? */
+					if (old_infomask2 & HEAP_KEYS_UPDATED)
+						status = MultiXactStatusUpdate;
+					else
+						status = MultiXactStatusNoKeyUpdate;
+				}
+
+				result = test_lockmode_for_conflict(status, rawxmax, mode,
+													&needwait);
+
+				/*
+				 * If the tuple was already locked by ourselves in a previous
+				 * iteration of this (say heap_lock_tuple was forced to
+				 * restart the locking loop because of a change in xmax), then
+				 * we hold the lock already on this tuple version and we don't
+				 * need to do anything; and this is not an error condition
+				 * either.  We just need to skip this tuple and continue
+				 * locking the next version in the update chain.
+				 */
+				if (result == HeapTupleSelfUpdated)
+					goto next;
+
+				if (needwait)
+				{
+					LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+					XactLockTableWait(rawxmax, rel, &mytup.t_self,
+									  XLTW_LockUpdated);
+					goto l4;
+				}
+				if (result != HeapTupleMayBeUpdated)
+				{
+					goto out_locked;
+				}
+			}
+		}
+
+		/* compute the new Xmax and infomask values for the tuple ... */
+		compute_new_xmax_infomask(xmax, old_infomask, mytup.t_data->t_infomask2,
+								  xid, mode, false,
+								  &new_xmax, &new_infomask, &new_infomask2);
+
+		if (PageIsAllVisible(BufferGetPage(buf)) &&
+			visibilitymap_clear(rel, block, vmbuffer,
+								VISIBILITYMAP_ALL_FROZEN))
+			cleared_all_frozen = true;
+
+		START_CRIT_SECTION();
+
+		/* ... and set them */
+		HeapTupleHeaderSetXmax(mytup.t_data, new_xmax);
+		mytup.t_data->t_infomask &= ~HEAP_XMAX_BITS;
+		mytup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+		mytup.t_data->t_infomask |= new_infomask;
+		mytup.t_data->t_infomask2 |= new_infomask2;
+
+		MarkBufferDirty(buf);
+
+		/* XLOG stuff */
+		if (RelationNeedsWAL(rel))
+		{
+			xl_heap_lock_updated xlrec;
+			XLogRecPtr	recptr;
+			Page		page = BufferGetPage(buf);
+
+			XLogBeginInsert();
+			XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
+
+			xlrec.offnum = ItemPointerGetOffsetNumber(&mytup.t_self);
+			xlrec.xmax = new_xmax;
+			xlrec.infobits_set = compute_infobits(new_infomask, new_infomask2);
+			xlrec.flags =
+				cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
+
+			XLogRegisterData((char *) &xlrec, SizeOfHeapLockUpdated);
+
+			recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_LOCK_UPDATED);
+
+			PageSetLSN(page, recptr);
+		}
+
+		END_CRIT_SECTION();
+
+next:
+		/* if we find the end of update chain, we're done. */
+		if (mytup.t_data->t_infomask & HEAP_XMAX_INVALID ||
+			ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid) ||
+			HeapTupleHeaderIsOnlyLocked(mytup.t_data))
+		{
+			result = HeapTupleMayBeUpdated;
+			goto out_locked;
+		}
+
+		/* tail recursion */
+		priorXmax = HeapTupleHeaderGetUpdateXid(mytup.t_data);
+		ItemPointerCopy(&(mytup.t_data->t_ctid), &tupid);
+		UnlockReleaseBuffer(buf);
+		if (vmbuffer != InvalidBuffer)
+			ReleaseBuffer(vmbuffer);
+	}
+
+	result = HeapTupleMayBeUpdated;
+
+out_locked:
+	UnlockReleaseBuffer(buf);
+
+	if (vmbuffer != InvalidBuffer)
+		ReleaseBuffer(vmbuffer);
+
+	return result;
+
+}
+
+/*
+ * heap_lock_updated_tuple
+ *		Follow update chain when locking an updated tuple, acquiring locks (row
+ *		marks) on the updated versions.
+ *
+ * The initial tuple is assumed to be already locked.
+ *
+ * This function doesn't check visibility, it just unconditionally marks the
+ * tuple(s) as locked.  If any tuple in the updated chain is being deleted
+ * concurrently (or updated with the key being modified), sleep until the
+ * transaction doing it is finished.
+ *
+ * Note that we don't acquire heavyweight tuple locks on the tuples we walk
+ * when we have to wait for other transactions to release them, as opposed to
+ * what heap_lock_tuple does.  The reason is that having more than one
+ * transaction walking the chain is probably uncommon enough that risk of
+ * starvation is not likely: one of the preconditions for being here is that
+ * the snapshot in use predates the update that created this tuple (because we
+ * started at an earlier version of the tuple), but at the same time such a
+ * transaction cannot be using repeatable read or serializable isolation
+ * levels, because that would lead to a serializability failure.
+ */
+static HTSU_Result
+heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid,
+						TransactionId xid, LockTupleMode mode)
+{
+	if (!ItemPointerEquals(&tuple->t_self, ctid))
+	{
+		/*
+		 * If this is the first possibly-multixact-able operation in the
+		 * current transaction, set my per-backend OldestMemberMXactId
+		 * setting. We can be certain that the transaction will never become a
+		 * member of any older MultiXactIds than that.  (We have to do this
+		 * even if we end up just using our own TransactionId below, since
+		 * some other backend could incorporate our XID into a MultiXact
+		 * immediately afterwards.)
+		 */
+		MultiXactIdSetOldestMember();
+
+		return heap_lock_updated_tuple_rec(rel, ctid, xid, mode);
+	}
+
+	/* nothing to lock */
+	return HeapTupleMayBeUpdated;
+}
+
+
+/*
+ *	heapam_lock_tuple - lock a tuple in shared or exclusive mode
+ *
+ * Note that this acquires a buffer pin, which the caller must release.
+ *
+ * Input parameters:
+ *	relation: relation containing tuple (caller must hold suitable lock)
+ *	tuple->t_self: TID of tuple to lock (rest of struct need not be valid)
+ *	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
+ *	follow_updates: if true, follow the update chain to also lock descendant
+ *		tuples.
+ *
+ * Output parameters:
+ *	*tuple: all fields filled in
+ *	*buffer: set to buffer holding tuple (pinned but not locked at exit)
+ *	*hufd: filled in failure cases (see below)
+ *
+ * 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.
+ *
+ * See README.tuplock for a thorough explanation of this mechanism.
+ */
+static HTSU_Result
+heapam_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;
+	ItemId		lp;
+	Page		page;
+	Buffer		vmbuffer = InvalidBuffer;
+	BlockNumber block;
+	TransactionId xid,
+				xmax;
+	uint16		old_infomask,
+				new_infomask,
+				new_infomask2;
+	bool		first_time = true;
+	bool		have_tuple_lock = false;
+	bool		cleared_all_frozen = false;
+	HeapTupleData	tuple;
+	Buffer 		buf;
+
+	Assert(stuple != NULL);
+
+	buf = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
+	block = ItemPointerGetBlockNumber(tid);
+	*buffer = buf;
+
+	/*
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
+	 */
+	if (PageIsAllVisible(BufferGetPage(buf)))
+		visibilitymap_pin(relation, block, &vmbuffer);
+
+	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+	page = BufferGetPage(buf);
+	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);
+
+l3:
+	result = HeapTupleSatisfiesUpdate(&tuple, cid, buf);
+
+	if (result == HeapTupleInvisible)
+	{
+		/*
+		 * This is possible, but only when locking a tuple for ON CONFLICT
+		 * UPDATE.  We return this value here rather than throwing an error in
+		 * order to give that case the opportunity to throw a more specific
+		 * error.
+		 */
+		result = HeapTupleInvisible;
+		goto out_locked;
+	}
+	else if (result == HeapTupleBeingUpdated || result == HeapTupleUpdated)
+	{
+		TransactionId xwait;
+		uint16		infomask;
+		uint16		infomask2;
+		bool		require_sleep;
+		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);
+
+		LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+
+		/*
+		 * If any subtransaction of the current top transaction already holds
+		 * a lock as strong as or stronger than what we're requesting, we
+		 * effectively hold the desired lock already.  We *must* succeed
+		 * without trying to take the tuple lock, else we will deadlock
+		 * against anyone wanting to acquire a stronger lock.
+		 *
+		 * Note we only do this the first time we loop on the HTSU result;
+		 * there is no point in testing in subsequent passes, because
+		 * evidently our own transaction cannot have acquired a new lock after
+		 * the first time we checked.
+		 */
+		if (first_time)
+		{
+			first_time = false;
+
+			if (infomask & HEAP_XMAX_IS_MULTI)
+			{
+				int			i;
+				int			nmembers;
+				MultiXactMember *members;
+
+				/*
+				 * We don't need to allow old multixacts here; if that had
+				 * been the case, HeapTupleSatisfiesUpdate would have returned
+				 * MayBeUpdated and we wouldn't be here.
+				 */
+				nmembers =
+					GetMultiXactIdMembers(xwait, &members, false,
+										  HEAP_XMAX_IS_LOCKED_ONLY(infomask));
+
+				for (i = 0; i < nmembers; i++)
+				{
+					/* only consider members of our own transaction */
+					if (!TransactionIdIsCurrentTransactionId(members[i].xid))
+						continue;
+
+					if (TUPLOCK_from_mxstatus(members[i].status) >= mode)
+					{
+						pfree(members);
+						result = HeapTupleMayBeUpdated;
+						goto out_unlocked;
+					}
+				}
+
+				if (members)
+					pfree(members);
+			}
+			else if (TransactionIdIsCurrentTransactionId(xwait))
+			{
+				switch (mode)
+				{
+					case LockTupleKeyShare:
+						Assert(HEAP_XMAX_IS_KEYSHR_LOCKED(infomask) ||
+							   HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
+							   HEAP_XMAX_IS_EXCL_LOCKED(infomask));
+						result = HeapTupleMayBeUpdated;
+						goto out_unlocked;
+					case LockTupleShare:
+						if (HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
+							HEAP_XMAX_IS_EXCL_LOCKED(infomask))
+						{
+							result = HeapTupleMayBeUpdated;
+							goto out_unlocked;
+						}
+						break;
+					case LockTupleNoKeyExclusive:
+						if (HEAP_XMAX_IS_EXCL_LOCKED(infomask))
+						{
+							result = HeapTupleMayBeUpdated;
+							goto out_unlocked;
+						}
+						break;
+					case LockTupleExclusive:
+						if (HEAP_XMAX_IS_EXCL_LOCKED(infomask) &&
+							infomask2 & HEAP_KEYS_UPDATED)
+						{
+							result = HeapTupleMayBeUpdated;
+							goto out_unlocked;
+						}
+						break;
+				}
+			}
+		}
+
+		/*
+		 * Initially assume that we will have to wait for the locking
+		 * transaction(s) to finish.  We check various cases below in which
+		 * this can be turned off.
+		 */
+		require_sleep = true;
+		if (mode == LockTupleKeyShare)
+		{
+			/*
+			 * If we're requesting KeyShare, and there's no update present, we
+			 * don't need to wait.  Even if there is an update, we can still
+			 * continue if the key hasn't been modified.
+			 *
+			 * However, if there are updates, we need to walk the update chain
+			 * to mark future versions of the row as locked, too.  That way,
+			 * if somebody deletes that future version, we're protected
+			 * against the key going away.  This locking of future versions
+			 * could block momentarily, if a concurrent transaction is
+			 * deleting a key; or it could return a value to the effect that
+			 * the transaction deleting the key has already committed.  So we
+			 * do this before re-locking the buffer; otherwise this would be
+			 * prone to deadlocks.
+			 *
+			 * Note that the TID we're locking was grabbed before we unlocked
+			 * the buffer.  For it to change while we're not looking, the
+			 * other properties we're testing for below after re-locking the
+			 * buffer would also change, in which case we would restart this
+			 * loop above.
+			 */
+			if (!(infomask2 & HEAP_KEYS_UPDATED))
+			{
+				bool		updated;
+
+				updated = !HEAP_XMAX_IS_LOCKED_ONLY(infomask);
+
+				/*
+				 * If there are updates, follow the update chain; bail out if
+				 * that cannot be done.
+				 */
+				if (follow_updates && updated)
+				{
+					HTSU_Result res;
+
+					res = heap_lock_updated_tuple(relation, &tuple, &t_ctid,
+												  GetCurrentTransactionId(),
+												  mode);
+					if (res != HeapTupleMayBeUpdated)
+					{
+						result = res;
+						/* recovery code expects to have buffer lock held */
+						LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+						goto failed;
+					}
+				}
+
+				LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+				/*
+				 * Make sure it's still an appropriate lock, else start over.
+				 * Also, if it wasn't updated before we released the lock, but
+				 * is updated now, we start over too; the reason is that we
+				 * 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) ||
+					 !updated))
+					goto l3;
+
+				/* Things look okay, so we can skip sleeping */
+				require_sleep = false;
+
+				/*
+				 * Note we allow Xmax to change here; other updaters/lockers
+				 * could have modified it before we grabbed the buffer lock.
+				 * However, this is not a problem, because with the recheck we
+				 * just did we ensure that they still don't conflict with the
+				 * lock we want.
+				 */
+			}
+		}
+		else if (mode == LockTupleShare)
+		{
+			/*
+			 * If we're requesting Share, we can similarly avoid sleeping if
+			 * there's no update and no exclusive lock present.
+			 */
+			if (HEAP_XMAX_IS_LOCKED_ONLY(infomask) &&
+				!HEAP_XMAX_IS_EXCL_LOCKED(infomask))
+			{
+				LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+				/*
+				 * 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))
+					goto l3;
+				require_sleep = false;
+			}
+		}
+		else if (mode == LockTupleNoKeyExclusive)
+		{
+			/*
+			 * If we're requesting NoKeyExclusive, we might also be able to
+			 * avoid sleeping; just ensure that there no conflicting lock
+			 * already acquired.
+			 */
+			if (infomask & HEAP_XMAX_IS_MULTI)
+			{
+				if (!DoesMultiXactIdConflict((MultiXactId) xwait, infomask,
+											 mode))
+				{
+					/*
+					 * No conflict, but if the xmax changed under us in the
+					 * meantime, start over.
+					 */
+					LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+					if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
+						!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple.t_data),
+											 xwait))
+						goto l3;
+
+					/* otherwise, we're good */
+					require_sleep = false;
+				}
+			}
+			else if (HEAP_XMAX_IS_KEYSHR_LOCKED(infomask))
+			{
+				LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+				/* if the xmax changed in the meantime, start over */
+				if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
+					!TransactionIdEquals(
+										 HeapTupleHeaderGetRawXmax(tuple.t_data),
+										 xwait))
+					goto l3;
+				/* otherwise, we're good */
+				require_sleep = false;
+			}
+		}
+
+		/*
+		 * As a check independent from those above, we can also avoid sleeping
+		 * if the current transaction is the sole locker of the tuple.  Note
+		 * that the strength of the lock already held is irrelevant; this is
+		 * not about recording the lock in Xmax (which will be done regardless
+		 * of this optimization, below).  Also, note that the cases where we
+		 * hold a lock stronger than we are requesting are already handled
+		 * above by not doing anything.
+		 *
+		 * Note we only deal with the non-multixact case here; MultiXactIdWait
+		 * is well equipped to deal with this situation on its own.
+		 */
+		if (require_sleep && !(infomask & HEAP_XMAX_IS_MULTI) &&
+			TransactionIdIsCurrentTransactionId(xwait))
+		{
+			/* ... but if the xmax changed in the meantime, start over */
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+			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));
+			require_sleep = false;
+		}
+
+		/*
+		 * Time to sleep on the other transaction/multixact, if necessary.
+		 *
+		 * If the other transaction is an update that's already committed,
+		 * then sleeping cannot possibly do any good: if we're required to
+		 * sleep, get out to raise an error instead.
+		 *
+		 * By here, we either have already acquired the buffer exclusive lock,
+		 * 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)
+		{
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+			goto failed;
+		}
+		else if (require_sleep)
+		{
+			/*
+			 * Acquire tuple lock to establish our priority for the tuple, or
+			 * die trying.  LockTuple will release us when we are next-in-line
+			 * for the tuple.  We must do this even if we are share-locking.
+			 *
+			 * If we are forced to "start over" below, we keep the tuple lock;
+			 * this arranges that we stay at the head of the line while
+			 * rechecking tuple state.
+			 */
+			if (!heap_acquire_tuplock(relation, tid, mode, wait_policy,
+									  &have_tuple_lock))
+			{
+				/*
+				 * This can only happen if wait_policy is Skip and the lock
+				 * couldn't be obtained.
+				 */
+				result = HeapTupleWouldBlock;
+				/* recovery code expects to have buffer lock held */
+				LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+				goto failed;
+			}
+
+			if (infomask & HEAP_XMAX_IS_MULTI)
+			{
+				MultiXactStatus status = get_mxact_status_for_lock(mode, false);
+
+				/* We only ever lock tuples, never update them */
+				if (status >= MultiXactStatusNoKeyUpdate)
+					elog(ERROR, "invalid lock mode in heap_lock_tuple");
+
+				/* wait for multixact to end, or die trying  */
+				switch (wait_policy)
+				{
+					case LockWaitBlock:
+						MultiXactIdWait((MultiXactId) xwait, status, infomask,
+										relation, &tuple.t_self, XLTW_Lock, NULL);
+						break;
+					case LockWaitSkip:
+						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
+														status, infomask, relation,
+														NULL))
+						{
+							result = HeapTupleWouldBlock;
+							/* recovery code expects to have buffer lock held */
+							LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+							goto failed;
+						}
+						break;
+					case LockWaitError:
+						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
+														status, infomask, relation,
+														NULL))
+							ereport(ERROR,
+									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+									 errmsg("could not obtain lock on row in relation \"%s\"",
+											RelationGetRelationName(relation))));
+
+						break;
+				}
+
+				/*
+				 * Of course, the multixact might not be done here: if we're
+				 * requesting a light lock mode, other transactions with light
+				 * locks could still be alive, as well as locks owned by our
+				 * own xact or other subxacts of this backend.  We need to
+				 * preserve the surviving MultiXact members.  Note that it
+				 * isn't absolutely necessary in the latter case, but doing so
+				 * is simpler.
+				 */
+			}
+			else
+			{
+				/* wait for regular transaction to end, or die trying */
+				switch (wait_policy)
+				{
+					case LockWaitBlock:
+						XactLockTableWait(xwait, relation, &tuple.t_self,
+										  XLTW_Lock);
+						break;
+					case LockWaitSkip:
+						if (!ConditionalXactLockTableWait(xwait))
+						{
+							result = HeapTupleWouldBlock;
+							/* recovery code expects to have buffer lock held */
+							LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+							goto failed;
+						}
+						break;
+					case LockWaitError:
+						if (!ConditionalXactLockTableWait(xwait))
+							ereport(ERROR,
+									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+									 errmsg("could not obtain lock on row in relation \"%s\"",
+											RelationGetRelationName(relation))));
+						break;
+				}
+			}
+
+			/* if there are updates, follow the update chain */
+			if (follow_updates && !HEAP_XMAX_IS_LOCKED_ONLY(infomask))
+			{
+				HTSU_Result res;
+
+				res = heap_lock_updated_tuple(relation, &tuple, &t_ctid,
+											  GetCurrentTransactionId(),
+											  mode);
+				if (res != HeapTupleMayBeUpdated)
+				{
+					result = res;
+					/* recovery code expects to have buffer lock held */
+					LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+					goto failed;
+				}
+			}
+
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+			/*
+			 * xwait is done, but if xwait had just locked the tuple then some
+			 * 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),
+									 xwait))
+				goto l3;
+
+			if (!(infomask & HEAP_XMAX_IS_MULTI))
+			{
+				/*
+				 * Otherwise check if it committed or aborted.  Note we cannot
+				 * be here if the tuple was only locked by somebody who didn't
+				 * conflict with us; that would have been handled above.  So
+				 * that transaction must necessarily be gone by now.  But
+				 * don't check for this in the multixact case, because some
+				 * locker transactions might still be running.
+				 */
+				UpdateXmaxHintBits(tuple.t_data, buf, xwait);
+			}
+		}
+
+		/* By here, we're certain that we hold buffer exclusive lock again */
+
+		/*
+		 * We may lock if previous xmax aborted, or if it committed but only
+		 * locked the tuple without updating it; or if we didn't have to wait
+		 * 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))
+			result = HeapTupleMayBeUpdated;
+		else
+			result = HeapTupleUpdated;
+	}
+
+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);
+		if (result == HeapTupleSelfUpdated)
+			hufd->cmax = HeapTupleHeaderGetCmax(tuple.t_data);
+		else
+			hufd->cmax = InvalidCommandId;
+		goto out_locked;
+	}
+
+	/*
+	 * If we didn't pin the visibility map page and the page has become all
+	 * visible while we were busy locking the buffer, or during some
+	 * subsequent window during which we had it unlocked, we'll have to unlock
+	 * and re-lock, to avoid holding the buffer lock across I/O.  That's a bit
+	 * unfortunate, especially since we'll now have to recheck whether the
+	 * tuple has been locked or updated under us, but hopefully it won't
+	 * happen very often.
+	 */
+	if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
+	{
+		LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+		visibilitymap_pin(relation, block, &vmbuffer);
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+		goto l3;
+	}
+
+	xmax = HeapTupleHeaderGetRawXmax(tuple.t_data);
+	old_infomask = tuple.t_data->t_infomask;
+
+	/*
+	 * If this is the first possibly-multixact-able operation in the current
+	 * transaction, set my per-backend OldestMemberMXactId setting. We can be
+	 * certain that the transaction will never become a member of any older
+	 * MultiXactIds than that.  (We have to do this even if we end up just
+	 * using our own TransactionId below, since some other backend could
+	 * incorporate our XID into a MultiXact immediately afterwards.)
+	 */
+	MultiXactIdSetOldestMember();
+
+	/*
+	 * Compute the new xmax and infomask to store into the tuple.  Note we do
+	 * 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,
+							  GetCurrentTransactionId(), mode, false,
+							  &xid, &new_infomask, &new_infomask2);
+
+	START_CRIT_SECTION();
+
+	/*
+	 * Store transaction information of xact locking the tuple.
+	 *
+	 * Note: Cmax is meaningless in this context, so don't set it; this avoids
+	 * possibly generating a useless combo CID.  Moreover, if we're locking a
+	 * previously updated tuple, it's important to preserve the Cmax.
+	 *
+	 * 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;
+	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
+		HeapTupleHeaderClearHotUpdated(tuple.t_data);
+	HeapTupleHeaderSetXmax(tuple.t_data, xid);
+
+	/*
+	 * Make sure there is no forward chain link in t_ctid.  Note that in the
+	 * cases where the tuple has been updated, we must not overwrite t_ctid,
+	 * because it was set by the updater.  Moreover, if the tuple has been
+	 * updated, we need to follow the update chain to lock the new versions of
+	 * the tuple as well.
+	 */
+	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
+		tuple.t_data->t_ctid = *tid;
+
+	/* Clear only the all-frozen bit on visibility map if needed */
+	if (PageIsAllVisible(page) &&
+		visibilitymap_clear(relation, block, vmbuffer,
+							VISIBILITYMAP_ALL_FROZEN))
+		cleared_all_frozen = true;
+
+
+	MarkBufferDirty(buf);
+
+	/*
+	 * XLOG stuff.  You might think that we don't need an XLOG record because
+	 * there is no state change worth restoring after a crash.  You would be
+	 * wrong however: we have just written either a TransactionId or a
+	 * MultiXactId that may never have been seen on disk before, and we need
+	 * to make sure that there are XLOG entries covering those ID numbers.
+	 * Else the same IDs might be re-used after a crash, which would be
+	 * disastrous if this page made it to disk before the crash.  Essentially
+	 * we have to enforce the WAL log-before-data rule even in this case.
+	 * (Also, in a PITR log-shipping or 2PC environment, we have to have XLOG
+	 * entries for everything anyway.)
+	 */
+	if (RelationNeedsWAL(relation))
+	{
+		xl_heap_lock xlrec;
+		XLogRecPtr	recptr;
+
+		XLogBeginInsert();
+		XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
+
+		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple.t_self);
+		xlrec.locking_xid = xid;
+		xlrec.infobits_set = compute_infobits(new_infomask,
+											  tuple.t_data->t_infomask2);
+		xlrec.flags = cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
+		XLogRegisterData((char *) &xlrec, SizeOfHeapLock);
+
+		/* we don't decode row locks atm, so no need to log the origin */
+
+		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_LOCK);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	result = HeapTupleMayBeUpdated;
+
+out_locked:
+	LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+
+out_unlocked:
+	if (BufferIsValid(vmbuffer))
+		ReleaseBuffer(vmbuffer);
+
+	/*
+	 * Don't update the visibility map here. Locking a tuple doesn't change
+	 * visibility info.
+	 */
+
+	/*
+	 * Now that we have successfully marked the tuple as locked, we can
+	 * release the lmgr tuple lock, if we had it.
+	 */
+	if (have_tuple_lock)
+		UnlockTupleTuplock(relation, tid, mode);
+
+	*stuple = heap_copytuple(&tuple);
+	return result;
+}
+
+/*
+ *	heapam_get_latest_tid -  get the latest tid of a specified tuple
+ *
+ * Actually, this gets the latest version that is visible according to
+ * the passed snapshot.  You can pass SnapshotDirty to get the very latest,
+ * possibly uncommitted version.
+ *
+ * *tid is both an input and an output parameter: it is updated to
+ * show the latest version of the row.  Note that it will not be changed
+ * if no version of the row passes the snapshot test.
+ */
+static void
+heapam_get_latest_tid(Relation relation,
+					Snapshot snapshot,
+					ItemPointer tid)
+{
+	BlockNumber blk;
+	ItemPointerData ctid;
+	TransactionId priorXmax;
+
+	/* this is to avoid Assert failures on bad input */
+	if (!ItemPointerIsValid(tid))
+		return;
+
+	/*
+	 * Since this can be called with user-supplied TID, don't trust the input
+	 * too much.  (RelationGetNumberOfBlocks is an expensive check, so we
+	 * don't check t_ctid links again this way.  Note that it would not do to
+	 * call it just once and save the result, either.)
+	 */
+	blk = ItemPointerGetBlockNumber(tid);
+	if (blk >= RelationGetNumberOfBlocks(relation))
+		elog(ERROR, "block number %u is out of range for relation \"%s\"",
+			 blk, RelationGetRelationName(relation));
+
+	/*
+	 * Loop to chase down t_ctid links.  At top of loop, ctid is the tuple we
+	 * need to examine, and *tid is the TID we will return if ctid turns out
+	 * to be bogus.
+	 *
+	 * Note that we will loop until we reach the end of the t_ctid chain.
+	 * Depending on the snapshot passed, there might be at most one visible
+	 * version of the row, but we don't try to optimize for that.
+	 */
+	ctid = *tid;
+	priorXmax = InvalidTransactionId;	/* cannot check first XMIN */
+	for (;;)
+	{
+		Buffer		buffer;
+		Page		page;
+		OffsetNumber offnum;
+		ItemId		lp;
+		HeapTupleData tp;
+		bool		valid;
+
+		/*
+		 * Read, pin, and lock the page.
+		 */
+		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&ctid));
+		LockBuffer(buffer, BUFFER_LOCK_SHARE);
+		page = BufferGetPage(buffer);
+		TestForOldSnapshot(snapshot, relation, page);
+
+		/*
+		 * Check for bogus item number.  This is not treated as an error
+		 * condition because it can happen while following a t_ctid link. We
+		 * just assume that the prior tid is OK and return it unchanged.
+		 */
+		offnum = ItemPointerGetOffsetNumber(&ctid);
+		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+		lp = PageGetItemId(page, offnum);
+		if (!ItemIdIsNormal(lp))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+
+		/* OK to access the tuple */
+		tp.t_self = ctid;
+		tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+		tp.t_len = ItemIdGetLength(lp);
+		tp.t_tableOid = RelationGetRelid(relation);
+
+		/*
+		 * After following a t_ctid link, we might arrive at an unrelated
+		 * tuple.  Check for XMIN match.
+		 */
+		if (TransactionIdIsValid(priorXmax) &&
+			!TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(tp.t_data)))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+
+		/*
+		 * Check time qualification of tuple; if visible, set it as the new
+		 * result candidate.
+		 */
+		valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, &tp, snapshot, buffer);
+		CheckForSerializableConflictOut(valid, relation, &tp, buffer, snapshot);
+		if (valid)
+			*tid = ctid;
+
+		/*
+		 * If there's a valid t_ctid link, follow it, else we're done.
+		 */
+		if ((tp.t_data->t_infomask & HEAP_XMAX_INVALID) ||
+			HeapTupleHeaderIsOnlyLocked(tp.t_data) ||
+			ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+
+		ctid = tp.t_data->t_ctid;
+		priorXmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
+		UnlockReleaseBuffer(buffer);
+	}							/* end of loop */
+}
+
+
+/*
+ *	heapam_sync		- sync a heap, for use when no WAL has been written
+ *
+ * This forces the heap contents (including TOAST heap if any) down to disk.
+ * If we skipped using WAL, and WAL is otherwise needed, we must force the
+ * relation down to disk before it's safe to commit the transaction.  This
+ * requires writing out any dirty buffers and then doing a forced fsync.
+ *
+ * Indexes are not touched.  (Currently, index operations associated with
+ * the commands that use this are WAL-logged and so do not need fsync.
+ * That behavior might change someday, but in any case it's likely that
+ * any fsync decisions required would be per-index and hence not appropriate
+ * to be done here.)
+ */
+static void
+heapam_sync(Relation rel)
+{
+	/* non-WAL-logged tables never need fsync */
+	if (!RelationNeedsWAL(rel))
+		return;
+
+	/* main heap */
+	FlushRelationBuffers(rel);
+	/* FlushRelationBuffers will have opened rd_smgr */
+	smgrimmedsync(rel->rd_smgr, MAIN_FORKNUM);
+
+	/* FSM is not critical, don't bother syncing it */
+
+	/* toast heap, if any */
+	if (OidIsValid(rel->rd_rel->reltoastrelid))
+	{
+		Relation	toastrel;
+
+		toastrel = heap_open(rel->rd_rel->reltoastrelid, AccessShareLock);
+		FlushRelationBuffers(toastrel);
+		smgrimmedsync(toastrel->rd_smgr, MAIN_FORKNUM);
+		heap_close(toastrel, AccessShareLock);
+	}
+}
+
+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 bool
+heapam_tuple_is_heaopnly(StorageTuple tuple)
+{
+	return HeapTupleIsHeapOnly((HeapTuple)tuple);
+}
+
+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)
+{
+	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
+
+    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 = heapam_multi_insert;
+
+    amroutine->speculative_finish = heapam_finish_speculative;
+    amroutine->speculative_abort = heapam_abort_speculative;
+
+    amroutine->get_tuple_data = heapam_get_tuple_data;
+    amroutine->tuple_is_heaponly = heapam_tuple_is_heaopnly;
+    amroutine->tuple_from_datum = heapam_form_tuple_by_datum;
+    amroutine->tuple_get_latest_tid = heapam_get_latest_tid;
+
+    amroutine->relation_sync = heapam_sync;
 
 	amroutine->slot_storageam = heapam_storage_slot_handler;
 
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 191f088..8fba61c 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -111,6 +111,7 @@
 #include "access/heapam_common.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"
@@ -127,13 +128,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
@@ -358,7 +359,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/storageam.c b/src/backend/access/heap/storageam.c
new file mode 100644
index 0000000..d1d7364
--- /dev/null
+++ b/src/backend/access/heap/storageam.c
@@ -0,0 +1,306 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/heap/storageam.c
+ *
+ *
+ * NOTES
+ *	  This file contains the storage_ routines which implement
+ *	  the POSTGRES storage access method used for all POSTGRES
+ *	  relations.
+ *
+ *-------------------------------------------------------------------------
+ */
+#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 "access/relscan.h"
+#include "access/storageam.h"
+#include "access/storageamapi.h"
+#include "access/tuptoaster.h"
+#include "access/valid.h"
+#include "access/visibilitymap.h"
+#include "access/xloginsert.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/bufmgr.h"
+#include "storage/lmgr.h"
+#include "storage/predicate.h"
+#include "storage/procarray.h"
+#include "storage/smgr.h"
+#include "storage/spin.h"
+#include "utils/builtins.h"
+#include "utils/inval.h"
+#include "utils/rel.h"
+#include "utils/tqual.h"
+
+
+/*
+ *	storage_fetch		- retrieve tuple with given tid
+ *
+ * On entry, tuple->t_self is the TID to fetch.  We pin the buffer holding
+ * the tuple, fill in the remaining fields of *tuple, and check the tuple
+ * against the specified snapshot.
+ *
+ * If successful (tuple found and passes snapshot time qual), then *userbuf
+ * is set to the buffer holding the tuple and TRUE is returned.  The caller
+ * must unpin the buffer when done with the tuple.
+ *
+ * If the tuple is not found (ie, item number references a deleted slot),
+ * then tuple->t_data is set to NULL and FALSE is returned.
+ *
+ * If the tuple is found but fails the time qual check, then FALSE is returned
+ * but tuple->t_data is left pointing to the tuple.
+ *
+ * keep_buf determines what is done with the buffer in the FALSE-result cases.
+ * When the caller specifies keep_buf = true, we retain the pin on the buffer
+ * and return it in *userbuf (so the caller must eventually unpin it); when
+ * keep_buf = false, the pin is released and *userbuf is set to InvalidBuffer.
+ *
+ * stats_relation is the relation to charge the heap_fetch operation against
+ * for statistical purposes.  (This could be the heap rel itself, an
+ * associated index, or NULL to not count the fetch at all.)
+ *
+ * heap_fetch does not follow HOT chains: only the exact TID requested will
+ * be fetched.
+ *
+ * It is somewhat inconsistent that we ereport() on invalid block number but
+ * return false on invalid item number.  There are a couple of reasons though.
+ * One is that the caller can relatively easily check the block number for
+ * validity, but cannot check the item number without reading the page
+ * himself.  Another is that when we are following a t_ctid link, we can be
+ * reasonably confident that the page number is valid (since VACUUM shouldn't
+ * truncate off the destination page without having killed the referencing
+ * tuple first), but the item number might well not be good.
+ */
+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
+ *
+ * Note that this acquires a buffer pin, which the caller must release.
+ *
+ * Input parameters:
+ *	relation: relation containing tuple (caller must hold suitable lock)
+ *	tuple->t_self: TID of tuple to lock (rest of struct need not be valid)
+ *	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
+ *	follow_updates: if true, follow the update chain to also lock descendant
+ *		tuples.
+ *
+ * Output parameters:
+ *	*tuple: all fields filled in
+ *	*buffer: set to buffer holding tuple (pinned but not locked at exit)
+ *	*hufd: filled in failure cases (see below)
+ *
+ * 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.
+ *
+ * See README.tuplock for a thorough explanation of this mechanism.
+ */
+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)
+{
+	return relation->rd_stamroutine->tuple_insert(relation, slot, cid,
+							options, bistate);
+}
+
+/*
+ * 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,
+				   CommandId cid, Snapshot crosscheck, bool wait,
+				   HeapUpdateFailureData *hufd, LockTupleMode *lockmode)
+{
+	return relation->rd_stamroutine->tuple_update(relation, otid, slot, cid,
+							crosscheck, wait, hufd, lockmode);
+}
+
+
+/*
+ *	storage_multi_insert	- insert multiple tuple into a storage
+ *
+ * This is like heap_insert(), but inserts multiple tuples in one operation.
+ * That's faster than calling heap_insert() in a loop, because when multiple
+ * tuples can be inserted on a single page, we can write just a single WAL
+ * record covering all of them, and only need to lock/unlock the page once.
+ *
+ * Note: this leaks memory into the current memory context. You can create a
+ * temporary context before calling this, if that's a problem.
+ */
+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_finish_speculative - mark speculative insertion as successful
+ *
+ * To successfully finish a speculative insertion we have to clear speculative
+ * token from tuple.  To do so the t_ctid field, which will contain a
+ * speculative token value, is modified in place to point to the tuple itself,
+ * which is characteristic of a newly inserted ordinary tuple.
+ *
+ * NB: It is not ok to commit without either finishing or aborting a
+ * speculative insertion.  We could treat speculative tuples of committed
+ * transactions implicitly as completed, but then we would have to be prepared
+ * to deal with speculative tokens on committed tuples.  That wouldn't be
+ * difficult - no-one looks at the ctid field of a tuple with invalid xmax -
+ * but clearing the token at completion isn't very expensive either.
+ * An explicit confirmation WAL record also makes logical decoding simpler.
+ */
+void
+storage_finish_speculative(Relation relation, TupleTableSlot *slot)
+{
+	relation->rd_stamroutine->speculative_finish(relation, slot);
+}
+
+/*
+ *	storage_abort_speculative - kill a speculatively inserted tuple
+ *
+ * Marks a tuple that was speculatively inserted in the same command as dead,
+ * by setting its xmin as invalid.  That makes it immediately appear as dead
+ * to all transactions, including our own.  In particular, it makes
+ * HeapTupleSatisfiesDirty() regard the tuple as dead, so that another backend
+ * inserting a duplicate key value won't unnecessarily wait for our whole
+ * transaction to finish (it'll just wait for our speculative insertion to
+ * finish).
+ *
+ * Killing the tuple prevents "unprincipled deadlocks", which are deadlocks
+ * that arise due to a mutual dependency that is not user visible.  By
+ * definition, unprincipled deadlocks cannot be prevented by the user
+ * reordering lock acquisition in client code, because the implementation level
+ * lock acquisitions are not under the user's direct control.  If speculative
+ * inserters did not take this precaution, then under high concurrency they
+ * could deadlock with each other, which would not be acceptable.
+ *
+ * This is somewhat redundant with heap_delete, but we prefer to have a
+ * dedicated routine with stripped down requirements.  Note that this is also
+ * used to delete the TOAST tuples created during speculative insertion.
+ *
+ * This routine does not affect logical decoding as it only looks at
+ * confirmation records.
+ */
+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);
+}
+
+bool
+storage_tuple_is_heaponly(Relation relation, StorageTuple tuple)
+{
+	return relation->rd_stamroutine->tuple_is_heaponly(tuple);
+}
+
+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
+ *
+ * This forces the heap contents (including TOAST heap if any) down to disk.
+ * If we skipped using WAL, and WAL is otherwise needed, we must force the
+ * relation down to disk before it's safe to commit the transaction.  This
+ * requires writing out any dirty buffers and then doing a forced fsync.
+ *
+ * Indexes are not touched.  (Currently, index operations associated with
+ * the commands that use this are WAL-logged and so do not need fsync.
+ * That behavior might change someday, but in any case it's likely that
+ * any fsync decisions required would be per-index and hence not appropriate
+ * to be done here.)
+ */
+void
+storage_sync(Relation rel)
+{
+	rel->rd_stamroutine->relation_sync(rel);
+}
diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index 5a8f1da..d766a6e 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,12 @@ 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/commands/copy.c b/src/backend/commands/copy.c
index 8456bfd..c81ddf5 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -22,6 +22,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"
@@ -2694,8 +2695,6 @@ CopyFrom(CopyState cstate)
 
 			if (slot == NULL)	/* "do nothing" */
 				skip_tuple = true;
-			else				/* trigger might have changed tuple */
-				tuple = ExecHeapifySlot(slot);
 		}
 
 		if (!skip_tuple)
@@ -2758,19 +2757,18 @@ 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);
+					storage_insert(resultRelInfo->ri_RelationDesc, slot, mycid, hi_options, bistate);
 
 					if (resultRelInfo->ri_NumIndices > 0)
 						recheckIndexes = ExecInsertIndexTuples(slot,
-															   &(tuple->t_self),
+															   &(slot->tts_tid),
 															   estate,
 															   false,
 															   NULL,
 															   NIL);
 
 					/* AFTER ROW INSERT Triggers */
-					ExecARInsertTriggers(estate, resultRelInfo, tuple,
+					ExecARInsertTriggers(estate, resultRelInfo, slot,
 										 recheckIndexes, cstate->transition_capture);
 
 					list_free(recheckIndexes);
@@ -2866,7 +2864,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;
 }
@@ -2899,12 +2897,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);
 
 	/*
@@ -2923,7 +2921,7 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 				ExecInsertIndexTuples(myslot, &(bufferedTuples[i]->t_self),
 									  estate, false, NULL, NIL);
 			ExecARInsertTriggers(estate, resultRelInfo,
-								 bufferedTuples[i],
+								 myslot,
 								 recheckIndexes, cstate->transition_capture);
 			list_free(recheckIndexes);
 		}
@@ -2940,8 +2938,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 a0ec444..d119149 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,24 @@ 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);
+		slot->tts_tupleOid = InvalidOid;
 
-	heap_insert(myState->rel,
-				tuple,
-				myState->output_cid,
-				myState->hi_options,
-				myState->bistate);
+	storage_insert(myState->rel,
+					 slot,
+					myState->output_cid,
+					myState->hi_options,
+					myState->bistate);
 
 	/* We know this is a newly created relation, so there are no indexes */
 
@@ -619,7 +619,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 b440740..6102481 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,16 +492,15 @@ 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);
+	ExecMaterializeSlot(slot);
 
-	heap_insert(myState->transientrel,
-				tuple,
+	storage_insert(myState->transientrel,
+						slot,
 				myState->output_cid,
 				myState->hi_options,
 				myState->bistate);
@@ -522,7 +522,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 96354bd..ec6523e 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"
@@ -4652,7 +4653,7 @@ 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);
 
 			ResetExprContext(econtext);
 
@@ -4676,7 +4677,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 f891cd1..5d83506 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2411,17 +2411,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 *
@@ -3061,9 +3065,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)
 	{
@@ -3079,11 +3084,11 @@ GetTupleForTrigger(EState *estate,
 		 * lock tuple for update
 		 */
 ltrmark:;
-		tuple.t_self = *tid;
-		test = heap_lock_tuple(relation, &tuple,
+		test = storage_lock_tuple(relation, tid, &tuple,
 							   estate->es_output_cid,
 							   lockmode, LockWaitBlock,
 							   false, &buffer, &hufd);
+		result = tuple;
 		switch (test)
 		{
 			case HeapTupleSelfUpdated:
@@ -3115,7 +3120,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;
@@ -3161,6 +3167,7 @@ ltrmark:;
 	{
 		Page		page;
 		ItemId		lp;
+		HeapTupleData tupledata;
 
 		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 
@@ -3179,17 +3186,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;
 }
 
@@ -3898,8 +3905,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;
@@ -3974,10 +3981,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
@@ -3991,10 +3997,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/execMain.c b/src/backend/executor/execMain.c
index 4b594d4..a7127e4 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1894,7 +1894,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 +1974,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				 */
 				if (resultRelInfo->ri_PartitionRoot)
 				{
-					HeapTuple	tuple = ExecFetchSlotTuple(slot);
+					StorageTuple	tuple = ExecFetchSlotTuple(slot);
 					TupleConversionMap *map;
 
 					rel = resultRelInfo->ri_PartitionRoot;
@@ -2021,7 +2021,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 +2480,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 +2498,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 +2531,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 +2564,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 +2579,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,8 +2598,8 @@ 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),
-									 priorXmax))
+			t_data = storage_tuple_get_data(relation, tuple, XMIN);
+			if (!TransactionIdEquals(t_data.xid, priorXmax))
 			{
 				ReleaseBuffer(buffer);
 				return NULL;
@@ -2617,7 +2620,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,17 +2650,20 @@ 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,
+			test = storage_lock_tuple(relation, tid, tuple,
 								   estate->es_output_cid,
 								   lockmode, wait_policy,
 								   false, &buffer, &hufd);
@@ -2695,12 +2702,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 +2732,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 +2740,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,8 +2749,8 @@ 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),
-								 priorXmax))
+		t_data = storage_tuple_get_data(relation, tuple, XMIN);
+		if (!TransactionIdEquals(t_data.xid, priorXmax))
 		{
 			ReleaseBuffer(buffer);
 			return NULL;
@@ -2762,7 +2768,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 +2778,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 +2836,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 +2855,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 +2883,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 +2914,6 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 
 		if (erm->markType == ROW_MARK_REFERENCE)
 		{
-			HeapTuple	copyTuple;
-
 			Assert(erm->relation != NULL);
 
 			/* fetch the tuple's ctid */
@@ -2929,11 +2937,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,
+				tuple = fdwroutine->RefetchForeignRow(epqstate->estate,
 														  erm,
 														  datum,
 														  &updated);
-				if (copyTuple == NULL)
+				if (tuple == NULL)
 					elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck");
 
 				/*
@@ -2947,23 +2955,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,
+				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 +2976,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);
+
 		}
 	}
 }
@@ -3151,8 +3147,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 6700f0a..8d625b6 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),
+		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),
+		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,7 @@ void
 ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 {
 	bool		skip_tuple = false;
-	HeapTuple	tuple;
+	StorageTuple	tuple;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 
@@ -349,6 +351,7 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 	if (!skip_tuple)
 	{
 		List	   *recheckIndexes = NIL;
+		tuple_data	t_data;
 
 		/* Check the constraints of the tuple */
 		if (rel->rd_att->constr)
@@ -359,14 +362,15 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 
 		/* OK, store the tuple and create index entries for it */
 		simple_heap_insert(rel, tuple);
+		t_data = storage_tuple_get_data(rel, tuple, TID);
 
 		if (resultRelInfo->ri_NumIndices > 0)
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+			recheckIndexes = ExecInsertIndexTuples(slot, &(t_data.tid),
 												   estate, false, NULL,
 												   NIL);
 
 		/* AFTER ROW INSERT Triggers */
-		ExecARInsertTriggers(estate, resultRelInfo, tuple,
+		ExecARInsertTriggers(estate, resultRelInfo, slot,
 							 recheckIndexes, NULL);
 
 		/*
@@ -390,7 +394,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);
@@ -426,8 +430,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 		/* OK, update the tuple and index entries for it */
 		simple_heap_update(rel, tid, tuple);
 
-		if (resultRelInfo->ri_NumIndices > 0 &&
-			!HeapTupleIsHeapOnly(tuple))
+		if (resultRelInfo->ri_NumIndices > 0 && !storage_tuple_is_heaponly(rel, tuple))
 			recheckIndexes = ExecInsertIndexTuples(slot, tid,
 												   estate, false, NULL,
 												   NIL);
diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c
index 9389560..f06f34a 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,
+		test = storage_lock_tuple(erm->relation, &tid, &tuple,
 							   estate->es_output_cid,
 							   lockmode, erm->waitPolicy, true,
 							   &buffer, &hufd);
-		ReleaseBuffer(buffer);
+		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,
+			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 3b23df7..da4ec94 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/executor.h"
 #include "executor/nodeModifyTable.h"
@@ -164,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;
 
@@ -191,7 +192,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
 static void
 ExecCheckHeapTupleVisible(EState *estate,
 						  Relation rel,
-						  HeapTuple tuple,
+						  StorageTuple tuple,
 						  Buffer buffer)
 {
 	if (!IsolationUsesXactSnapshot())
@@ -204,13 +205,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")));
@@ -226,19 +229,20 @@ ExecCheckTIDVisible(EState *estate,
 					ResultRelInfo *relinfo,
 					ItemPointer tid)
 {
+	Buffer buffer;
 	Relation	rel = relinfo->ri_RelationDesc;
-	Buffer		buffer;
-	HeapTupleData tuple;
+	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);
 }
 
 /* ----------------------------------------------------------------
@@ -259,7 +263,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,
 	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
 	 */
 	resultRelInfo = estate->es_result_relation_info;
@@ -284,6 +282,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.
@@ -371,19 +371,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.
@@ -401,9 +413,6 @@ ExecInsert(ModifyTableState *mtstate,
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
-
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -415,9 +424,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)
@@ -433,14 +439,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;
 	}
@@ -460,7 +464,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
@@ -551,24 +556,24 @@ 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,
+			newId = storage_insert(resultRelationDesc, slot,
 								estate->es_output_cid,
 								HEAP_INSERT_SPECULATIVE,
 								NULL);
 
 			/* insert index entries for tuple */
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+			recheckIndexes = ExecInsertIndexTuples(slot, &(slot->tts_tid),
 												   estate, true, &specConflict,
 												   arbiterIndexes);
 
 			/* adjust the tuple's state accordingly */
 			if (!specConflict)
-				heap_finish_speculative(resultRelationDesc, tuple);
+				storage_finish_speculative(resultRelationDesc, slot);
 			else
-				heap_abort_speculative(resultRelationDesc, tuple);
+				storage_abort_speculative(resultRelationDesc, slot);
 
 			/*
 			 * Wake up anyone waiting for our decision.  They will re-check
@@ -596,17 +601,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,
+			newId = storage_insert(resultRelationDesc, slot,
 								estate->es_output_cid,
 								0, NULL);
 
 			/* insert index entries for tuple */
 			if (resultRelInfo->ri_NumIndices > 0)
-				recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+				recheckIndexes = ExecInsertIndexTuples(slot, &(slot->tts_tid),
 													   estate, false, NULL,
 													   arbiterIndexes);
 		}
@@ -616,11 +618,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);
@@ -671,7 +673,7 @@ ExecInsert(ModifyTableState *mtstate,
 static TupleTableSlot *
 ExecDelete(ModifyTableState *mtstate,
 		   ItemPointer tupleid,
-		   HeapTuple oldtuple,
+		   StorageTuple oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate,
@@ -716,8 +718,6 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
-		HeapTuple	tuple;
-
 		/*
 		 * delete from foreign table: let the FDW do it
 		 *
@@ -743,8 +743,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
 	{
@@ -758,7 +760,7 @@ ExecDelete(ModifyTableState *mtstate,
 		 * mode transactions.
 		 */
 ldelete:;
-		result = heap_delete(resultRelationDesc, tupleid,
+		result = storage_delete(resultRelationDesc, tupleid,
 							 estate->es_output_cid,
 							 estate->es_crosscheck_snapshot,
 							 true /* wait for commit */ ,
@@ -858,7 +860,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)
@@ -872,20 +874,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);
@@ -894,7 +895,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))
@@ -931,14 +932,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;
@@ -1003,14 +1004,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
 	{
@@ -1020,7 +1021,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
@@ -1056,7 +1057,7 @@ lreplace:;
 		 * needed for referential integrity updates in transaction-snapshot
 		 * mode transactions.
 		 */
-		result = heap_update(resultRelationDesc, tupleid, tuple,
+		result = storage_update(resultRelationDesc, tupleid, slot,
 							 estate->es_output_cid,
 							 estate->es_crosscheck_snapshot,
 							 true /* wait for commit */ ,
@@ -1148,8 +1149,8 @@ lreplace:;
 		 *
 		 * 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),
+		if ((resultRelInfo->ri_NumIndices > 0) && !storage_tuple_is_heaponly(resultRelationDesc, tuple))
+			recheckIndexes = ExecInsertIndexTuples(slot, &(slot->tts_tid),
 												   estate, false, NULL, NIL);
 	}
 
@@ -1206,11 +1207,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);
@@ -1221,10 +1223,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:
@@ -1249,7 +1249,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"),
@@ -1280,7 +1281,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:
@@ -1308,10 +1311,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
@@ -1326,7 +1329,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 */
 	}
@@ -1366,12 +1371,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;
 }
 
@@ -1552,7 +1559,7 @@ ExecModifyTable(PlanState *pstate)
 	ItemPointer tupleid = NULL;
 	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 0ee76e7..8a6b217 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;
@@ -332,12 +333,6 @@ TidNext(TidScanState *node)
 	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.
 	 */
 	bBackward = ScanDirectionIsBackward(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 083f7d6..52779b7 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"
@@ -354,7 +355,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);
@@ -389,7 +390,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 4e41024..cdd45ef 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -133,40 +133,19 @@ 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,
-		   HeapTuple tuple, Buffer *userbuf, bool keep_buf,
-		   Relation stats_relation);
+extern Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid,
+			int options, BulkInsertState bistate);
 extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
 					   Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
 					   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);
 
 extern BulkInsertState GetBulkInsertState(void);
 extern void FreeBulkInsertState(BulkInsertState);
 extern void ReleaseBulkInsertStatePin(BulkInsertState bistate);
 
-extern Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid,
-			int options, BulkInsertState bistate);
-extern void heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
-				  CommandId cid, int options, BulkInsertState bistate);
-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 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,
-				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);
@@ -179,7 +158,6 @@ extern void simple_heap_delete(Relation relation, ItemPointer tid);
 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);
 
 /* in heap/pruneheap.c */
diff --git a/src/include/access/heapam_common.h b/src/include/access/heapam_common.h
index 1fe15ed..799b4ed 100644
--- a/src/include/access/heapam_common.h
+++ b/src/include/access/heapam_common.h
@@ -34,6 +34,111 @@
 #include "utils/relcache.h"
 #include "utils/snapshot.h"
 
+/*
+ * Each tuple lock mode has a corresponding heavyweight lock, and one or two
+ * corresponding MultiXactStatuses (one to merely lock tuples, another one to
+ * update them).  This table (and the macros below) helps us determine the
+ * heavyweight lock mode and MultiXactStatus values to use for any particular
+ * tuple lock strength.
+ *
+ * Don't look at lockstatus/updstatus directly!  Use get_mxact_status_for_lock
+ * instead.
+ */
+static const struct
+{
+	LOCKMODE	hwlock;
+	int			lockstatus;
+	int			updstatus;
+}
+
+			tupleLockExtraInfo[MaxLockTupleMode + 1] =
+{
+	{							/* LockTupleKeyShare */
+		AccessShareLock,
+		MultiXactStatusForKeyShare,
+		-1						/* KeyShare does not allow updating tuples */
+	},
+	{							/* LockTupleShare */
+		RowShareLock,
+		MultiXactStatusForShare,
+		-1						/* Share does not allow updating tuples */
+	},
+	{							/* LockTupleNoKeyExclusive */
+		ExclusiveLock,
+		MultiXactStatusForNoKeyUpdate,
+		MultiXactStatusNoKeyUpdate
+	},
+	{							/* LockTupleExclusive */
+		AccessExclusiveLock,
+		MultiXactStatusForUpdate,
+		MultiXactStatusUpdate
+	}
+};
+
+/*
+ * This table maps tuple lock strength values for each particular
+ * MultiXactStatus value.
+ */
+static const int MultiXactStatusLock[MaxMultiXactStatus + 1] =
+{
+	LockTupleKeyShare,			/* ForKeyShare */
+	LockTupleShare,				/* ForShare */
+	LockTupleNoKeyExclusive,	/* ForNoKeyUpdate */
+	LockTupleExclusive,			/* ForUpdate */
+	LockTupleNoKeyExclusive,	/* NoKeyUpdate */
+	LockTupleExclusive			/* Update */
+};
+
+/* Get the LockTupleMode for a given MultiXactStatus */
+#define TUPLOCK_from_mxstatus(status) \
+			(MultiXactStatusLock[(status)])
+
+/*
+ * Acquire heavyweight locks on tuples, using a LockTupleMode strength value.
+ * This is more readable than having every caller translate it to lock.h's
+ * LOCKMODE.
+ */
+#define LockTupleTuplock(rel, tup, mode) \
+	LockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
+#define UnlockTupleTuplock(rel, tup, mode) \
+	UnlockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
+#define ConditionalLockTupleTuplock(rel, tup, mode) \
+	ConditionalLockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
+/* Get the LOCKMODE for a given MultiXactStatus */
+#define LOCKMODE_from_mxstatus(status) \
+			(tupleLockExtraInfo[TUPLOCK_from_mxstatus((status))].hwlock)
+extern HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
+					TransactionId xid, CommandId cid, int options);
+
+extern HTSU_Result heap_delete(Relation relation, ItemPointer tid,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			HeapUpdateFailureData *hufd);
+extern HTSU_Result heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			HeapUpdateFailureData *hufd, LockTupleMode *lockmode);
+
+extern XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
+extern uint8 compute_infobits(uint16 infomask, uint16 infomask2);
+extern void compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
+						  uint16 old_infomask2, TransactionId add_to_xmax,
+						  LockTupleMode mode, bool is_update,
+						  TransactionId *result_xmax, uint16 *result_infomask,
+						  uint16 *result_infomask2);
+extern void UpdateXmaxHintBits(HeapTupleHeader tuple, Buffer buffer, TransactionId xid);
+extern bool DoesMultiXactIdConflict(MultiXactId multi, uint16 infomask,
+						LockTupleMode lockmode);
+extern bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
+						   uint16 infomask, Relation rel, int *remaining);
+
+extern void MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
+				Relation rel, ItemPointer ctid, XLTW_Oper oper,
+				int *remaining);
+extern MultiXactStatus get_mxact_status_for_lock(LockTupleMode mode, bool is_update);
+
+extern void heap_inplace_update(Relation relation, HeapTuple tuple);
+extern bool heap_acquire_tuplock(Relation relation, ItemPointer tid,
+					 LockTupleMode mode, LockWaitPolicy wait_policy,
+					 bool *have_tuple_lock);
 
 /* in heap/heapam_common.c */
 extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
@@ -43,6 +148,28 @@ extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin);
 typedef struct StorageSlotAmRoutine StorageSlotAmRoutine;
 extern StorageSlotAmRoutine* heapam_storage_slot_handler(void);
 
+
+/*
+ * Given two versions of the same t_infomask for a tuple, compare them and
+ * return whether the relevant status for a tuple Xmax has changed.  This is
+ * used after a buffer lock has been released and reacquired: we want to ensure
+ * that the tuple state continues to be the same it was when we previously
+ * examined it.
+ *
+ * Note the Xmax field itself must be compared separately.
+ */
+static inline bool
+xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask)
+{
+	const uint16 interesting =
+	HEAP_XMAX_IS_MULTI | HEAP_XMAX_LOCK_ONLY | HEAP_LOCK_MASK;
+
+	if ((new_infomask & interesting) != (old_infomask & interesting))
+		return true;
+
+	return false;
+}
+
 /*
  * SetHintBits()
  *
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index 9539d67..16dfb3e 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -811,6 +811,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/storageam.h b/src/include/access/storageam.h
new file mode 100644
index 0000000..9502c92
--- /dev/null
+++ b/src/include/access/storageam.h
@@ -0,0 +1,81 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "executor/tuptable.h"
+
+/* A physical tuple coming from a storage AM scan */
+typedef void *StorageTuple;
+
+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;
+
+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);
+
+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,
+				   CommandId cid, Snapshot crosscheck, bool wait,
+				   HeapUpdateFailureData *hufd, LockTupleMode *lockmode);
+
+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 void storage_finish_speculative(Relation relation, TupleTableSlot *slot);
+
+extern tuple_data storage_tuple_get_data(Relation relation, StorageTuple tuple, tuple_data_flags flags);
+
+extern bool storage_tuple_is_heaponly(Relation relation, StorageTuple tuple);
+
+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 95fe028..c2e6dc2 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -13,32 +13,13 @@
 
 #include "access/htup.h"
 #include "access/heapam.h"
+#include "access/storageam.h"
 #include "access/sdir.h"
 #include "access/skey.h"
 #include "executor/tuptable.h"
 #include "utils/relcache.h"
 #include "utils/snapshot.h"
 
-/* A physical tuple coming from a storage AM scan */
-typedef void *StorageTuple;
-
-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;
-
-
 typedef HeapScanDesc (*scan_begin_hook) (Relation relation,
 										Snapshot snapshot,
 										int nkeys, ScanKey key,
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index aeb363f..0da9ac9 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -189,7 +189,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 7708818..8704b7b 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -196,7 +196,7 @@ 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,
+extern StorageTuple EvalPlanQualFetch(EState *estate, Relation relation,
 				  int lockmode, LockWaitPolicy wait_policy, ItemPointer tid,
 				  TransactionId priorXmax);
 extern void EvalPlanQualInit(EPQState *epqstate, EState *estate,
@@ -204,8 +204,8 @@ extern void EvalPlanQualInit(EPQState *epqstate, EState *estate,
 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);
 extern void ExecSetupPartitionTupleRouting(Relation rel,
 							   Index resultRTindex,
 							   EState *estate,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 90a60ab..1ed1864 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -503,7 +503,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 */
 
@@ -2021,7 +2021,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.7.4.windows.1

0007-Scan-functions-are-added-to-storage-AM.patchapplication/octet-stream; name=0007-Scan-functions-are-added-to-storage-AM.patchDownload
From efaf90b264d97f08233b6532717e1f5166f1f164 Mon Sep 17 00:00:00 2001
From: Hari Babu Kommi <kommi.haribabu@gmail.com>
Date: Fri, 8 Sep 2017 15:45:04 +1000
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           | 1504 ++--------------------------
 src/backend/access/heap/heapam_storage.c   | 1299 ++++++++++++++++++++++++
 src/backend/access/heap/rewriteheap.c      |    2 +-
 src/backend/access/heap/storageam.c        |  235 +++++
 src/backend/access/index/genam.c           |    7 +-
 src/backend/access/index/indexam.c         |    3 +-
 src/backend/access/nbtree/nbtinsert.c      |    5 +-
 src/backend/bootstrap/bootstrap.c          |   25 +-
 src/backend/catalog/aclchk.c               |   13 +-
 src/backend/catalog/index.c                |   27 +-
 src/backend/catalog/partition.c            |    6 +-
 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/trigger.c             |    3 +-
 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     |   16 +-
 src/backend/executor/execTuples.c          |    8 +-
 src/backend/executor/functions.c           |    4 +-
 src/backend/executor/nodeAgg.c             |    4 +-
 src/backend/executor/nodeBitmapHeapscan.c  |   11 +-
 src/backend/executor/nodeForeignscan.c     |    6 +-
 src/backend/executor/nodeGather.c          |    8 +-
 src/backend/executor/nodeGatherMerge.c     |   12 +-
 src/backend/executor/nodeIndexonlyscan.c   |    4 +-
 src/backend/executor/nodeIndexscan.c       |   16 +-
 src/backend/executor/nodeSamplescan.c      |   21 +-
 src/backend/executor/nodeSeqscan.c         |   39 +-
 src/backend/executor/nodeWindowAgg.c       |    4 +-
 src/backend/executor/spi.c                 |   20 +-
 src/backend/executor/tqueue.c              |   16 +-
 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                |   30 +-
 src/include/access/heapam_common.h         |    8 +
 src/include/access/storageam.h             |   42 +-
 src/include/executor/functions.h           |    2 +-
 src/include/executor/spi.h                 |   10 +-
 src/include/executor/tqueue.h              |    2 +-
 src/include/funcapi.h                      |    2 +-
 56 files changed, 1924 insertions(+), 1734 deletions(-)

diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index 5f076ef..063e079 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 f7b68a8..eb33b26 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 d20f211..b64fec8 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -81,19 +81,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 XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 				Buffer newbuf, HeapTuple oldtup,
 				HeapTuple newtup, HeapTuple old_key_tup,
@@ -112,139 +99,6 @@ static bool Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
 static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_modified,
 					   bool *copy);
 
-/* ----------------------------------------------------------------
- *						 heap support routines
- * ----------------------------------------------------------------
- */
-
-/* ----------------
- *		initscan - scan code common to heap_beginscan and heap_rescan
- * ----------------
- */
-static void
-initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
-{
-	bool		allow_strat;
-	bool		allow_sync;
-
-	/*
-	 * Determine the number of blocks we have to scan.
-	 *
-	 * It is sufficient to do this once at scan start, since any tuples added
-	 * while the scan is in progress will be invisible to my snapshot anyway.
-	 * (That is not true when using a non-MVCC snapshot.  However, we couldn't
-	 * guarantee to return tuples added after scan start anyway, since they
-	 * might go into pages we already scanned.  To guarantee consistent
-	 * results for a non-MVCC snapshot, the caller must hold some higher-level
-	 * lock that ensures the interesting tuple(s) won't change.)
-	 */
-	if (scan->rs_parallel != NULL)
-		scan->rs_nblocks = scan->rs_parallel->phs_nblocks;
-	else
-		scan->rs_nblocks = RelationGetNumberOfBlocks(scan->rs_rd);
-
-	/*
-	 * If the table is large relative to NBuffers, use a bulk-read access
-	 * strategy and enable synchronized scanning (see syncscan.c).  Although
-	 * the thresholds for these features could be different, we make them the
-	 * same so that there are only two behaviors to tune rather than four.
-	 * (However, some callers need to be able to disable one or both of these
-	 * behaviors, independently of the size of the table; also there is a GUC
-	 * variable that can disable synchronized scanning.)
-	 *
-	 * 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)
-	{
-		allow_strat = scan->rs_allow_strat;
-		allow_sync = scan->rs_allow_sync;
-	}
-	else
-		allow_strat = allow_sync = false;
-
-	if (allow_strat)
-	{
-		/* During a rescan, keep the previous strategy object. */
-		if (scan->rs_strategy == NULL)
-			scan->rs_strategy = GetAccessStrategy(BAS_BULKREAD);
-	}
-	else
-	{
-		if (scan->rs_strategy != NULL)
-			FreeAccessStrategy(scan->rs_strategy);
-		scan->rs_strategy = NULL;
-	}
-
-	if (scan->rs_parallel != NULL)
-	{
-		/* For parallel scan, believe whatever ParallelHeapScanDesc says. */
-		scan->rs_syncscan = scan->rs_parallel->phs_syncscan;
-	}
-	else if (keep_startblock)
-	{
-		/*
-		 * When rescanning, we want to keep the previous startblock setting,
-		 * so that rewinding a cursor doesn't generate surprising results.
-		 * Reset the active syncscan setting, though.
-		 */
-		scan->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);
-	}
-	else
-	{
-		scan->rs_syncscan = false;
-		scan->rs_startblock = 0;
-	}
-
-	scan->rs_numblocks = InvalidBlockNumber;
-	scan->rs_inited = false;
-	scan->rs_ctup.t_data = NULL;
-	ItemPointerSetInvalid(&scan->rs_ctup.t_self);
-	scan->rs_cbuf = InvalidBuffer;
-	scan->rs_cblock = InvalidBlockNumber;
-
-	/* page-at-a-time fields are always invalid when not rs_inited */
-
-	/*
-	 * copy the scan key, if appropriate
-	 */
-	if (key != NULL)
-		memcpy(scan->rs_key, key, scan->rs_nkeys * sizeof(ScanKeyData));
-
-	/*
-	 * Currently, we don't have a stats counter for bitmap heap scans (but the
-	 * underlying bitmap index scans will be counted) or sample scans (we only
-	 * update stats for tuple fetches there)
-	 */
-	if (!scan->rs_bitmapscan && !scan->rs_samplescan)
-		pgstat_count_heap_scan(scan->rs_rd);
-}
-
-/*
- * 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
-heap_setscanlimits(HeapScanDesc scan, BlockNumber startBlk, BlockNumber numBlks)
-{
-	Assert(!scan->rs_inited);	/* else too late to change */
-	Assert(!scan->rs_syncscan); /* else rs_startblock is significant */
-
-	/* Check startBlk is valid (but allow case of zero blocks...) */
-	Assert(startBlk == 0 || startBlk < scan->rs_nblocks);
-
-	scan->rs_startblock = startBlk;
-	scan->rs_numblocks = numBlks;
-}
-
 /*
  * heapgetpage - subroutine for heapgettup()
  *
@@ -363,603 +217,6 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 	scan->rs_ntuples = ntup;
 }
 
-/* ----------------
- *		heapgettup - fetch next heap tuple
- *
- *		Initialize the scan if not already done; then advance to the next
- *		tuple as indicated by "dir"; return the next tuple in scan->rs_ctup,
- *		or set scan->rs_ctup.t_data = NULL if no more tuples.
- *
- * dir == NoMovementScanDirection means "re-fetch the tuple indicated
- * by scan->rs_ctup".
- *
- * Note: the reason nkeys/key are passed separately, even though they are
- * kept in the scan descriptor, is that the caller may not want us to check
- * the scankeys.
- *
- * Note: when we fall off the end of the scan in either direction, we
- * reset rs_inited.  This means that a further request with the same
- * scan direction will restart the scan, which is a bit odd, but a
- * request with the opposite scan direction will start a fresh scan
- * in the proper direction.  The latter is required behavior for cursors,
- * while the former case is generally undefined behavior in Postgres
- * so we don't care too much.
- * ----------------
- */
-static void
-heapgettup(HeapScanDesc scan,
-		   ScanDirection dir,
-		   int nkeys,
-		   ScanKey key)
-{
-	HeapTuple	tuple = &(scan->rs_ctup);
-	Snapshot	snapshot = scan->rs_snapshot;
-	bool		backward = ScanDirectionIsBackward(dir);
-	BlockNumber page;
-	bool		finished;
-	Page		dp;
-	int			lines;
-	OffsetNumber lineoff;
-	int			linesleft;
-	ItemId		lpp;
-
-	/*
-	 * calculate next starting lineoff, given scan direction
-	 */
-	if (ScanDirectionIsForward(dir))
-	{
-		if (!scan->rs_inited)
-		{
-			/*
-			 * return null immediately if relation is empty
-			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
-			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
-				tuple->t_data = NULL;
-				return;
-			}
-			if (scan->rs_parallel != NULL)
-			{
-				heap_parallelscan_startblock_init(scan);
-
-				page = heap_parallelscan_nextpage(scan);
-
-				/* Other processes might have already finished the scan. */
-				if (page == InvalidBlockNumber)
-				{
-					Assert(!BufferIsValid(scan->rs_cbuf));
-					tuple->t_data = NULL;
-					return;
-				}
-			}
-			else
-				page = scan->rs_startblock; /* first page */
-			heapgetpage(scan, page);
-			lineoff = FirstOffsetNumber;	/* first offnum */
-			scan->rs_inited = true;
-		}
-		else
-		{
-			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-			lineoff =			/* next offnum */
-				OffsetNumberNext(ItemPointerGetOffsetNumber(&(tuple->t_self)));
-		}
-
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
-		lines = PageGetMaxOffsetNumber(dp);
-		/* page and lineoff now reference the physically next tid */
-
-		linesleft = lines - lineoff + 1;
-	}
-	else if (backward)
-	{
-		/* backward parallel scan not supported */
-		Assert(scan->rs_parallel == NULL);
-
-		if (!scan->rs_inited)
-		{
-			/*
-			 * return null immediately if relation is empty
-			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
-			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
-				tuple->t_data = NULL;
-				return;
-			}
-
-			/*
-			 * Disable reporting to syncscan logic in a backwards scan; it's
-			 * not very likely anyone else is doing the same thing at the same
-			 * time, and much more likely that we'll just bollix things for
-			 * forward scanners.
-			 */
-			scan->rs_syncscan = false;
-			/* start from last page of the scan */
-			if (scan->rs_startblock > 0)
-				page = scan->rs_startblock - 1;
-			else
-				page = scan->rs_nblocks - 1;
-			heapgetpage(scan, page);
-		}
-		else
-		{
-			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-		}
-
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
-		lines = PageGetMaxOffsetNumber(dp);
-
-		if (!scan->rs_inited)
-		{
-			lineoff = lines;	/* final offnum */
-			scan->rs_inited = true;
-		}
-		else
-		{
-			lineoff =			/* previous offnum */
-				OffsetNumberPrev(ItemPointerGetOffsetNumber(&(tuple->t_self)));
-		}
-		/* page and lineoff now reference the physically previous tid */
-
-		linesleft = lineoff;
-	}
-	else
-	{
-		/*
-		 * ``no movement'' scan direction: refetch prior tuple
-		 */
-		if (!scan->rs_inited)
-		{
-			Assert(!BufferIsValid(scan->rs_cbuf));
-			tuple->t_data = NULL;
-			return;
-		}
-
-		page = ItemPointerGetBlockNumber(&(tuple->t_self));
-		if (page != scan->rs_cblock)
-			heapgetpage(scan, page);
-
-		/* Since the tuple was previously fetched, needn't lock page here */
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
-		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
-		lpp = PageGetItemId(dp, lineoff);
-		Assert(ItemIdIsNormal(lpp));
-
-		tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
-		tuple->t_len = ItemIdGetLength(lpp);
-
-		return;
-	}
-
-	/*
-	 * advance the scan until we find a qualifying tuple or run out of stuff
-	 * to scan
-	 */
-	lpp = PageGetItemId(dp, lineoff);
-	for (;;)
-	{
-		while (linesleft > 0)
-		{
-			if (ItemIdIsNormal(lpp))
-			{
-				bool		valid;
-
-				tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
-				tuple->t_len = ItemIdGetLength(lpp);
-				ItemPointerSet(&(tuple->t_self), page, lineoff);
-
-				/*
-				 * if current tuple qualifies, return it.
-				 */
-				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine,
-													 tuple,
-													 snapshot,
-													 scan->rs_cbuf);
-
-				CheckForSerializableConflictOut(valid, scan->rs_rd, tuple,
-												scan->rs_cbuf, snapshot);
-
-				if (valid && key != NULL)
-					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
-								nkeys, key, valid);
-
-				if (valid)
-				{
-					LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
-					return;
-				}
-			}
-
-			/*
-			 * otherwise move to the next item on the page
-			 */
-			--linesleft;
-			if (backward)
-			{
-				--lpp;			/* move back in this page's ItemId array */
-				--lineoff;
-			}
-			else
-			{
-				++lpp;			/* move forward in this page's ItemId array */
-				++lineoff;
-			}
-		}
-
-		/*
-		 * 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);
-
-		/*
-		 * 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);
-			if (page == 0)
-				page = scan->rs_nblocks;
-			page--;
-		}
-		else if (scan->rs_parallel != NULL)
-		{
-			page = heap_parallelscan_nextpage(scan);
-			finished = (page == InvalidBlockNumber);
-		}
-		else
-		{
-			page++;
-			if (page >= scan->rs_nblocks)
-				page = 0;
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
-
-			/*
-			 * Report our new scan position for synchronization purposes. We
-			 * don't do that when moving backwards, however. That would just
-			 * mess up any other forward-moving scanners.
-			 *
-			 * Note: we do this before checking for end of scan so that the
-			 * final state of the position hint is back at the start of the
-			 * rel.  That's not strictly necessary, but otherwise when you run
-			 * the same query multiple times the starting position would shift
-			 * 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);
-		}
-
-		/*
-		 * return NULL if we've exhausted all the pages
-		 */
-		if (finished)
-		{
-			if (BufferIsValid(scan->rs_cbuf))
-				ReleaseBuffer(scan->rs_cbuf);
-			scan->rs_cbuf = InvalidBuffer;
-			scan->rs_cblock = InvalidBlockNumber;
-			tuple->t_data = NULL;
-			scan->rs_inited = false;
-			return;
-		}
-
-		heapgetpage(scan, page);
-
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
-		lines = PageGetMaxOffsetNumber((Page) dp);
-		linesleft = lines;
-		if (backward)
-		{
-			lineoff = lines;
-			lpp = PageGetItemId(dp, lines);
-		}
-		else
-		{
-			lineoff = FirstOffsetNumber;
-			lpp = PageGetItemId(dp, FirstOffsetNumber);
-		}
-	}
-}
-
-/* ----------------
- *		heapgettup_pagemode - fetch next heap tuple in page-at-a-time mode
- *
- *		Same API as heapgettup, but used in page-at-a-time mode
- *
- * The internal logic is much the same as heapgettup's too, but there are some
- * differences: we do not take the buffer content lock (that only needs to
- * happen inside heapgetpage), and we iterate through just the tuples listed
- * in rs_vistuples[] rather than all tuples on the page.  Notice that
- * lineindex is 0-based, where the corresponding loop variable lineoff in
- * heapgettup is 1-based.
- * ----------------
- */
-static void
-heapgettup_pagemode(HeapScanDesc scan,
-					ScanDirection dir,
-					int nkeys,
-					ScanKey key)
-{
-	HeapTuple	tuple = &(scan->rs_ctup);
-	bool		backward = ScanDirectionIsBackward(dir);
-	BlockNumber page;
-	bool		finished;
-	Page		dp;
-	int			lines;
-	int			lineindex;
-	OffsetNumber lineoff;
-	int			linesleft;
-	ItemId		lpp;
-
-	/*
-	 * calculate next starting lineindex, given scan direction
-	 */
-	if (ScanDirectionIsForward(dir))
-	{
-		if (!scan->rs_inited)
-		{
-			/*
-			 * return null immediately if relation is empty
-			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
-			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
-				tuple->t_data = NULL;
-				return;
-			}
-			if (scan->rs_parallel != NULL)
-			{
-				heap_parallelscan_startblock_init(scan);
-
-				page = heap_parallelscan_nextpage(scan);
-
-				/* Other processes might have already finished the scan. */
-				if (page == InvalidBlockNumber)
-				{
-					Assert(!BufferIsValid(scan->rs_cbuf));
-					tuple->t_data = NULL;
-					return;
-				}
-			}
-			else
-				page = scan->rs_startblock; /* first page */
-			heapgetpage(scan, page);
-			lineindex = 0;
-			scan->rs_inited = true;
-		}
-		else
-		{
-			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-			lineindex = scan->rs_cindex + 1;
-		}
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
-		/* page and lineindex now reference the next visible tid */
-
-		linesleft = lines - lineindex;
-	}
-	else if (backward)
-	{
-		/* backward parallel scan not supported */
-		Assert(scan->rs_parallel == NULL);
-
-		if (!scan->rs_inited)
-		{
-			/*
-			 * return null immediately if relation is empty
-			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
-			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
-				tuple->t_data = NULL;
-				return;
-			}
-
-			/*
-			 * Disable reporting to syncscan logic in a backwards scan; it's
-			 * not very likely anyone else is doing the same thing at the same
-			 * time, and much more likely that we'll just bollix things for
-			 * forward scanners.
-			 */
-			scan->rs_syncscan = false;
-			/* start from last page of the scan */
-			if (scan->rs_startblock > 0)
-				page = scan->rs_startblock - 1;
-			else
-				page = scan->rs_nblocks - 1;
-			heapgetpage(scan, page);
-		}
-		else
-		{
-			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-		}
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
-
-		if (!scan->rs_inited)
-		{
-			lineindex = lines - 1;
-			scan->rs_inited = true;
-		}
-		else
-		{
-			lineindex = scan->rs_cindex - 1;
-		}
-		/* page and lineindex now reference the previous visible tid */
-
-		linesleft = lineindex + 1;
-	}
-	else
-	{
-		/*
-		 * ``no movement'' scan direction: refetch prior tuple
-		 */
-		if (!scan->rs_inited)
-		{
-			Assert(!BufferIsValid(scan->rs_cbuf));
-			tuple->t_data = NULL;
-			return;
-		}
-
-		page = ItemPointerGetBlockNumber(&(tuple->t_self));
-		if (page != scan->rs_cblock)
-			heapgetpage(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);
-		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
-		lpp = PageGetItemId(dp, lineoff);
-		Assert(ItemIdIsNormal(lpp));
-
-		tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
-		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]);
-
-		return;
-	}
-
-	/*
-	 * advance the scan until we find a qualifying tuple or run out of stuff
-	 * to scan
-	 */
-	for (;;)
-	{
-		while (linesleft > 0)
-		{
-			lineoff = scan->rs_vistuples[lineindex];
-			lpp = PageGetItemId(dp, lineoff);
-			Assert(ItemIdIsNormal(lpp));
-
-			tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
-			tuple->t_len = ItemIdGetLength(lpp);
-			ItemPointerSet(&(tuple->t_self), page, lineoff);
-
-			/*
-			 * if current tuple qualifies, return it.
-			 */
-			if (key != NULL)
-			{
-				bool		valid;
-
-				HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
-							nkeys, key, valid);
-				if (valid)
-				{
-					scan->rs_cindex = lineindex;
-					return;
-				}
-			}
-			else
-			{
-				scan->rs_cindex = lineindex;
-				return;
-			}
-
-			/*
-			 * otherwise move to the next item on the page
-			 */
-			--linesleft;
-			if (backward)
-				--lineindex;
-			else
-				++lineindex;
-		}
-
-		/*
-		 * if we get here, it means we've exhausted the items on this page and
-		 * it's time to move to the next.
-		 */
-		if (backward)
-		{
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
-			if (page == 0)
-				page = scan->rs_nblocks;
-			page--;
-		}
-		else if (scan->rs_parallel != NULL)
-		{
-			page = heap_parallelscan_nextpage(scan);
-			finished = (page == InvalidBlockNumber);
-		}
-		else
-		{
-			page++;
-			if (page >= scan->rs_nblocks)
-				page = 0;
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
-
-			/*
-			 * Report our new scan position for synchronization purposes. We
-			 * don't do that when moving backwards, however. That would just
-			 * mess up any other forward-moving scanners.
-			 *
-			 * Note: we do this before checking for end of scan so that the
-			 * final state of the position hint is back at the start of the
-			 * rel.  That's not strictly necessary, but otherwise when you run
-			 * the same query multiple times the starting position would shift
-			 * 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);
-		}
-
-		/*
-		 * return NULL if we've exhausted all the pages
-		 */
-		if (finished)
-		{
-			if (BufferIsValid(scan->rs_cbuf))
-				ReleaseBuffer(scan->rs_cbuf);
-			scan->rs_cbuf = InvalidBuffer;
-			scan->rs_cblock = InvalidBlockNumber;
-			tuple->t_data = NULL;
-			scan->rs_inited = false;
-			return;
-		}
-
-		heapgetpage(scan, page);
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
-		linesleft = lines;
-		if (backward)
-			lineindex = lines - 1;
-		else
-			lineindex = 0;
-	}
-}
-
 
 #if defined(DISABLE_COMPLEX_MACRO)
 /*
@@ -1186,317 +443,96 @@ relation_close(Relation relation, LOCKMODE lockmode)
 		UnlockRelationId(&relid, lockmode);
 }
 
-
-/* ----------------
- *		heap_open - open a heap relation by relation OID
- *
- *		This is essentially relation_open plus check that the relation
- *		is not an index nor a composite type.  (The caller should also
- *		check that it's not a view or foreign table before assuming it has
- *		storage.)
- * ----------------
- */
-Relation
-heap_open(Oid relationId, LOCKMODE lockmode)
-{
-	Relation	r;
-
-	r = relation_open(relationId, lockmode);
-
-	if (r->rd_rel->relkind == RELKIND_INDEX)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is an index",
-						RelationGetRelationName(r))));
-	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is a composite type",
-						RelationGetRelationName(r))));
-
-	return r;
-}
-
-/* ----------------
- *		heap_openrv - open a heap relation specified
- *		by a RangeVar node
- *
- *		As above, but relation is specified by a RangeVar.
- * ----------------
- */
-Relation
-heap_openrv(const RangeVar *relation, LOCKMODE lockmode)
-{
-	Relation	r;
-
-	r = relation_openrv(relation, lockmode);
-
-	if (r->rd_rel->relkind == RELKIND_INDEX)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is an index",
-						RelationGetRelationName(r))));
-	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is a composite type",
-						RelationGetRelationName(r))));
-
-	return r;
-}
-
-/* ----------------
- *		heap_openrv_extended - open a heap relation specified
- *		by a RangeVar node
- *
- *		As above, but optionally return NULL instead of failing for
- *		relation-not-found.
- * ----------------
- */
-Relation
-heap_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
-					 bool missing_ok)
-{
-	Relation	r;
-
-	r = relation_openrv_extended(relation, lockmode, missing_ok);
-
-	if (r)
-	{
-		if (r->rd_rel->relkind == RELKIND_INDEX)
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("\"%s\" is an index",
-							RelationGetRelationName(r))));
-		else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("\"%s\" is a composite type",
-							RelationGetRelationName(r))));
-	}
-
-	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)
-{
-	HeapScanDesc scan;
-
-	/*
-	 * increment relation ref count while scanning relation
-	 *
-	 * This is just to make really sure the relcache entry won't go away while
-	 * the scan has a pointer to it.  Caller should be holding the rel open
-	 * anyway, so this is redundant in all normal scenarios...
-	 */
-	RelationIncrementReferenceCount(relation);
-
-	/*
-	 * allocate and initialize scan descriptor
-	 */
-	scan = (HeapScanDesc) palloc(sizeof(HeapScanDescData));
-
-	scan->rs_rd = relation;
-	scan->rs_snapshot = snapshot;
-	scan->rs_nkeys = nkeys;
-	scan->rs_bitmapscan = is_bitmapscan;
-	scan->rs_samplescan = is_samplescan;
-	scan->rs_strategy = NULL;	/* set in initscan */
-	scan->rs_allow_strat = allow_strat;
-	scan->rs_allow_sync = allow_sync;
-	scan->rs_temp_snap = temp_snap;
-	scan->rs_parallel = parallel_scan;
-
-	/*
-	 * we can use page-at-a-time mode if it's an MVCC-safe snapshot
-	 */
-	scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(snapshot);
-
-	/*
-	 * For a seqscan in a serializable transaction, acquire a predicate lock
-	 * on the entire relation. This is required not only to lock all the
-	 * matching tuples, but also to conflict with new insertions into the
-	 * table. In an indexscan, we take page locks on the index pages covering
-	 * the range specified in the scan qual, but in a heap scan there is
-	 * nothing more fine-grained to lock. A bitmap scan is a different story,
-	 * there we have already scanned the index and locked the index pages
-	 * covering the predicate. But in that case we still have to lock any
-	 * matching heap tuples.
-	 */
-	if (!is_bitmapscan)
-		PredicateLockRelation(relation, snapshot);
-
-	/* we only need to set this up once */
-	scan->rs_ctup.t_tableOid = RelationGetRelid(relation);
-
-	/*
-	 * we do this here instead of in initscan() because heap_rescan also calls
-	 * initscan() and we don't want to allocate memory again
-	 */
-	if (nkeys > 0)
-		scan->rs_key = (ScanKey) palloc(sizeof(ScanKeyData) * nkeys);
-	else
-		scan->rs_key = NULL;
-
-	initscan(scan, key, false);
-
-	return scan;
-}
-
+
 /* ----------------
- *		heap_rescan		- restart a relation scan
+ *		heap_open - open a heap relation by relation OID
+ *
+ *		This is essentially relation_open plus check that the relation
+ *		is not an index nor a composite type.  (The caller should also
+ *		check that it's not a view or foreign table before assuming it has
+ *		storage.)
  * ----------------
  */
-void
-heap_rescan(HeapScanDesc scan,
-			ScanKey key)
+Relation
+heap_open(Oid relationId, LOCKMODE lockmode)
 {
-	/*
-	 * unpin scan buffers
-	 */
-	if (BufferIsValid(scan->rs_cbuf))
-		ReleaseBuffer(scan->rs_cbuf);
+	Relation	r;
 
-	/*
-	 * reinitialize scan descriptor
-	 */
-	initscan(scan, key, true);
+	r = relation_open(relationId, lockmode);
+
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is an index",
+						RelationGetRelationName(r))));
+	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is a composite type",
+						RelationGetRelationName(r))));
+
+	return r;
 }
 
 /* ----------------
- *		heap_rescan_set_params	- restart a relation scan after changing params
+ *		heap_openrv - open a heap relation specified
+ *		by a RangeVar node
  *
- * 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.
+ *		As above, but relation is specified by a RangeVar.
  * ----------------
  */
-void
-heap_rescan_set_params(HeapScanDesc scan, ScanKey key,
-					   bool allow_strat, bool allow_sync, bool allow_pagemode)
+Relation
+heap_openrv(const RangeVar *relation, LOCKMODE lockmode)
 {
-	/* 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);
+	Relation	r;
+
+	r = relation_openrv(relation, lockmode);
+
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is an index",
+						RelationGetRelationName(r))));
+	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is a composite type",
+						RelationGetRelationName(r))));
+
+	return r;
 }
 
 /* ----------------
- *		heap_endscan	- end relation scan
+ *		heap_openrv_extended - open a heap relation specified
+ *		by a RangeVar node
  *
- *		See how to integrate with index scans.
- *		Check handling if reldesc caching.
+ *		As above, but optionally return NULL instead of failing for
+ *		relation-not-found.
  * ----------------
  */
-void
-heap_endscan(HeapScanDesc scan)
+Relation
+heap_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
+					 bool missing_ok)
 {
-	/* Note: no locking manipulations needed */
-
-	/*
-	 * unpin scan buffers
-	 */
-	if (BufferIsValid(scan->rs_cbuf))
-		ReleaseBuffer(scan->rs_cbuf);
-
-	/*
-	 * decrement relation reference count and free scan descriptor storage
-	 */
-	RelationDecrementReferenceCount(scan->rs_rd);
-
-	if (scan->rs_key)
-		pfree(scan->rs_key);
+	Relation	r;
 
-	if (scan->rs_strategy != NULL)
-		FreeAccessStrategy(scan->rs_strategy);
+	r = relation_openrv_extended(relation, lockmode, missing_ok);
 
-	if (scan->rs_temp_snap)
-		UnregisterSnapshot(scan->rs_snapshot);
+	if (r)
+	{
+		if (r->rd_rel->relkind == RELKIND_INDEX)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("\"%s\" is an index",
+							RelationGetRelationName(r))));
+		else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("\"%s\" is a composite type",
+							RelationGetRelationName(r))));
+	}
 
-	pfree(scan);
+	return r;
 }
 
 /* ----------------
@@ -1550,384 +586,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
- *
- *		Determine where the parallel seq scan should start.  This function may
- *		be called many times, once by each parallel worker.  We must be careful
- *		only to set the startblock once.
- * ----------------
- */
-static void
-heap_parallelscan_startblock_init(HeapScanDesc scan)
-{
-	BlockNumber sync_startpage = InvalidBlockNumber;
-	ParallelHeapScanDesc parallel_scan;
-
-	Assert(scan->rs_parallel);
-	parallel_scan = scan->rs_parallel;
-
-retry:
-	/* Grab the spinlock. */
-	SpinLockAcquire(&parallel_scan->phs_mutex);
-
-	/*
-	 * If the scan's startblock has not yet been initialized, we must do so
-	 * now.  If this is not a synchronized scan, we just start at block 0, but
-	 * if it is a synchronized scan, we must get the starting position from
-	 * the synchronized scan machinery.  We can't hold the spinlock while
-	 * doing that, though, so release the spinlock, get the information we
-	 * need, and retry.  If nobody else has initialized the scan in the
-	 * meantime, we'll fill in the value we fetched on the second time
-	 * through.
-	 */
-	if (parallel_scan->phs_startblock == InvalidBlockNumber)
-	{
-		if (!parallel_scan->phs_syncscan)
-			parallel_scan->phs_startblock = 0;
-		else if (sync_startpage != InvalidBlockNumber)
-			parallel_scan->phs_startblock = sync_startpage;
-		else
-		{
-			SpinLockRelease(&parallel_scan->phs_mutex);
-			sync_startpage = ss_get_location(scan->rs_rd, scan->rs_nblocks);
-			goto retry;
-		}
-	}
-	SpinLockRelease(&parallel_scan->phs_mutex);
-}
-
-/* ----------------
- *		heap_parallelscan_nextpage - get the next page to scan
- *
- *		Get the next page to scan.  Even if there are no pages left to scan,
- *		another backend could have grabbed a page to scan and not yet finished
- *		looking at it, so it doesn't follow that the scan is done when the
- *		first backend gets an InvalidBlockNumber return.
- * ----------------
- */
-static BlockNumber
-heap_parallelscan_nextpage(HeapScanDesc scan)
-{
-	BlockNumber page;
-	ParallelHeapScanDesc parallel_scan;
-	uint64		nallocated;
-
-	Assert(scan->rs_parallel);
-	parallel_scan = scan->rs_parallel;
-
-	/*
-	 * phs_nallocated tracks how many pages have been allocated to workers
-	 * already.  When phs_nallocated >= rs_nblocks, all blocks have been
-	 * allocated.
-	 *
-	 * Because we use an atomic fetch-and-add to fetch the current value, the
-	 * phs_nallocated counter will exceed rs_nblocks, because workers will
-	 * still increment the value, when they try to allocate the next block but
-	 * all blocks have been allocated already. The counter must be 64 bits
-	 * wide because of that, to avoid wrapping around when rs_nblocks is close
-	 * to 2^32.
-	 *
-	 * The actual page to return is calculated by adding the counter to the
-	 * starting block number, modulo nblocks.
-	 */
-	nallocated = pg_atomic_fetch_add_u64(&parallel_scan->phs_nallocated, 1);
-	if (nallocated >= scan->rs_nblocks)
-		page = InvalidBlockNumber;	/* all blocks have been allocated */
-	else
-		page = (nallocated + parallel_scan->phs_startblock) % scan->rs_nblocks;
-
-	/*
-	 * Report scan location.  Normally, we report the current page number.
-	 * When we reach the end of the scan, though, we report the starting page,
-	 * not the ending page, just so the starting positions for later scans
-	 * 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 (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);
-	}
-
-	return page;
-}
-
-/* ----------------
- *		heap_update_snapshot
- *
- *		Update snapshot info in heap scan descriptor.
- * ----------------
- */
-void
-heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
-{
-	Assert(IsMVCCSnapshot(snapshot));
-
-	RegisterSnapshot(snapshot);
-	scan->rs_snapshot = snapshot;
-	scan->rs_temp_snap = true;
-}
-
-/* ----------------
- *		heap_getnext	- retrieve next tuple in scan
- *
- *		Fix to work with index relations.
- *		We don't return the buffer anymore, but you can get it from the
- *		returned HeapTuple.
- * ----------------
- */
-
-#ifdef HEAPDEBUGALL
-#define HEAPDEBUG_1 \
-	elog(DEBUG2, "heap_getnext([%s,nkeys=%d],dir=%d) called", \
-		 RelationGetRelationName(scan->rs_rd), scan->rs_nkeys, (int) direction)
-#define HEAPDEBUG_2 \
-	elog(DEBUG2, "heap_getnext returning EOS")
-#define HEAPDEBUG_3 \
-	elog(DEBUG2, "heap_getnext returning tuple")
-#else
-#define HEAPDEBUG_1
-#define HEAPDEBUG_2
-#define HEAPDEBUG_3
-#endif							/* !defined(HEAPDEBUGALL) */
-
-
-HeapTuple
-heap_getnext(HeapScanDesc scan, ScanDirection direction)
-{
-	/* Note: no locking manipulations needed */
-
-	HEAPDEBUG_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)
-	{
-		HEAPDEBUG_2;			/* heap_getnext returning EOS */
-		return NULL;
-	}
-
-	/*
-	 * if we get here it means we have a new current scan tuple, so point to
-	 * the proper return buffer and return the tuple.
-	 */
-	HEAPDEBUG_3;				/* heap_getnext returning tuple */
-
-	pgstat_count_heap_getnext(scan->rs_rd);
-
-	return &(scan->rs_ctup);
-}
-
-/*
- *	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
-heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
-					   Snapshot snapshot, HeapTuple heapTuple,
-					   bool *all_dead, bool first_call)
-{
-	Page		dp = (Page) BufferGetPage(buffer);
-	TransactionId prev_xmax = InvalidTransactionId;
-	OffsetNumber offnum;
-	bool		at_chain_start;
-	bool		valid;
-	bool		skip;
-
-	/* If this is not the first call, previous call returned a (live!) tuple */
-	if (all_dead)
-		*all_dead = first_call;
-
-	Assert(TransactionIdIsValid(RecentGlobalXmin));
-
-	Assert(ItemPointerGetBlockNumber(tid) == BufferGetBlockNumber(buffer));
-	offnum = ItemPointerGetOffsetNumber(tid);
-	at_chain_start = first_call;
-	skip = !first_call;
-
-	heapTuple->t_self = *tid;
-
-	/* Scan through possible multiple members of HOT-chain */
-	for (;;)
-	{
-		ItemId		lp;
-
-		/* check for bogus TID */
-		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(dp))
-			break;
-
-		lp = PageGetItemId(dp, offnum);
-
-		/* check for unused, dead, or redirected items */
-		if (!ItemIdIsNormal(lp))
-		{
-			/* We should only see a redirect at start of chain */
-			if (ItemIdIsRedirected(lp) && at_chain_start)
-			{
-				/* Follow the redirect */
-				offnum = ItemIdGetRedirect(lp);
-				at_chain_start = false;
-				continue;
-			}
-			/* else must be end of chain */
-			break;
-		}
-
-		heapTuple->t_data = (HeapTupleHeader) PageGetItem(dp, lp);
-		heapTuple->t_len = ItemIdGetLength(lp);
-		heapTuple->t_tableOid = RelationGetRelid(relation);
-		ItemPointerSetOffsetNumber(&heapTuple->t_self, offnum);
-
-		/*
-		 * Shouldn't see a HEAP_ONLY tuple at chain start.
-		 */
-		if (at_chain_start && HeapTupleIsHeapOnly(heapTuple))
-			break;
-
-		/*
-		 * The xmin should match the previous xmax value, else chain is
-		 * broken.
-		 */
-		if (TransactionIdIsValid(prev_xmax) &&
-			!TransactionIdEquals(prev_xmax,
-								 HeapTupleHeaderGetXmin(heapTuple->t_data)))
-			break;
-
-		/*
-		 * When first_call is true (and thus, skip is initially false) we'll
-		 * return the first tuple we find.  But on later passes, heapTuple
-		 * will initially be pointing to the tuple we returned last time.
-		 * Returning it again would be incorrect (and would loop forever), so
-		 * we skip it and return the next match we find.
-		 */
-		if (!skip)
-		{
-			/*
-			 * For the benefit of logical decoding, have t_self point at the
-			 * element of the HOT chain we're currently investigating instead
-			 * of the root tuple of the HOT chain. This is important because
-			 * the *Satisfies routine for historical mvcc snapshots needs the
-			 * correct tid to decide about the visibility in some cases.
-			 */
-			ItemPointerSet(&(heapTuple->t_self), BufferGetBlockNumber(buffer), offnum);
-
-			/* If it's visible per the snapshot, we must return it */
-			valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, heapTuple, snapshot, buffer);
-			CheckForSerializableConflictOut(valid, relation, heapTuple,
-											buffer, snapshot);
-			/* reset to original, non-redirected, tid */
-			heapTuple->t_self = *tid;
-
-			if (valid)
-			{
-				ItemPointerSetOffsetNumber(tid, offnum);
-				PredicateLockTuple(relation, heapTuple, snapshot);
-				if (all_dead)
-					*all_dead = false;
-				return true;
-			}
-		}
-		skip = false;
-
-		/*
-		 * If we can't see it, maybe no one else can either.  At caller
-		 * request, check whether all chain members are dead to all
-		 * transactions.
-		 *
-		 * Note: if you change the criterion here for what is "dead", fix the
-		 * planner's get_actual_variable_range() function to match.
-		 */
-		if (all_dead && *all_dead &&
-			!HeapTupleIsSurelyDead(heapTuple, RecentGlobalXmin))
-			*all_dead = false;
-
-		/*
-		 * Check to see if HOT chain continues past this tuple; if so fetch
-		 * the next offnum and loop around.
-		 */
-		if (HeapTupleIsHotUpdated(heapTuple))
-		{
-			Assert(ItemPointerGetBlockNumber(&heapTuple->t_data->t_ctid) ==
-				   ItemPointerGetBlockNumber(tid));
-			offnum = ItemPointerGetOffsetNumber(&heapTuple->t_data->t_ctid);
-			at_chain_start = false;
-			prev_xmax = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
-		}
-		else
-			break;				/* end of chain */
-	}
-
-	return false;
-}
-
-/*
- *	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
-heap_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 = heap_hot_search_buffer(tid, relation, buffer, snapshot,
-									&heapTuple, all_dead, true);
-	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-	ReleaseBuffer(buffer);
-	return result;
-}
-
-
 /*
  * UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends
  *
@@ -4762,32 +3420,6 @@ heap_execute_freeze_tuple(HeapTupleHeader tuple, xl_heap_freeze_tuple *frz)
 	tuple->t_infomask2 = frz->t_infomask2;
 }
 
-/*
- * heap_freeze_tuple
- *		Freeze tuple in place, without WAL logging.
- *
- * Useful for callers like CLUSTER that perform their own WAL logging.
- */
-bool
-heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
-				  TransactionId cutoff_multi)
-{
-	xl_heap_freeze_tuple frz;
-	bool		do_freeze;
-	bool		tuple_totally_frozen;
-
-	do_freeze = heap_prepare_freeze_tuple(tuple, cutoff_xid, cutoff_multi,
-										  &frz, &tuple_totally_frozen);
-
-	/*
-	 * Note that because this is not a WAL-logged operation, we don't need to
-	 * fill in the offset in the freeze record.
-	 */
-
-	if (do_freeze)
-		heap_execute_freeze_tuple(tuple, &frz);
-	return do_freeze;
-}
 
 /*
  * For a given MultiXactId, return the hint bits that should be set in the
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index a0e3272..12a8f56 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -1666,6 +1666,1094 @@ HeapTupleSatisfiesHistoricMVCC(StorageTuple stup, Snapshot snapshot,
 		return true;
 }
 
+/* ----------------------------------------------------------------
+ *						 heap support routines
+ * ----------------------------------------------------------------
+ */
+
+/* ----------------
+ *		heap_parallelscan_startblock_init - find and set the scan's startblock
+ *
+ *		Determine where the parallel seq scan should start.  This function may
+ *		be called many times, once by each parallel worker.  We must be careful
+ *		only to set the startblock once.
+ * ----------------
+ */
+static void
+heap_parallelscan_startblock_init(HeapScanDesc scan)
+{
+	BlockNumber sync_startpage = InvalidBlockNumber;
+	ParallelHeapScanDesc parallel_scan;
+
+	Assert(scan->rs_parallel);
+	parallel_scan = scan->rs_parallel;
+
+retry:
+	/* Grab the spinlock. */
+	SpinLockAcquire(&parallel_scan->phs_mutex);
+
+	/*
+	 * If the scan's startblock has not yet been initialized, we must do so
+	 * now.  If this is not a synchronized scan, we just start at block 0, but
+	 * if it is a synchronized scan, we must get the starting position from
+	 * the synchronized scan machinery.  We can't hold the spinlock while
+	 * doing that, though, so release the spinlock, get the information we
+	 * need, and retry.  If nobody else has initialized the scan in the
+	 * meantime, we'll fill in the value we fetched on the second time
+	 * through.
+	 */
+	if (parallel_scan->phs_startblock == InvalidBlockNumber)
+	{
+		if (!parallel_scan->phs_syncscan)
+			parallel_scan->phs_startblock = 0;
+		else if (sync_startpage != InvalidBlockNumber)
+			parallel_scan->phs_startblock = sync_startpage;
+		else
+		{
+			SpinLockRelease(&parallel_scan->phs_mutex);
+			sync_startpage = ss_get_location(scan->rs_rd, scan->rs_nblocks);
+			goto retry;
+		}
+	}
+	SpinLockRelease(&parallel_scan->phs_mutex);
+}
+
+/* ----------------
+ *		heap_parallelscan_nextpage - get the next page to scan
+ *
+ *		Get the next page to scan.  Even if there are no pages left to scan,
+ *		another backend could have grabbed a page to scan and not yet finished
+ *		looking at it, so it doesn't follow that the scan is done when the
+ *		first backend gets an InvalidBlockNumber return.
+ * ----------------
+ */
+static BlockNumber
+heap_parallelscan_nextpage(HeapScanDesc scan)
+{
+	BlockNumber page;
+	ParallelHeapScanDesc parallel_scan;
+	uint64		nallocated;
+
+	Assert(scan->rs_parallel);
+	parallel_scan = scan->rs_parallel;
+
+	/*
+	 * phs_nallocated tracks how many pages have been allocated to workers
+	 * already.  When phs_nallocated >= rs_nblocks, all blocks have been
+	 * allocated.
+	 *
+	 * Because we use an atomic fetch-and-add to fetch the current value, the
+	 * phs_nallocated counter will exceed rs_nblocks, because workers will
+	 * still increment the value, when they try to allocate the next block but
+	 * all blocks have been allocated already. The counter must be 64 bits
+	 * wide because of that, to avoid wrapping around when rs_nblocks is close
+	 * to 2^32.
+	 *
+	 * The actual page to return is calculated by adding the counter to the
+	 * starting block number, modulo nblocks.
+	 */
+	nallocated = pg_atomic_fetch_add_u64(&parallel_scan->phs_nallocated, 1);
+	if (nallocated >= scan->rs_nblocks)
+		page = InvalidBlockNumber;	/* all blocks have been allocated */
+	else
+		page = (nallocated + parallel_scan->phs_startblock) % scan->rs_nblocks;
+
+	/*
+	 * Report scan location.  Normally, we report the current page number.
+	 * When we reach the end of the scan, though, we report the starting page,
+	 * not the ending page, just so the starting positions for later scans
+	 * 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 (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);
+	}
+
+	return page;
+}
+
+
+/* ----------------
+ *		initscan - scan code common to heap_beginscan and heap_rescan
+ * ----------------
+ */
+static void
+initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
+{
+	bool		allow_strat;
+	bool		allow_sync;
+
+	/*
+	 * Determine the number of blocks we have to scan.
+	 *
+	 * It is sufficient to do this once at scan start, since any tuples added
+	 * while the scan is in progress will be invisible to my snapshot anyway.
+	 * (That is not true when using a non-MVCC snapshot.  However, we couldn't
+	 * guarantee to return tuples added after scan start anyway, since they
+	 * might go into pages we already scanned.  To guarantee consistent
+	 * results for a non-MVCC snapshot, the caller must hold some higher-level
+	 * lock that ensures the interesting tuple(s) won't change.)
+	 */
+	if (scan->rs_parallel != NULL)
+		scan->rs_nblocks = scan->rs_parallel->phs_nblocks;
+	else
+		scan->rs_nblocks = RelationGetNumberOfBlocks(scan->rs_rd);
+
+	/*
+	 * If the table is large relative to NBuffers, use a bulk-read access
+	 * strategy and enable synchronized scanning (see syncscan.c).  Although
+	 * the thresholds for these features could be different, we make them the
+	 * same so that there are only two behaviors to tune rather than four.
+	 * (However, some callers need to be able to disable one or both of these
+	 * behaviors, independently of the size of the table; also there is a GUC
+	 * variable that can disable synchronized scanning.)
+	 *
+	 * 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)
+	{
+		allow_strat = scan->rs_allow_strat;
+		allow_sync = scan->rs_allow_sync;
+	}
+	else
+		allow_strat = allow_sync = false;
+
+	if (allow_strat)
+	{
+		/* During a rescan, keep the previous strategy object. */
+		if (scan->rs_strategy == NULL)
+			scan->rs_strategy = GetAccessStrategy(BAS_BULKREAD);
+	}
+	else
+	{
+		if (scan->rs_strategy != NULL)
+			FreeAccessStrategy(scan->rs_strategy);
+		scan->rs_strategy = NULL;
+	}
+
+	if (scan->rs_parallel != NULL)
+	{
+		/* For parallel scan, believe whatever ParallelHeapScanDesc says. */
+		scan->rs_syncscan = scan->rs_parallel->phs_syncscan;
+	}
+	else if (keep_startblock)
+	{
+		/*
+		 * When rescanning, we want to keep the previous startblock setting,
+		 * so that rewinding a cursor doesn't generate surprising results.
+		 * Reset the active syncscan setting, though.
+		 */
+		scan->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);
+	}
+	else
+	{
+		scan->rs_syncscan = false;
+		scan->rs_startblock = 0;
+	}
+
+	scan->rs_numblocks = InvalidBlockNumber;
+	scan->rs_inited = false;
+	scan->rs_ctup.t_data = NULL;
+	ItemPointerSetInvalid(&scan->rs_ctup.t_self);
+	scan->rs_cbuf = InvalidBuffer;
+	scan->rs_cblock = InvalidBlockNumber;
+
+	/* page-at-a-time fields are always invalid when not rs_inited */
+
+	/*
+	 * copy the scan key, if appropriate
+	 */
+	if (key != NULL)
+		memcpy(scan->rs_key, key, scan->rs_nkeys * sizeof(ScanKeyData));
+
+	/*
+	 * Currently, we don't have a stats counter for bitmap heap scans (but the
+	 * underlying bitmap index scans will be counted) or sample scans (we only
+	 * update stats for tuple fetches there)
+	 */
+	if (!scan->rs_bitmapscan && !scan->rs_samplescan)
+		pgstat_count_heap_scan(scan->rs_rd);
+}
+
+
+/* ----------------
+ *		heapgettup - fetch next heap tuple
+ *
+ *		Initialize the scan if not already done; then advance to the next
+ *		tuple as indicated by "dir"; return the next tuple in scan->rs_ctup,
+ *		or set scan->rs_ctup.t_data = NULL if no more tuples.
+ *
+ * dir == NoMovementScanDirection means "re-fetch the tuple indicated
+ * by scan->rs_ctup".
+ *
+ * Note: the reason nkeys/key are passed separately, even though they are
+ * kept in the scan descriptor, is that the caller may not want us to check
+ * the scankeys.
+ *
+ * Note: when we fall off the end of the scan in either direction, we
+ * reset rs_inited.  This means that a further request with the same
+ * scan direction will restart the scan, which is a bit odd, but a
+ * request with the opposite scan direction will start a fresh scan
+ * in the proper direction.  The latter is required behavior for cursors,
+ * while the former case is generally undefined behavior in Postgres
+ * so we don't care too much.
+ * ----------------
+ */
+static void
+heapgettup(HeapScanDesc scan,
+		   ScanDirection dir,
+		   int nkeys,
+		   ScanKey key)
+{
+	HeapTuple	tuple = &(scan->rs_ctup);
+	Snapshot	snapshot = scan->rs_snapshot;
+	bool		backward = ScanDirectionIsBackward(dir);
+	BlockNumber page;
+	bool		finished;
+	Page		dp;
+	int			lines;
+	OffsetNumber lineoff;
+	int			linesleft;
+	ItemId		lpp;
+
+	/*
+	 * calculate next starting lineoff, given scan direction
+	 */
+	if (ScanDirectionIsForward(dir))
+	{
+		if (!scan->rs_inited)
+		{
+			/*
+			 * return null immediately if relation is empty
+			 */
+			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			{
+				Assert(!BufferIsValid(scan->rs_cbuf));
+				tuple->t_data = NULL;
+				return;
+			}
+			if (scan->rs_parallel != NULL)
+			{
+				heap_parallelscan_startblock_init(scan);
+
+				page = heap_parallelscan_nextpage(scan);
+
+				/* Other processes might have already finished the scan. */
+				if (page == InvalidBlockNumber)
+				{
+					Assert(!BufferIsValid(scan->rs_cbuf));
+					tuple->t_data = NULL;
+					return;
+				}
+			}
+			else
+				page = scan->rs_startblock; /* first page */
+			heapgetpage(scan, page);
+			lineoff = FirstOffsetNumber;	/* first offnum */
+			scan->rs_inited = true;
+		}
+		else
+		{
+			/* continue from previously returned page/tuple */
+			page = scan->rs_cblock; /* current page */
+			lineoff =			/* next offnum */
+				OffsetNumberNext(ItemPointerGetOffsetNumber(&(tuple->t_self)));
+		}
+
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		lines = PageGetMaxOffsetNumber(dp);
+		/* page and lineoff now reference the physically next tid */
+
+		linesleft = lines - lineoff + 1;
+	}
+	else if (backward)
+	{
+		/* backward parallel scan not supported */
+		Assert(scan->rs_parallel == NULL);
+
+		if (!scan->rs_inited)
+		{
+			/*
+			 * return null immediately if relation is empty
+			 */
+			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			{
+				Assert(!BufferIsValid(scan->rs_cbuf));
+				tuple->t_data = NULL;
+				return;
+			}
+
+			/*
+			 * Disable reporting to syncscan logic in a backwards scan; it's
+			 * not very likely anyone else is doing the same thing at the same
+			 * time, and much more likely that we'll just bollix things for
+			 * forward scanners.
+			 */
+			scan->rs_syncscan = false;
+			/* start from last page of the scan */
+			if (scan->rs_startblock > 0)
+				page = scan->rs_startblock - 1;
+			else
+				page = scan->rs_nblocks - 1;
+			heapgetpage(scan, page);
+		}
+		else
+		{
+			/* continue from previously returned page/tuple */
+			page = scan->rs_cblock; /* current page */
+		}
+
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		lines = PageGetMaxOffsetNumber(dp);
+
+		if (!scan->rs_inited)
+		{
+			lineoff = lines;	/* final offnum */
+			scan->rs_inited = true;
+		}
+		else
+		{
+			lineoff =			/* previous offnum */
+				OffsetNumberPrev(ItemPointerGetOffsetNumber(&(tuple->t_self)));
+		}
+		/* page and lineoff now reference the physically previous tid */
+
+		linesleft = lineoff;
+	}
+	else
+	{
+		/*
+		 * ``no movement'' scan direction: refetch prior tuple
+		 */
+		if (!scan->rs_inited)
+		{
+			Assert(!BufferIsValid(scan->rs_cbuf));
+			tuple->t_data = NULL;
+			return;
+		}
+
+		page = ItemPointerGetBlockNumber(&(tuple->t_self));
+		if (page != scan->rs_cblock)
+			heapgetpage(scan, page);
+
+		/* Since the tuple was previously fetched, needn't lock page here */
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
+		lpp = PageGetItemId(dp, lineoff);
+		Assert(ItemIdIsNormal(lpp));
+
+		tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+		tuple->t_len = ItemIdGetLength(lpp);
+
+		return;
+	}
+
+	/*
+	 * advance the scan until we find a qualifying tuple or run out of stuff
+	 * to scan
+	 */
+	lpp = PageGetItemId(dp, lineoff);
+	for (;;)
+	{
+		while (linesleft > 0)
+		{
+			if (ItemIdIsNormal(lpp))
+			{
+				bool		valid;
+
+				tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+				tuple->t_len = ItemIdGetLength(lpp);
+				ItemPointerSet(&(tuple->t_self), page, lineoff);
+
+				/*
+				 * if current tuple qualifies, return it.
+				 */
+				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine,
+													 tuple,
+													 snapshot,
+													 scan->rs_cbuf);
+
+				CheckForSerializableConflictOut(valid, scan->rs_rd, tuple,
+												scan->rs_cbuf, snapshot);
+
+				if (valid && key != NULL)
+					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
+								nkeys, key, valid);
+
+				if (valid)
+				{
+					LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+					return;
+				}
+			}
+
+			/*
+			 * otherwise move to the next item on the page
+			 */
+			--linesleft;
+			if (backward)
+			{
+				--lpp;			/* move back in this page's ItemId array */
+				--lineoff;
+			}
+			else
+			{
+				++lpp;			/* move forward in this page's ItemId array */
+				++lineoff;
+			}
+		}
+
+		/*
+		 * 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);
+
+		/*
+		 * 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);
+			if (page == 0)
+				page = scan->rs_nblocks;
+			page--;
+		}
+		else if (scan->rs_parallel != NULL)
+		{
+			page = heap_parallelscan_nextpage(scan);
+			finished = (page == InvalidBlockNumber);
+		}
+		else
+		{
+			page++;
+			if (page >= scan->rs_nblocks)
+				page = 0;
+			finished = (page == scan->rs_startblock) ||
+				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+
+			/*
+			 * Report our new scan position for synchronization purposes. We
+			 * don't do that when moving backwards, however. That would just
+			 * mess up any other forward-moving scanners.
+			 *
+			 * Note: we do this before checking for end of scan so that the
+			 * final state of the position hint is back at the start of the
+			 * rel.  That's not strictly necessary, but otherwise when you run
+			 * the same query multiple times the starting position would shift
+			 * 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);
+		}
+
+		/*
+		 * return NULL if we've exhausted all the pages
+		 */
+		if (finished)
+		{
+			if (BufferIsValid(scan->rs_cbuf))
+				ReleaseBuffer(scan->rs_cbuf);
+			scan->rs_cbuf = InvalidBuffer;
+			scan->rs_cblock = InvalidBlockNumber;
+			tuple->t_data = NULL;
+			scan->rs_inited = false;
+			return;
+		}
+
+		heapgetpage(scan, page);
+
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		lines = PageGetMaxOffsetNumber((Page) dp);
+		linesleft = lines;
+		if (backward)
+		{
+			lineoff = lines;
+			lpp = PageGetItemId(dp, lines);
+		}
+		else
+		{
+			lineoff = FirstOffsetNumber;
+			lpp = PageGetItemId(dp, FirstOffsetNumber);
+		}
+	}
+}
+
+/* ----------------
+ *		heapgettup_pagemode - fetch next heap tuple in page-at-a-time mode
+ *
+ *		Same API as heapgettup, but used in page-at-a-time mode
+ *
+ * The internal logic is much the same as heapgettup's too, but there are some
+ * differences: we do not take the buffer content lock (that only needs to
+ * happen inside heapgetpage), and we iterate through just the tuples listed
+ * in rs_vistuples[] rather than all tuples on the page.  Notice that
+ * lineindex is 0-based, where the corresponding loop variable lineoff in
+ * heapgettup is 1-based.
+ * ----------------
+ */
+static void
+heapgettup_pagemode(HeapScanDesc scan,
+					ScanDirection dir,
+					int nkeys,
+					ScanKey key)
+{
+	HeapTuple	tuple = &(scan->rs_ctup);
+	bool		backward = ScanDirectionIsBackward(dir);
+	BlockNumber page;
+	bool		finished;
+	Page		dp;
+	int			lines;
+	int			lineindex;
+	OffsetNumber lineoff;
+	int			linesleft;
+	ItemId		lpp;
+
+	/*
+	 * calculate next starting lineindex, given scan direction
+	 */
+	if (ScanDirectionIsForward(dir))
+	{
+		if (!scan->rs_inited)
+		{
+			/*
+			 * return null immediately if relation is empty
+			 */
+			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			{
+				Assert(!BufferIsValid(scan->rs_cbuf));
+				tuple->t_data = NULL;
+				return;
+			}
+			if (scan->rs_parallel != NULL)
+			{
+				heap_parallelscan_startblock_init(scan);
+
+				page = heap_parallelscan_nextpage(scan);
+
+				/* Other processes might have already finished the scan. */
+				if (page == InvalidBlockNumber)
+				{
+					Assert(!BufferIsValid(scan->rs_cbuf));
+					tuple->t_data = NULL;
+					return;
+				}
+			}
+			else
+				page = scan->rs_startblock; /* first page */
+			heapgetpage(scan, page);
+			lineindex = 0;
+			scan->rs_inited = true;
+		}
+		else
+		{
+			/* continue from previously returned page/tuple */
+			page = scan->rs_cblock; /* current page */
+			lineindex = scan->rs_cindex + 1;
+		}
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
+		lines = scan->rs_ntuples;
+		/* page and lineindex now reference the next visible tid */
+
+		linesleft = lines - lineindex;
+	}
+	else if (backward)
+	{
+		/* backward parallel scan not supported */
+		Assert(scan->rs_parallel == NULL);
+
+		if (!scan->rs_inited)
+		{
+			/*
+			 * return null immediately if relation is empty
+			 */
+			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			{
+				Assert(!BufferIsValid(scan->rs_cbuf));
+				tuple->t_data = NULL;
+				return;
+			}
+
+			/*
+			 * Disable reporting to syncscan logic in a backwards scan; it's
+			 * not very likely anyone else is doing the same thing at the same
+			 * time, and much more likely that we'll just bollix things for
+			 * forward scanners.
+			 */
+			scan->rs_syncscan = false;
+			/* start from last page of the scan */
+			if (scan->rs_startblock > 0)
+				page = scan->rs_startblock - 1;
+			else
+				page = scan->rs_nblocks - 1;
+			heapgetpage(scan, page);
+		}
+		else
+		{
+			/* continue from previously returned page/tuple */
+			page = scan->rs_cblock; /* current page */
+		}
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
+		lines = scan->rs_ntuples;
+
+		if (!scan->rs_inited)
+		{
+			lineindex = lines - 1;
+			scan->rs_inited = true;
+		}
+		else
+		{
+			lineindex = scan->rs_cindex - 1;
+		}
+		/* page and lineindex now reference the previous visible tid */
+
+		linesleft = lineindex + 1;
+	}
+	else
+	{
+		/*
+		 * ``no movement'' scan direction: refetch prior tuple
+		 */
+		if (!scan->rs_inited)
+		{
+			Assert(!BufferIsValid(scan->rs_cbuf));
+			tuple->t_data = NULL;
+			return;
+		}
+
+		page = ItemPointerGetBlockNumber(&(tuple->t_self));
+		if (page != scan->rs_cblock)
+			heapgetpage(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);
+		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
+		lpp = PageGetItemId(dp, lineoff);
+		Assert(ItemIdIsNormal(lpp));
+
+		tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+		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]);
+
+		return;
+	}
+
+	/*
+	 * advance the scan until we find a qualifying tuple or run out of stuff
+	 * to scan
+	 */
+	for (;;)
+	{
+		while (linesleft > 0)
+		{
+			lineoff = scan->rs_vistuples[lineindex];
+			lpp = PageGetItemId(dp, lineoff);
+			Assert(ItemIdIsNormal(lpp));
+
+			tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+			tuple->t_len = ItemIdGetLength(lpp);
+			ItemPointerSet(&(tuple->t_self), page, lineoff);
+
+			/*
+			 * if current tuple qualifies, return it.
+			 */
+			if (key != NULL)
+			{
+				bool		valid;
+
+				HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
+							nkeys, key, valid);
+				if (valid)
+				{
+					scan->rs_cindex = lineindex;
+					return;
+				}
+			}
+			else
+			{
+				scan->rs_cindex = lineindex;
+				return;
+			}
+
+			/*
+			 * otherwise move to the next item on the page
+			 */
+			--linesleft;
+			if (backward)
+				--lineindex;
+			else
+				++lineindex;
+		}
+
+		/*
+		 * if we get here, it means we've exhausted the items on this page and
+		 * it's time to move to the next.
+		 */
+		if (backward)
+		{
+			finished = (page == scan->rs_startblock) ||
+				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+			if (page == 0)
+				page = scan->rs_nblocks;
+			page--;
+		}
+		else if (scan->rs_parallel != NULL)
+		{
+			page = heap_parallelscan_nextpage(scan);
+			finished = (page == InvalidBlockNumber);
+		}
+		else
+		{
+			page++;
+			if (page >= scan->rs_nblocks)
+				page = 0;
+			finished = (page == scan->rs_startblock) ||
+				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+
+			/*
+			 * Report our new scan position for synchronization purposes. We
+			 * don't do that when moving backwards, however. That would just
+			 * mess up any other forward-moving scanners.
+			 *
+			 * Note: we do this before checking for end of scan so that the
+			 * final state of the position hint is back at the start of the
+			 * rel.  That's not strictly necessary, but otherwise when you run
+			 * the same query multiple times the starting position would shift
+			 * 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);
+		}
+
+		/*
+		 * return NULL if we've exhausted all the pages
+		 */
+		if (finished)
+		{
+			if (BufferIsValid(scan->rs_cbuf))
+				ReleaseBuffer(scan->rs_cbuf);
+			scan->rs_cbuf = InvalidBuffer;
+			scan->rs_cblock = InvalidBlockNumber;
+			tuple->t_data = NULL;
+			scan->rs_inited = false;
+			return;
+		}
+
+		heapgetpage(scan, page);
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
+		lines = scan->rs_ntuples;
+		linesleft = lines;
+		if (backward)
+			lineindex = lines - 1;
+		else
+			lineindex = 0;
+	}
+}
+
+
+static HeapScanDesc
+heapam_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)
+{
+	HeapScanDesc scan;
+
+	/*
+	 * increment relation ref count while scanning relation
+	 *
+	 * This is just to make really sure the relcache entry won't go away while
+	 * the scan has a pointer to it.  Caller should be holding the rel open
+	 * anyway, so this is redundant in all normal scenarios...
+	 */
+	RelationIncrementReferenceCount(relation);
+
+	/*
+	 * allocate and initialize scan descriptor
+	 */
+	scan = (HeapScanDesc) palloc(sizeof(HeapScanDescData));
+
+	scan->rs_rd = relation;
+	scan->rs_snapshot = snapshot;
+	scan->rs_nkeys = nkeys;
+	scan->rs_bitmapscan = is_bitmapscan;
+	scan->rs_samplescan = is_samplescan;
+	scan->rs_strategy = NULL;	/* set in initscan */
+	scan->rs_allow_strat = allow_strat;
+	scan->rs_allow_sync = allow_sync;
+	scan->rs_temp_snap = temp_snap;
+	scan->rs_parallel = parallel_scan;
+
+	/*
+	 * we can use page-at-a-time mode if it's an MVCC-safe snapshot
+	 */
+	scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(snapshot);
+
+	/*
+	 * For a seqscan in a serializable transaction, acquire a predicate lock
+	 * on the entire relation. This is required not only to lock all the
+	 * matching tuples, but also to conflict with new insertions into the
+	 * table. In an indexscan, we take page locks on the index pages covering
+	 * the range specified in the scan qual, but in a heap scan there is
+	 * nothing more fine-grained to lock. A bitmap scan is a different story,
+	 * there we have already scanned the index and locked the index pages
+	 * covering the predicate. But in that case we still have to lock any
+	 * matching heap tuples.
+	 */
+	if (!is_bitmapscan)
+		PredicateLockRelation(relation, snapshot);
+
+	/* we only need to set this up once */
+	scan->rs_ctup.t_tableOid = RelationGetRelid(relation);
+
+	/*
+	 * we do this here instead of in initscan() because heap_rescan also calls
+	 * initscan() and we don't want to allocate memory again
+	 */
+	if (nkeys > 0)
+		scan->rs_key = (ScanKey) palloc(sizeof(ScanKeyData) * nkeys);
+	else
+		scan->rs_key = NULL;
+
+	initscan(scan, key, false);
+
+	return scan;
+}
+
+/* ----------------
+ *		heapam_rescan		- restart a relation scan
+ * ----------------
+ */
+static void
+heapam_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
+	 */
+	if (BufferIsValid(scan->rs_cbuf))
+		ReleaseBuffer(scan->rs_cbuf);
+
+	/*
+	 * reinitialize scan descriptor
+	 */
+	initscan(scan, key, true);
+
+	/*
+	 * 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);
+	}
+}
+
+/* ----------------
+ *		heapam_endscan	- end relation scan
+ *
+ *		See how to integrate with index scans.
+ *		Check handling if reldesc caching.
+ * ----------------
+ */
+static void
+heapam_endscan(HeapScanDesc scan)
+{
+	/* Note: no locking manipulations needed */
+
+	/*
+	 * unpin scan buffers
+	 */
+	if (BufferIsValid(scan->rs_cbuf))
+		ReleaseBuffer(scan->rs_cbuf);
+
+	/*
+	 * decrement relation reference count and free scan descriptor storage
+	 */
+	RelationDecrementReferenceCount(scan->rs_rd);
+
+	if (scan->rs_key)
+		pfree(scan->rs_key);
+
+	if (scan->rs_strategy != NULL)
+		FreeAccessStrategy(scan->rs_strategy);
+
+	if (scan->rs_temp_snap)
+		UnregisterSnapshot(scan->rs_snapshot);
+
+	pfree(scan);
+}
+
+/* ----------------
+ *		heapam_scan_update_snapshot
+ *
+ *		Update snapshot info in heap scan descriptor.
+ * ----------------
+ */
+static void
+heapam_scan_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
+{
+	Assert(IsMVCCSnapshot(snapshot));
+
+	RegisterSnapshot(snapshot);
+	scan->rs_snapshot = snapshot;
+	scan->rs_temp_snap = true;
+}
+
+/* ----------------
+ *		heapam_getnext	- retrieve next tuple in scan
+ *
+ *		Fix to work with index relations.
+ *		We don't return the buffer anymore, but you can get it from the
+ *		returned HeapTuple.
+ * ----------------
+ */
+
+#ifdef HEAPAMDEBUGALL
+#define HEAPAMDEBUG_1 \
+	elog(DEBUG2, "heapam_getnext([%s,nkeys=%d],dir=%d) called", \
+		 RelationGetRelationName(scan->rs_rd), scan->rs_nkeys, (int) direction)
+#define HEAPAMDEBUG_2 \
+	elog(DEBUG2, "heapam_getnext returning EOS")
+#define HEAPAMDEBUG_3 \
+	elog(DEBUG2, "heapam_getnext returning tuple")
+#else
+#define HEAPAMDEBUG_1
+#define HEAPAMDEBUG_2
+#define HEAPAMDEBUG_3
+#endif							/* !defined(HEAPDEBUGALL) */
+
+
+static StorageTuple
+heapam_getnext(HeapScanDesc sscan, ScanDirection direction)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	/* Note: no locking manipulations needed */
+
+	HEAPAMDEBUG_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)
+	{
+		HEAPAMDEBUG_2;			/* heap_getnext returning EOS */
+		return NULL;
+	}
+
+	/*
+	 * if we get here it means we have a new current scan tuple, so point to
+	 * the proper return buffer and return the tuple.
+	 */
+	HEAPAMDEBUG_3;				/* heap_getnext returning tuple */
+
+	pgstat_count_heap_getnext(scan->rs_rd);
+
+	return &(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
+
+static TupleTableSlot *
+heapam_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
@@ -1818,9 +2906,210 @@ heap_fetch(Relation relation,
 	return false;
 }
 
+/*
+ *	heapam_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.
+ */
+static bool
+heapam_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
+					   Snapshot snapshot, HeapTuple heapTuple,
+					   bool *all_dead, bool first_call)
+{
+	Page		dp = (Page) BufferGetPage(buffer);
+	TransactionId prev_xmax = InvalidTransactionId;
+	OffsetNumber offnum;
+	bool		at_chain_start;
+	bool		valid;
+	bool		skip;
+
+	/* If this is not the first call, previous call returned a (live!) tuple */
+	if (all_dead)
+		*all_dead = first_call;
+
+	Assert(TransactionIdIsValid(RecentGlobalXmin));
+
+	Assert(ItemPointerGetBlockNumber(tid) == BufferGetBlockNumber(buffer));
+	offnum = ItemPointerGetOffsetNumber(tid);
+	at_chain_start = first_call;
+	skip = !first_call;
+
+	heapTuple->t_self = *tid;
 
+	/* Scan through possible multiple members of HOT-chain */
+	for (;;)
+	{
+		ItemId		lp;
 
+		/* check for bogus TID */
+		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(dp))
+			break;
 
+		lp = PageGetItemId(dp, offnum);
+
+		/* check for unused, dead, or redirected items */
+		if (!ItemIdIsNormal(lp))
+		{
+			/* We should only see a redirect at start of chain */
+			if (ItemIdIsRedirected(lp) && at_chain_start)
+			{
+				/* Follow the redirect */
+				offnum = ItemIdGetRedirect(lp);
+				at_chain_start = false;
+				continue;
+			}
+			/* else must be end of chain */
+			break;
+		}
+
+		heapTuple->t_data = (HeapTupleHeader) PageGetItem(dp, lp);
+		heapTuple->t_len = ItemIdGetLength(lp);
+		heapTuple->t_tableOid = RelationGetRelid(relation);
+		ItemPointerSetOffsetNumber(&heapTuple->t_self, offnum);
+
+		/*
+		 * Shouldn't see a HEAP_ONLY tuple at chain start.
+		 */
+		if (at_chain_start && HeapTupleIsHeapOnly(heapTuple))
+			break;
+
+		/*
+		 * The xmin should match the previous xmax value, else chain is
+		 * broken.
+		 */
+		if (TransactionIdIsValid(prev_xmax) &&
+			!TransactionIdEquals(prev_xmax,
+								 HeapTupleHeaderGetXmin(heapTuple->t_data)))
+			break;
+
+		/*
+		 * When first_call is true (and thus, skip is initially false) we'll
+		 * return the first tuple we find.  But on later passes, heapTuple
+		 * will initially be pointing to the tuple we returned last time.
+		 * Returning it again would be incorrect (and would loop forever), so
+		 * we skip it and return the next match we find.
+		 */
+		if (!skip)
+		{
+			/*
+			 * For the benefit of logical decoding, have t_self point at the
+			 * element of the HOT chain we're currently investigating instead
+			 * of the root tuple of the HOT chain. This is important because
+			 * the *Satisfies routine for historical mvcc snapshots needs the
+			 * correct tid to decide about the visibility in some cases.
+			 */
+			ItemPointerSet(&(heapTuple->t_self), BufferGetBlockNumber(buffer), offnum);
+
+			/* If it's visible per the snapshot, we must return it */
+			valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, heapTuple, snapshot, buffer);
+			CheckForSerializableConflictOut(valid, relation, heapTuple,
+											buffer, snapshot);
+			/* reset to original, non-redirected, tid */
+			heapTuple->t_self = *tid;
+
+			if (valid)
+			{
+				ItemPointerSetOffsetNumber(tid, offnum);
+				PredicateLockTuple(relation, heapTuple, snapshot);
+				if (all_dead)
+					*all_dead = false;
+				return true;
+			}
+		}
+		skip = false;
+
+		/*
+		 * If we can't see it, maybe no one else can either.  At caller
+		 * request, check whether all chain members are dead to all
+		 * transactions.
+		 */
+		if (all_dead && *all_dead &&
+			!HeapTupleIsSurelyDead(heapTuple, RecentGlobalXmin))
+			*all_dead = false;
+
+		/*
+		 * Check to see if HOT chain continues past this tuple; if so fetch
+		 * the next offnum and loop around.
+		 */
+		if (HeapTupleIsHotUpdated(heapTuple))
+		{
+			Assert(ItemPointerGetBlockNumber(&heapTuple->t_data->t_ctid) ==
+				   ItemPointerGetBlockNumber(tid));
+			offnum = ItemPointerGetOffsetNumber(&heapTuple->t_data->t_ctid);
+			at_chain_start = false;
+			prev_xmax = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
+		}
+		else
+			break;				/* end of chain */
+	}
+
+	return false;
+}
+
+/*
+ * heapam_setscanlimits - restrict range of a heapscan
+ *
+ * startBlk is the page to start at
+ * numBlks is number of pages to scan (InvalidBlockNumber means "all")
+ */
+static void
+heapam_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	Assert(!scan->rs_inited);	/* else too late to change */
+	Assert(!scan->rs_syncscan); /* else rs_startblock is significant */
+
+	/* Check startBlk is valid (but allow case of zero blocks...) */
+	Assert(startBlk == 0 || startBlk < scan->rs_nblocks);
+
+	scan->rs_startblock = startBlk;
+	scan->rs_numblocks = numBlks;
+}
+
+
+/*
+ * heapam_freeze_tuple
+ *		Freeze tuple in place, without WAL logging.
+ *
+ * Useful for callers like CLUSTER that perform their own WAL logging.
+ */
+static bool
+heapam_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
+				  TransactionId cutoff_multi)
+{
+	xl_heap_freeze_tuple frz;
+	bool		do_freeze;
+	bool		tuple_totally_frozen;
+
+	do_freeze = heap_prepare_freeze_tuple(tuple, cutoff_xid, cutoff_multi,
+										  &frz, &tuple_totally_frozen);
+
+	/*
+	 * Note that because this is not a WAL-logged operation, we don't need to
+	 * fill in the offset in the freeze record.
+	 */
+
+	if (do_freeze)
+		heap_execute_freeze_tuple(tuple, &frz);
+	return do_freeze;
+}
 
 /* ----------------------------------------------------------------
  *				storage AM support routines for heapam
@@ -3793,6 +5082,16 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 {
 	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
 
+	amroutine->scan_begin = heapam_beginscan;
+	amroutine->scansetlimits = heapam_setscanlimits;
+	amroutine->scan_getnext = heapam_getnext;
+	amroutine->scan_getnextslot = heapam_getnextslot;
+	amroutine->scan_end = heapam_endscan;
+	amroutine->scan_rescan = heapam_rescan;
+	amroutine->scan_update_snapshot = heapam_scan_update_snapshot;
+	amroutine->tuple_freeze = heapam_freeze_tuple;
+	amroutine->hot_search_buffer = heapam_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/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 8fba61c..a475a85 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -409,7 +409,7 @@ rewrite_heap_tuple(RewriteState state,
 	 * While we have our hands on the tuple, we may as well freeze any
 	 * eligible xmin or xmax, so that future VACUUM effort can be saved.
 	 */
-	heap_freeze_tuple(new_tuple->t_data, state->rs_freeze_xid,
+	storage_freeze_tuple(state->rs_new_rel, new_tuple->t_data, state->rs_freeze_xid,
 					  state->rs_cutoff_multi);
 
 	/*
diff --git a/src/backend/access/heap/storageam.c b/src/backend/access/heap/storageam.c
index d1d7364..76b94dc 100644
--- a/src/backend/access/heap/storageam.c
+++ b/src/backend/access/heap/storageam.c
@@ -48,6 +48,174 @@
 #include "utils/tqual.h"
 
 
+/* ----------------
+ *		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);
+}
+
 /*
  *	storage_fetch		- retrieve tuple with given tid
  *
@@ -99,6 +267,73 @@ storage_fetch(Relation relation,
 							userbuf, keep_buf, stats_relation);
 }
 
+/*
+ *	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;
+}
+
+/*
+ * heap_freeze_tuple
+ *		Freeze tuple in place, without WAL logging.
+ *
+ * Useful for callers like CLUSTER that perform their own WAL logging.
+ */
+bool
+storage_freeze_tuple(Relation rel, HeapTupleHeader tuple, TransactionId cutoff_xid,
+				  TransactionId cutoff_multi)
+{
+	return rel->rd_stamroutine->tuple_freeze(tuple, cutoff_xid, cutoff_multi);
+}
+
 
 /*
  *	storage_lock_tuple - lock a tuple in shared or exclusive mode
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 01321a2..db5c93b 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,7 +395,7 @@ 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,
+		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 bef4255..349a127 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,7 +606,7 @@ 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,
+	got_heap_tuple = storage_hot_search_buffer(tid, scan->heapRelation,
 											scan->xs_cbuf,
 											scan->xs_snapshot,
 											&scan->xs_ctup,
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index bf963fc..0e25e9a 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,7 +326,7 @@ _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,
+				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/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 0453fd4..975cd5b 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 "bootstrap/bootstrap.h"
 #include "catalog/index.h"
@@ -573,18 +574,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,
@@ -592,7 +593,7 @@ boot_openrel(char *relname)
 				   sizeof((*app)->am_typ));
 			app++;
 		}
-		heap_endscan(scan);
+		storage_endscan(scan);
 		heap_close(rel, NoLock);
 	}
 
@@ -903,25 +904,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 ccde66a..d2a8a06 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"
@@ -797,14 +798,14 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 								ObjectIdGetDatum(namespaceId));
 
 					rel = heap_open(ProcedureRelationId, AccessShareLock);
-					scan = heap_beginscan_catalog(rel, 1, key);
+					scan = storage_beginscan_catalog(rel, 1, 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;
@@ -842,14 +843,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 0240df7..68c46f7 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"
@@ -1904,10 +1905,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
 	{
@@ -2279,7 +2280,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	}
 
     method = heapRelation->rd_stamroutine;
-	scan = heap_beginscan_strat(heapRelation,	/* relation */
+	scan = storage_beginscan_strat(heapRelation,	/* relation */
 								snapshot,	/* snapshot */
 								0,	/* number of keys */
 								NULL,	/* scan key */
@@ -2288,7 +2289,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 
 	/* 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 */
@@ -2301,7 +2302,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;
 
@@ -2613,7 +2614,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		}
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	/* we can now forget our snapshot, if set */
 	if (IsBootstrapProcessingMode() || indexInfo->ii_Concurrent)
@@ -2684,14 +2685,14 @@ IndexCheckExclusion(Relation heapRelation,
 	 * Scan all live tuples in the base relation.
 	 */
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = heap_beginscan_strat(heapRelation,	/* relation */
+	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 = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((heapTuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		CHECK_FOR_INTERRUPTS();
 
@@ -2727,7 +2728,7 @@ IndexCheckExclusion(Relation heapRelation,
 								   estate, true);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 
 	ExecDropSingleTupleTableSlot(slot);
@@ -3004,7 +3005,7 @@ 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 */
+	scan = storage_beginscan_strat(heapRelation,	/* relation */
 								snapshot,	/* snapshot */
 								0,	/* number of keys */
 								NULL,	/* scan key */
@@ -3014,7 +3015,7 @@ validate_index_heapscan(Relation heapRelation,
 	/*
 	 * 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;
@@ -3171,7 +3172,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 73eff17..b8137f2 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -986,7 +986,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);
 
 		/*
@@ -995,7 +995,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;
@@ -1011,7 +1011,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 5746dc3..1d048e6 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 323471b..517e310 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 3ef7ba8..145e3c1 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 fb53d71..a51f2e4 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 dbcc5bc..e0f6973 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 e2544e5..6727d15 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 c81ddf5..00e71e3 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2028,10 +2028,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();
 
@@ -2043,7 +2043,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 e138539..39850b1 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 b61aaac..46bc3da 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"
@@ -1948,8 +1949,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);
@@ -1989,7 +1990,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 ec6523e..6b6beb9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4540,7 +4540,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
@@ -4548,7 +4548,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)
 			{
@@ -4661,7 +4661,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 
 		MemoryContextSwitchTo(oldCxt);
-		heap_endscan(scan);
+		storage_endscan(scan);
 		UnregisterSnapshot(snapshot);
 
 		ExecDropSingleTupleTableSlot(oldslot);
@@ -5064,9 +5064,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,
@@ -5078,7 +5078,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;
@@ -8243,7 +8243,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
@@ -8251,7 +8251,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);
 
@@ -8266,7 +8266,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	}
 
 	MemoryContextSwitchTo(oldcxt);
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 	ExecDropSingleTupleTableSlot(slot);
 	FreeExecutorState(estate);
@@ -8321,9 +8321,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;
@@ -8352,7 +8352,7 @@ validateForeignKeyConstraint(char *conname,
 		RI_FKey_check_ins(&fcinfo);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 }
 
@@ -10802,8 +10802,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;
@@ -10862,7 +10862,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 8559c3b..cdfa8ff 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/trigger.c b/src/backend/commands/trigger.c
index 5d83506..a9452d4 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -15,8 +15,9 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
-#include "access/sysattr.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
+#include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 7ed16ae..c07f508 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"
@@ -2315,8 +2316,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;
 
@@ -2345,7 +2346,7 @@ AlterDomainNotNull(List *names, bool notNull)
 					}
 				}
 			}
-			heap_endscan(scan);
+			storage_endscan(scan);
 			UnregisterSnapshot(snapshot);
 
 			/* Close each rel after processing, but keep lock */
@@ -2711,8 +2712,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;
 
@@ -2755,7 +2756,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 faa1812..e24ac9f 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"
@@ -447,9 +448,9 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
 
 		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);
 
@@ -469,7 +470,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
 			MemoryContextSwitchTo(oldcontext);
 		}
 
-		heap_endscan(scan);
+		storage_endscan(scan);
 		heap_close(pgclass, AccessShareLock);
 	}
 
@@ -1121,9 +1122,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;
@@ -1160,7 +1161,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 f1636a5..6ade9df 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 89e189f..5e9daea 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 8d625b6..6f6861f 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,20 @@ RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 
 	/* Start an index 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 +313,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 b7a2cbc..0a5098d 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -681,7 +681,7 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 Datum
 ExecFetchSlotTupleDatum(TupleTableSlot *slot)
 {
-	HeapTuple	tup;
+	StorageTuple	tup;
 	TupleDesc	tupdesc;
 
 	/* Fetch slot's contents in regular-physical-tuple form */
@@ -765,7 +765,7 @@ ExecHeapifySlot(TupleTableSlot *slot)
 TupleTableSlot *
 ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
 {
-	HeapTuple	newTuple;
+	StorageTuple	newTuple;
 	MemoryContext oldContext;
 
 	/*
@@ -1085,7 +1085,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;
@@ -1093,7 +1093,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 42a4ca9..79b74ee 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)
 {
@@ -597,7 +597,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 0ae5873..d94169c 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -3097,7 +3097,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,
@@ -3212,7 +3212,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 60a6cb0..7921025 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 "executor/execdebug.h"
 #include "executor/nodeBitmapHeapscan.h"
@@ -400,7 +401,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 			HeapTupleData heapTuple;
 
 			ItemPointerSet(&tid, page, offnum);
-			if (heap_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
+			if (storage_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
 									   &heapTuple, NULL, true))
 				scan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
 		}
@@ -685,7 +686,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);
 
 	if (node->tbmiterator)
 		tbm_end_iterate(node->tbmiterator);
@@ -764,7 +765,7 @@ ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 	/*
 	 * close heap scan
 	 */
-	heap_endscan(scanDesc);
+	storage_endscan(scanDesc);
 
 	/*
 	 * close the heap relation.
@@ -865,7 +866,7 @@ 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,
+	scanstate->ss.ss_currentScanDesc = storage_beginscan_bm(currentRelation,
 														 estate->es_snapshot,
 														 0,
 														 NULL);
@@ -1023,5 +1024,5 @@ ExecBitmapHeapInitializeWorker(BitmapHeapScanState *node, shm_toc *toc)
 	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 02f6c81..abec3a9 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 022d75b..5f4a294 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -45,7 +45,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);
 
 
@@ -252,7 +252,7 @@ gather_getnext(GatherState *gatherstate)
 	TupleTableSlot *outerTupleSlot;
 	TupleTableSlot *fslot = gatherstate->funnel_slot;
 	MemoryContext tupleContext = gatherstate->ps.ps_ExprContext->ecxt_per_tuple_memory;
-	HeapTuple	tup;
+	StorageTuple	tup;
 
 	while (gatherstate->nreaders > 0 || gatherstate->need_to_scan_locally)
 	{
@@ -295,7 +295,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;
@@ -303,7 +303,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 d20d466..ef9d9f1 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -44,7 +44,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 */
@@ -53,7 +53,7 @@ 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,
+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);
@@ -399,7 +399,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);
@@ -617,7 +617,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
@@ -689,12 +689,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;
 	MemoryContext oldContext;
 	MemoryContext tupleContext;
 
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index 5351cb8..f770bc4 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,7 +186,7 @@ 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
+		 * 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.
 		 */
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 638b17b..7330ff9 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 6a118d1..04f85e5 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -29,9 +29,9 @@
 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,
-				   HeapScanDesc scan);
+static StorageTuple tablesample_getnext(SampleScanState *scanstate);
+static bool SampleTupleVisible(StorageTuple tuple, OffsetNumber tupoffset,
+		HeapScanDesc scan); //hari
 
 /* ----------------------------------------------------------------
  *						Scan Support
@@ -47,7 +47,7 @@ static bool SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset,
 static TupleTableSlot *
 SampleNext(SampleScanState *node)
 {
-	HeapTuple	tuple;
+	StorageTuple	tuple;
 	TupleTableSlot *slot;
 
 	/*
@@ -66,7 +66,8 @@ SampleNext(SampleScanState *node)
 	if (tuple)
 		ExecStoreTuple(tuple,	/* tuple to store */
 					   slot,	/* slot to store in */
-					   node->ss.ss_currentScanDesc->rs_cbuf,	/* tuple's buffer */
+					   //harinode->ss.ss_currentScanDesc->rs_cbuf,	/* tuple's buffer */
+					   InvalidBuffer,
 					   false);	/* don't pfree this pointer */
 	else
 		ExecClearTuple(slot);
@@ -244,7 +245,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,7 +350,7 @@ tablesample_init(SampleScanState *scanstate)
 	if (scanstate->ss.ss_currentScanDesc == NULL)
 	{
 		scanstate->ss.ss_currentScanDesc =
-			heap_beginscan_sampling(scanstate->ss.ss_currentRelation,
+			storage_beginscan_sampling(scanstate->ss.ss_currentRelation,
 									scanstate->ss.ps.state->es_snapshot,
 									0, NULL,
 									scanstate->use_bulkread,
@@ -358,7 +359,7 @@ tablesample_init(SampleScanState *scanstate)
 	}
 	else
 	{
-		heap_rescan_set_params(scanstate->ss.ss_currentScanDesc, NULL,
+		storage_rescan_set_params(scanstate->ss.ss_currentScanDesc, NULL,
 							   scanstate->use_bulkread,
 							   allow_sync,
 							   scanstate->use_pagemode);
@@ -376,7 +377,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 +555,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 d4ac939..839d3a6 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,7 +69,7 @@ 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,
+		scandesc = storage_beginscan(node->ss.ss_currentRelation,
 								  estate->es_snapshot,
 								  0, NULL);
 		node->ss.ss_currentScanDesc = scandesc;
@@ -78,26 +78,7 @@ SeqNext(SeqScanState *node)
 	/*
 	 * 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,12 +251,12 @@ ExecEndSeqScan(SeqScanState *node)
 void
 ExecReScanSeqScan(SeqScanState *node)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 
 	scan = node->ss.ss_currentScanDesc;
 
 	if (scan != NULL)
-		heap_rescan(scan,		/* scan desc */
+		storage_rescan(scan,		/* scan desc */
 					NULL);		/* new scan keys */
 
 	ExecScanReScan((ScanState *) node);
@@ -322,7 +303,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);
 }
 
 /* ----------------------------------------------------------------
@@ -353,5 +334,5 @@ ExecSeqScanInitializeWorker(SeqScanState *node, shm_toc *toc)
 
 	pscan = shm_toc_lookup(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 80be460..d55a752 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -2091,7 +2091,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;
@@ -2159,7 +2159,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 afe231f..418c2a6 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -627,11 +627,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)
 	{
@@ -655,7 +655,7 @@ SPI_copytuple(HeapTuple tuple)
 }
 
 HeapTupleHeader
-SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc)
+SPI_returntuple(StorageTuple tuple, TupleDesc tupdesc)
 {
 	MemoryContext oldcxt;
 	HeapTupleHeader dtup;
@@ -686,7 +686,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)
 {
@@ -854,7 +854,7 @@ char *
 SPI_gettype(TupleDesc tupdesc, int fnumber)
 {
 	Oid			typoid;
-	HeapTuple	typeTuple;
+	StorageTuple	typeTuple;
 	char	   *result;
 
 	SPI_result = 0;
@@ -962,7 +962,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);
@@ -1683,7 +1683,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);
@@ -1714,8 +1714,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 81964d7..81d0adc 100644
--- a/src/backend/executor/tqueue.c
+++ b/src/backend/executor/tqueue.c
@@ -192,12 +192,12 @@ static void TQSendRecordInfo(TQueueDestReceiver *tqueue, int32 typmod,
 				 TupleDesc tupledesc);
 static void TupleQueueHandleControlMessage(TupleQueueReader *reader,
 							   Size nbytes, char *data);
-static HeapTuple TupleQueueHandleDataMessage(TupleQueueReader *reader,
+static StorageTuple TupleQueueHandleDataMessage(TupleQueueReader *reader,
 							Size nbytes, HeapTupleHeader data);
-static HeapTuple TQRemapTuple(TupleQueueReader *reader,
+static StorageTuple TQRemapTuple(TupleQueueReader *reader,
 			 TupleDesc tupledesc,
 			 TupleRemapInfo **field_remapinfo,
-			 HeapTuple tuple);
+			 StorageTuple tuple);
 static Datum TQRemap(TupleQueueReader *reader, TupleRemapInfo *remapinfo,
 		Datum value, bool *changed);
 static Datum TQRemapArray(TupleQueueReader *reader, ArrayRemapInfo *remapinfo,
@@ -682,7 +682,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)
 {
 	shm_mq_result result;
@@ -737,7 +737,7 @@ TupleQueueReaderNext(TupleQueueReader *reader, bool nowait, bool *done)
 /*
  * Handle a data message - that is, a tuple - from the remote side.
  */
-static HeapTuple
+static StorageTuple
 TupleQueueHandleDataMessage(TupleQueueReader *reader,
 							Size nbytes,
 							HeapTupleHeader data)
@@ -766,11 +766,11 @@ TupleQueueHandleDataMessage(TupleQueueReader *reader,
 /*
  * Copy the given tuple, remapping any transient typmods contained in it.
  */
-static HeapTuple
+static StorageTuple
 TQRemapTuple(TupleQueueReader *reader,
 			 TupleDesc tupledesc,
 			 TupleRemapInfo **field_remapinfo,
-			 HeapTuple tuple)
+			 StorageTuple tuple)
 {
 	Datum	   *values;
 	bool	   *isnull;
@@ -1128,7 +1128,7 @@ TupleQueueHandleControlMessage(TupleQueueReader *reader, Size nbytes,
 static TupleRemapInfo *
 BuildTupleRemapInfo(Oid typid, MemoryContext mycontext)
 {
-	HeapTuple	tup;
+	StorageTuple	tup;
 	Form_pg_type typ;
 
 	/* This is recursive, so it could be driven to stack overflow. */
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 776b1c0..fec203d 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1882,9 +1882,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;
@@ -1911,7 +1911,7 @@ get_database_list(void)
 		MemoryContextSwitchTo(oldcxt);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	CommitTransactionCommand();
@@ -2042,13 +2042,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;
@@ -2134,7 +2134,7 @@ do_autovacuum(void)
 		}
 	}
 
-	heap_endscan(relScan);
+	storage_endscan(relScan);
 
 	/* second pass: check TOAST tables */
 	ScanKeyInit(&key,
@@ -2142,8 +2142,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;
@@ -2189,7 +2189,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 accf302..74113a7 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 6c89442..fca56d4 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 071b3a9..4924dac 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(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 eb6960d..82c042a 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -21,6 +21,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"
@@ -1208,10 +1209,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 cdd45ef..4cddd73 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -106,40 +106,16 @@ 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);
-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_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 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 Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 			int options, BulkInsertState bistate);
-extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
-					   Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
-					   bool *all_dead, bool first_call);
-extern bool heap_hot_search(ItemPointer tid, Relation relation,
-				Snapshot snapshot, bool *all_dead);
+
 extern void setLastTid(const ItemPointer tid);
 
 extern BulkInsertState GetBulkInsertState(void);
@@ -147,8 +123,6 @@ extern void FreeBulkInsertState(BulkInsertState);
 extern void ReleaseBulkInsertStatePin(BulkInsertState bistate);
 
 extern void heap_inplace_update(Relation relation, HeapTuple tuple);
-extern bool heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
-				  TransactionId cutoff_multi);
 extern bool heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
 						MultiXactId cutoff_multi, Buffer buf);
 extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
@@ -158,8 +132,6 @@ extern void simple_heap_delete(Relation relation, ItemPointer tid);
 extern void simple_heap_update(Relation relation, ItemPointer otid,
 				   HeapTuple tup);
 
-extern void heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
-
 /* 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/heapam_common.h b/src/include/access/heapam_common.h
index 799b4ed..66a96d7 100644
--- a/src/include/access/heapam_common.h
+++ b/src/include/access/heapam_common.h
@@ -107,6 +107,9 @@ static const int MultiXactStatusLock[MaxMultiXactStatus + 1] =
 /* Get the LOCKMODE for a given MultiXactStatus */
 #define LOCKMODE_from_mxstatus(status) \
 			(tupleLockExtraInfo[TUPLOCK_from_mxstatus((status))].hwlock)
+
+extern bool	synchronize_seqscans;
+
 extern HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
 					TransactionId xid, CommandId cid, int options);
 
@@ -136,6 +139,11 @@ extern void MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 in
 extern MultiXactStatus get_mxact_status_for_lock(LockTupleMode mode, bool is_update);
 
 extern void heap_inplace_update(Relation relation, HeapTuple tuple);
+
+extern bool heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
+						MultiXactId cutoff_multi, Buffer buf);
+extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
+
 extern bool heap_acquire_tuplock(Relation relation, ItemPointer tid,
 					 LockTupleMode mode, LockWaitPolicy wait_policy,
 					 bool *have_tuple_lock);
diff --git a/src/include/access/storageam.h b/src/include/access/storageam.h
index 9502c92..507af71 100644
--- a/src/include/access/storageam.h
+++ b/src/include/access/storageam.h
@@ -19,6 +19,7 @@
 
 /* A physical tuple coming from a storage AM scan */
 typedef void *StorageTuple;
+typedef void *StorageScanDesc;
 
 typedef union tuple_data
 {
@@ -36,6 +37,34 @@ typedef enum tuple_data_flags
 	CTID
 } tuple_data_flags;
 
+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,
@@ -44,6 +73,15 @@ 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 bool storage_freeze_tuple(Relation rel, HeapTupleHeader tuple, TransactionId cutoff_xid,
+				  TransactionId cutoff_multi);
+
 extern HTSU_Result storage_lock_tuple(Relation relation, ItemPointer tid, StorageTuple *stuple,
 				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 				bool follow_updates,
@@ -72,10 +110,6 @@ extern bool storage_tuple_is_heaponly(Relation relation, StorageTuple tuple);
 
 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/executor/functions.h b/src/include/executor/functions.h
index 718d894..7f9bef1 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 acade7e..d466c99 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,9 +117,9 @@ 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,
+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);
@@ -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 a717ac6..4156767 100644
--- a/src/include/executor/tqueue.h
+++ b/src/include/executor/tqueue.h
@@ -27,7 +27,7 @@ extern DestReceiver *CreateTupleQueueDestReceiver(shm_mq_handle *handle);
 extern TupleQueueReader *CreateTupleQueueReader(shm_mq_handle *handle,
 					   TupleDesc tupledesc);
 extern void DestroyTupleQueueReader(TupleQueueReader *reader);
-extern HeapTuple TupleQueueReaderNext(TupleQueueReader *reader,
+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 951af2a..ab0e091 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -229,7 +229,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.7.4.windows.1

#80Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Haribabu Kommi (#79)
Re: Pluggable storage

On Thu, Sep 14, 2017 at 8:17 AM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:

Instead of modifying the Bitmap Heap and Sample scan's to avoid referring
the internal members of the HeapScanDesc, I divided the HeapScanDesc
into two parts.

1. StorageScanDesc
2. HeapPageScanDesc

The StorageScanDesc contains the minimal information that is required
outside
the Storage routine and this must be provided by all storage routines. This
structure contains minimal information such as relation, snapshot, buffer
and
etc.

The HeapPageScanDesc contains other extra information that is required for
Bitmap Heap and Sample scans to work. This structure contains the
information
of blocks, visible offsets and etc. Currently this structure is used only
in
Bitmap Heap and Sample scan and it's supported contrib modules, except
the pgstattuple module. The pgstattuple needs some additional changes.

By adding additional storage API to return HeapPageScanDesc as it required
by the Bitmap Heap and Sample scan's and this API is called only in these
two scan's. And also these scan methods are choosen by the planner only
when the storage routine supports to returning of HeapPageScanDesc API.
Currently Implemented the planner support only for Bitmap, yet to do it
for Sample scan.

With the above approach, I removed all the references of HeapScanDesc
outside the heap. The changes of this approach is available in the
0008-Remove-HeapScanDesc-usage-outside-heap.patch

Suggestions/comments with the above approach.

For me, that's an interesting idea. Naturally, the way BitmapHeapScan and
SampleScan work even on very high-level is applicable only for some storage
AMs (i.e. heap-like storage AMs). For example, index-organized table
wouldn't ever support BitmapHeapScan, because it refers tuples by PK values
not TIDs. However, in this case, storage AM might have some alternative to
our BitmapHeapScan. So, index-organized table might have some compressed
representation of ordered PK values set and use it for bulk fetch of PK
index.

Therefore, I think it would be nice to make BitmapHeapScan an
heap-storage-AM-specific scan method while other storage AMs could provide
other storage-AM-specific scan methods. Probably it would be too much for
this patchset and should be done during one of next work cycles on storage
AM (I'm sure that such huge project as pluggable storage AMs would have
multiple iterations).

Similarly, SampleScans contain storage-AM-specific logic. For instance,
our SYSTEM sampling method fetches random blocks from heap providing high
performance way to sample heap. Coming back to the example of
index-organized table, it could provide it's own storage-AM-specific table
sampling methods including sophisticated PK tree traversal with fetching
random small ranges of PK. Given that tablesample methods are already
pluggable, making them storage-AM-specific would lead to user-visible
changes. I.e. tablesample method should be created for particular storage
AM or set of storage AMs. However, I didn't yet figure out what should API
exactly look like...

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

#81Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Alexander Korotkov (#80)
8 attachment(s)
Re: Pluggable storage

On Fri, Sep 15, 2017 at 5:10 AM, Alexander Korotkov <
a.korotkov@postgrespro.ru> wrote:

On Thu, Sep 14, 2017 at 8:17 AM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:

Instead of modifying the Bitmap Heap and Sample scan's to avoid referring
the internal members of the HeapScanDesc, I divided the HeapScanDesc
into two parts.

1. StorageScanDesc
2. HeapPageScanDesc

The StorageScanDesc contains the minimal information that is required
outside
the Storage routine and this must be provided by all storage routines.
This
structure contains minimal information such as relation, snapshot, buffer
and
etc.

The HeapPageScanDesc contains other extra information that is required for
Bitmap Heap and Sample scans to work. This structure contains the
information
of blocks, visible offsets and etc. Currently this structure is used only
in
Bitmap Heap and Sample scan and it's supported contrib modules, except
the pgstattuple module. The pgstattuple needs some additional changes.

By adding additional storage API to return HeapPageScanDesc as it required
by the Bitmap Heap and Sample scan's and this API is called only in these
two scan's. And also these scan methods are choosen by the planner only
when the storage routine supports to returning of HeapPageScanDesc API.
Currently Implemented the planner support only for Bitmap, yet to do it
for Sample scan.

With the above approach, I removed all the references of HeapScanDesc
outside the heap. The changes of this approach is available in the
0008-Remove-HeapScanDesc-usage-outside-heap.patch

Suggestions/comments with the above approach.

For me, that's an interesting idea. Naturally, the way BitmapHeapScan and
SampleScan work even on very high-level is applicable only for some storage
AMs (i.e. heap-like storage AMs). For example, index-organized table
wouldn't ever support BitmapHeapScan, because it refers tuples by PK values
not TIDs. However, in this case, storage AM might have some alternative to
our BitmapHeapScan. So, index-organized table might have some compressed
representation of ordered PK values set and use it for bulk fetch of PK
index.

Therefore, I think it would be nice to make BitmapHeapScan an
heap-storage-AM-specific scan method while other storage AMs could provide
other storage-AM-specific scan methods. Probably it would be too much for
this patchset and should be done during one of next work cycles on storage
AM (I'm sure that such huge project as pluggable storage AMs would have
multiple iterations).

Thanks for your opinion. Yes, that was my first thought of making these
two scan methods as part of the storage AMs. I feel the approach of just
exposing some additional hooks doesn't look good. This may need some
better infrastructure to provide storage AMs of their own scan methods.

Because of this reason, currently I developed the temporary approach of
separating HeapScanDesc into two structures.

Similarly, SampleScans contain storage-AM-specific logic. For instance,
our SYSTEM sampling method fetches random blocks from heap providing high
performance way to sample heap. Coming back to the example of
index-organized table, it could provide it's own storage-AM-specific table
sampling methods including sophisticated PK tree traversal with fetching
random small ranges of PK. Given that tablesample methods are already
pluggable, making them storage-AM-specific would lead to user-visible
changes. I.e. tablesample method should be created for particular storage
AM or set of storage AMs. However, I didn't yet figure out what should API
exactly look like...

Regarding SampleScans, I feel we can follow the same approach of supporting
particular sample methods with particular storage AMs similar like Bitmap
scans.
I didn't check it completely.

Rebased patches are attached.

Regards,
Hari Babu
Fujitsu Australia

Attachments:

0008-Remove-HeapScanDesc-usage-outside-heap.patchapplication/octet-stream; name=0008-Remove-HeapScanDesc-usage-outside-heap.patchDownload
From 0f46bfe62208596bc841e64d56fdb01f0d9e5184 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Thu, 14 Sep 2017 12:34:30 +1000
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           |  37 +--
 src/backend/access/heap/heapam_storage.c   | 417 ++++++++++++++++-------------
 src/backend/access/heap/storageam.c        |  51 +++-
 src/backend/access/index/genam.c           |   4 +-
 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  |  67 +++--
 src/backend/executor/nodeSamplescan.c      |  51 ++--
 src/backend/executor/nodeSeqscan.c         |   5 +-
 src/backend/optimizer/util/plancat.c       |   3 +-
 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                |   4 +-
 src/include/access/relscan.h               |  47 ++--
 src/include/access/storageam.h             |  32 +--
 src/include/access/storageamapi.h          |  23 +-
 src/include/nodes/execnodes.h              |   5 +-
 39 files changed, 488 insertions(+), 380 deletions(-)

diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index 063e07992b..55d2e09da5 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 b64fec8e4a..9b5f24a650 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -107,8 +107,9 @@ static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_mo
  * 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;
@@ -118,13 +119,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;
 	}
 
 	/*
@@ -135,20 +136,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
@@ -158,7 +159,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;
 
@@ -193,7 +194,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);
@@ -201,20 +202,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;
 }
 
 
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 12a8f56456..a678402232 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -1711,7 +1711,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;
 		}
 	}
@@ -1753,10 +1753,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.
@@ -1765,12 +1765,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;
@@ -1799,9 +1799,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
@@ -1815,8 +1815,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;
@@ -1827,20 +1827,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)
 	{
@@ -1849,25 +1849,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 */
 
@@ -1875,7 +1875,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
@@ -1883,7 +1883,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);
 }
 
 
@@ -1917,7 +1917,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;
@@ -1932,14 +1932,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;
 			}
@@ -1952,29 +1952,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 */
 
@@ -1985,14 +1985,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;
 			}
@@ -2003,30 +2003,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
 		{
@@ -2042,20 +2042,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));
@@ -2086,21 +2086,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;
 				}
 			}
@@ -2125,17 +2125,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)
@@ -2146,10 +2146,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
@@ -2163,8 +2163,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);
 		}
 
 		/*
@@ -2172,21 +2172,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)
@@ -2237,14 +2237,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;
 			}
@@ -2257,27 +2257,27 @@ 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;
 		}
 
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
+		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;
@@ -2287,14 +2287,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;
 			}
@@ -2305,32 +2305,32 @@ 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 */
 		}
 
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
+		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 */
 
@@ -2341,20 +2341,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));
@@ -2363,8 +2363,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;
 	}
@@ -2377,7 +2377,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));
 
@@ -2392,17 +2392,17 @@ 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;
+					scan->rs_pagescan.rs_cindex = lineindex;
 					return;
 				}
 			}
 			else
 			{
-				scan->rs_cindex = lineindex;
+				scan->rs_pagescan.rs_cindex = lineindex;
 				return;
 			}
 
@@ -2422,10 +2422,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)
@@ -2436,10 +2436,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
@@ -2453,8 +2453,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);
 		}
 
 		/*
@@ -2462,20 +2462,20 @@ 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);
 
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
+		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;
@@ -2485,7 +2485,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 }
 
 
-static HeapScanDesc
+static StorageScanDesc
 heapam_beginscan(Relation relation, Snapshot snapshot,
 				int nkeys, ScanKey key,
 				ParallelHeapScanDesc parallel_scan,
@@ -2512,12 +2512,12 @@ heapam_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;
@@ -2526,7 +2526,7 @@ heapam_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
@@ -2550,13 +2550,29 @@ heapam_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;
+}
+
+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;
 }
 
 /* ----------------
@@ -2564,21 +2580,23 @@ heapam_beginscan(Relation relation, Snapshot snapshot,
  * ----------------
  */
 static void
-heapam_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
+heapam_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
@@ -2609,29 +2627,31 @@ heapam_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
  * ----------------
  */
 static void
-heapam_endscan(HeapScanDesc scan)
+heapam_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);
 }
@@ -2643,12 +2663,14 @@ heapam_endscan(HeapScanDesc scan)
  * ----------------
  */
 static void
-heapam_scan_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
+heapam_scan_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;
 }
 
@@ -2677,7 +2699,7 @@ heapam_scan_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
 
 
 static StorageTuple
-heapam_getnext(HeapScanDesc sscan, ScanDirection direction)
+heapam_getnext(StorageScanDesc sscan, ScanDirection direction)
 {
 	HeapScanDesc scan = (HeapScanDesc) sscan;
 
@@ -2685,11 +2707,11 @@ heapam_getnext(HeapScanDesc sscan, ScanDirection direction)
 
 	HEAPAMDEBUG_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)
 	{
@@ -2703,7 +2725,7 @@ heapam_getnext(HeapScanDesc sscan, ScanDirection direction)
 	 */
 	HEAPAMDEBUG_3;				/* heap_getnext returning tuple */
 
-	pgstat_count_heap_getnext(scan->rs_rd);
+	pgstat_count_heap_getnext(scan->rs_scan.rs_rd);
 
 	return &(scan->rs_ctup);
 }
@@ -2723,7 +2745,7 @@ heapam_getnext(HeapScanDesc sscan, ScanDirection direction)
 #endif
 
 static TupleTableSlot *
-heapam_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
+heapam_getnextslot(StorageScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
 {
 	HeapScanDesc scan = (HeapScanDesc) sscan;
 
@@ -2731,11 +2753,11 @@ heapam_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *
 
 	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)
 	{
@@ -2750,11 +2772,32 @@ heapam_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *
 	 */
 	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);
 }
 
+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);
+}
+
 /*
  *	heap_fetch		- retrieve tuple with given tid
  *
@@ -3069,18 +3112,18 @@ heapam_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
  * numBlks is number of pages to scan (InvalidBlockNumber means "all")
  */
 static void
-heapam_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
+heapam_setscanlimits(StorageScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
 {
 	HeapScanDesc scan = (HeapScanDesc) sscan;
 
-	Assert(!scan->rs_inited);	/* else too late to change */
-	Assert(!scan->rs_syncscan); /* else rs_startblock is significant */
+	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;
 }
 
 
@@ -5086,12 +5129,26 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 	amroutine->scansetlimits = heapam_setscanlimits;
 	amroutine->scan_getnext = heapam_getnext;
 	amroutine->scan_getnextslot = heapam_getnextslot;
+	amroutine->scan_fetch_tuple_from_offset = heapam_fetch_tuple_from_offset;
 	amroutine->scan_end = heapam_endscan;
 	amroutine->scan_rescan = heapam_rescan;
 	amroutine->scan_update_snapshot = heapam_scan_update_snapshot;
 	amroutine->tuple_freeze = heapam_freeze_tuple;
 	amroutine->hot_search_buffer = heapam_hot_search_buffer;
 
+	/*
+	 * 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;
     amroutine->tuple_delete = heapam_heap_delete;
diff --git a/src/backend/access/heap/storageam.c b/src/backend/access/heap/storageam.c
index 76b94dc8c3..af59d46a86 100644
--- a/src/backend/access/heap/storageam.c
+++ b/src/backend/access/heap/storageam.c
@@ -54,7 +54,7 @@
  *		Caller must hold a suitable lock on the correct relation.
  * ----------------
  */
-HeapScanDesc
+StorageScanDesc
 storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan)
 {
 	Snapshot	snapshot;
@@ -67,6 +67,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
  *
@@ -74,7 +93,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);
 }
@@ -104,7 +123,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)
 {
@@ -112,7 +131,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);
@@ -122,7 +141,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)
@@ -132,7 +151,7 @@ storage_beginscan_strat(Relation relation, Snapshot snapshot,
 								   false, false, false);
 }
 
-HeapScanDesc
+StorageScanDesc
 storage_beginscan_bm(Relation relation, Snapshot snapshot,
 				  int nkeys, ScanKey key)
 {
@@ -140,7 +159,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)
@@ -155,7 +174,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);
@@ -171,7 +190,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,
@@ -186,7 +205,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);
 }
@@ -199,23 +218,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);
+}
+
 /*
  *	storage_fetch		- retrieve tuple with given tid
  *
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index db5c93b4c7..bcdef50366 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/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 975cd5be99..17c1e1fef4 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -564,7 +564,7 @@ boot_openrel(char *relname)
 	int			i;
 	struct typmap **app;
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tup;
 
 	if (strlen(relname) >= NAMEDATALEN)
@@ -880,7 +880,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 d2a8a06097..281e3a8e2a 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -789,7 +789,7 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 				{
 					ScanKeyData key[1];
 					Relation	rel;
-					HeapScanDesc scan;
+					StorageScanDesc scan;
 					HeapTuple	tuple;
 
 					ScanKeyInit(&key[0],
@@ -830,7 +830,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 68c46f720d..2564b14f19 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1897,7 +1897,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],
@@ -2210,7 +2210,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];
@@ -2649,7 +2649,7 @@ IndexCheckExclusion(Relation heapRelation,
 					Relation indexRelation,
 					IndexInfo *indexInfo)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
@@ -2963,7 +2963,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 415421917b..a8692c2580 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -947,7 +947,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 be20ac7b2d..87c8d9ed07 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2022,7 +2022,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 39850b1b37..09135774c8 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 46bc3da1fb..f8cbfcc33b 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1875,7 +1875,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 cd86fb9101..2a077a3a1d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4481,7 +4481,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;
@@ -5053,7 +5053,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;
 
@@ -8202,7 +8202,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	Expr	   *origexpr;
 	ExprState  *exprstate;
 	TupleDesc	tupdesc;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	ExprContext *econtext;
 	MemoryContext oldcxt;
@@ -8285,7 +8285,7 @@ validateForeignKeyConstraint(char *conname,
 							 Oid pkindOid,
 							 Oid constraintOid)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	Trigger		trig;
 	Snapshot	snapshot;
@@ -10737,7 +10737,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 cdfa8ffb3f..be3c0db9e2 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 c07f508d5b..09b5b9f422 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2310,7 +2310,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;
 
@@ -2706,7 +2706,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 e24ac9f538..20f3a6cf8c 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -443,7 +443,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
 		 * pg_class
 		 */
 		Relation	pgclass;
-		HeapScanDesc scan;
+		StorageScanDesc scan;
 		HeapTuple	tuple;
 
 		pgclass = heap_open(RelationRelationId, AccessShareLock);
@@ -1091,7 +1091,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 7921025178..208ff91d4c 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -54,14 +54,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);
 
@@ -76,7 +76,8 @@ static TupleTableSlot *
 BitmapHeapNext(BitmapHeapScanState *node)
 {
 	ExprContext *econtext;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
+	HeapPageScanDesc pagescan;
 	TIDBitmap  *tbm;
 	TBMIterator *tbmiterator = NULL;
 	TBMSharedIterator *shared_tbmiterator = NULL;
@@ -92,6 +93,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;
@@ -191,8 +193,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 
 	for (;;)
 	{
-		Page		dp;
-		ItemId		lp;
+		StorageTuple tuple;
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -219,7 +220,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;
@@ -228,7 +229,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			/*
 			 * Fetch the current heap page and identify candidate tuples.
 			 */
-			bitgetpage(scan, tbmres);
+			bitgetpage(node, tbmres);
 
 			if (tbmres->ntuples >= 0)
 				node->exact_pages++;
@@ -238,7 +239,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);
@@ -248,7 +249,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			/*
 			 * Continuing in previously obtained page; advance rs_cindex
 			 */
-			scan->rs_cindex++;
+			pagescan->rs_cindex++;
 
 #ifdef USE_PREFETCH
 
@@ -275,7 +276,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;
@@ -293,23 +294,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);
@@ -350,8 +342,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;
@@ -360,7 +354,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,
@@ -403,7 +397,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
@@ -419,23 +413,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);
 		}
 	}
@@ -443,7 +434,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 
 	Assert(ntup <= MaxHeapTuplesPerPage);
-	scan->rs_ntuples = ntup;
+	pagescan->rs_ntuples = ntup;
 }
 
 /*
@@ -569,7 +560,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;
@@ -724,7 +715,7 @@ void
 ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 {
 	Relation	relation;
-	HeapScanDesc scanDesc;
+	StorageScanDesc scanDesc;
 
 	/*
 	 * extract information from the node
@@ -871,6 +862,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 04f85e5f2d..589505a509 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -31,7 +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
@@ -66,8 +66,7 @@ SampleNext(SampleScanState *node)
 	if (tuple)
 		ExecStoreTuple(tuple,	/* tuple to store */
 					   slot,	/* slot to store in */
-					   //harinode->ss.ss_currentScanDesc->rs_cbuf,	/* tuple's buffer */
-					   InvalidBuffer,
+					   node->ss.ss_currentScanDesc->rs_cbuf,	/* tuple's buffer */
 					   false);	/* don't pfree this pointer */
 	else
 		ExecClearTuple(slot);
@@ -356,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
 	{
@@ -381,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;
@@ -395,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)
@@ -406,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;
 	}
@@ -455,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)
@@ -493,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;
 
 			/*
@@ -513,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);
 		}
 
 		/*
@@ -528,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 */
@@ -548,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,
@@ -569,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 839d3a65ec..ec6702484f 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -316,9 +316,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 a1ebd4acc8..4503593ffc 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -248,7 +248,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 fec203dee3..9f505a5085 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1864,7 +1864,7 @@ get_database_list(void)
 {
 	List	   *dblist = NIL;
 	Relation	rel;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tup;
 	MemoryContext resultcxt;
 
@@ -1930,7 +1930,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 74113a75b8..86db35d790 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 61981a0939..13d84a75a8 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 4924daca76..7122987ad7 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -420,7 +420,7 @@ DefineQueryRewrite(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 4cddd73355..42c26861de 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -97,6 +97,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;
 
@@ -106,7 +108,7 @@ typedef struct ParallelHeapScanDescData *ParallelHeapScanDesc;
  */
 #define HeapScanIsValid(scan) PointerIsValid(scan)
 
-extern void heapgetpage(HeapScanDesc scan, BlockNumber page);
+extern void heapgetpage(StorageScanDesc sscan, BlockNumber page);
 
 extern Size heap_parallelscan_estimate(Snapshot snapshot);
 extern void heap_parallelscan_initialize(ParallelHeapScanDesc target,
diff --git a/src/include/access/relscan.h b/src/include/access/relscan.h
index 147f862a2b..8e00c842e9 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/storageam.h b/src/include/access/storageam.h
index 507af714a9..f335c79ad1 100644
--- a/src/include/access/storageam.h
+++ b/src/include/access/storageam.h
@@ -19,7 +19,7 @@
 
 /* A physical tuple coming from a storage AM scan */
 typedef void *StorageTuple;
-typedef void *StorageScanDesc;
+typedef struct StorageScanDescData *StorageScanDesc;
 
 typedef union tuple_data
 {
@@ -37,29 +37,31 @@ typedef enum tuple_data_flags
 	CTID
 } tuple_data_flags;
 
-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,
+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 HeapScanDesc storage_beginscan_catalog(Relation relation, int nkeys, ScanKey key);
-extern HeapScanDesc storage_beginscan_strat(Relation relation, Snapshot snapshot,
+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 HeapScanDesc storage_beginscan_bm(Relation relation, Snapshot snapshot,
+extern StorageScanDesc storage_beginscan_bm(Relation relation, Snapshot snapshot,
 				  int nkeys, ScanKey key);
-extern HeapScanDesc storage_beginscan_sampling(Relation relation, Snapshot snapshot,
+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(HeapScanDesc scan);
-extern void storage_rescan(HeapScanDesc scan, ScanKey key);
-extern void storage_rescan_set_params(HeapScanDesc scan, ScanKey key,
+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 c2e6dc2aef..d849b86546 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -20,7 +20,7 @@
 #include "utils/relcache.h"
 #include "utils/snapshot.h"
 
-typedef HeapScanDesc (*scan_begin_hook) (Relation relation,
+typedef StorageScanDesc (*scan_begin_hook) (Relation relation,
 										Snapshot snapshot,
 										int nkeys, ScanKey key,
 										ParallelHeapScanDesc parallel_scan,
@@ -30,22 +30,26 @@ typedef HeapScanDesc (*scan_begin_hook) (Relation relation,
 										bool is_bitmapscan,
 										bool is_samplescan,
 										bool temp_snap);
-typedef void (*scan_setlimits_hook) (HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
+typedef ParallelHeapScanDesc (*scan_get_parallelheapscandesc_hook) (StorageScanDesc scan);
+typedef HeapPageScanDesc (*scan_get_heappagescandesc_hook) (StorageScanDesc scan);
+typedef void (*scan_setlimits_hook) (StorageScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
 
 /* must return a TupleTableSlot? */
-typedef StorageTuple (*scan_getnext_hook) (HeapScanDesc scan,
+typedef StorageTuple (*scan_getnext_hook) (StorageScanDesc scan,
 											ScanDirection direction);
 
-typedef TupleTableSlot* (*scan_getnext_slot_hook) (HeapScanDesc scan,
+typedef TupleTableSlot* (*scan_getnext_slot_hook) (StorageScanDesc scan,
 										ScanDirection direction, TupleTableSlot *slot);
+typedef StorageTuple (*scan_fetch_tuple_from_offset_hook) (StorageScanDesc scan,
+							BlockNumber blkno, OffsetNumber offset);
 
-typedef void (*scan_end_hook) (HeapScanDesc scan);
+typedef void (*scan_end_hook) (StorageScanDesc scan);
 
 
-typedef void (*scan_getpage_hook) (HeapScanDesc scan, BlockNumber page);
-typedef void (*scan_rescan_hook) (HeapScanDesc scan, ScanKey key, bool set_params,
+typedef void (*scan_getpage_hook) (StorageScanDesc scan, BlockNumber page);
+typedef void (*scan_rescan_hook) (StorageScanDesc scan, ScanKey key, bool set_params,
 					bool allow_strat, bool allow_sync, bool allow_pagemode);
-typedef void (*scan_update_snapshot_hook) (HeapScanDesc scan, Snapshot snapshot);
+typedef void (*scan_update_snapshot_hook) (StorageScanDesc scan, Snapshot snapshot);
 
 typedef bool (*hot_search_buffer_hook) (ItemPointer tid, Relation relation,
 					   Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
@@ -161,9 +165,12 @@ typedef struct StorageAmRoutine
 
 	/* Operations on relation scans */
 	scan_begin_hook scan_begin;
+	scan_get_parallelheapscandesc_hook scan_get_parallelheapscandesc;
+	scan_get_heappagescandesc_hook scan_get_heappagescandesc;
 	scan_setlimits_hook scansetlimits;
 	scan_getnext_hook scan_getnext;
 	scan_getnext_slot_hook scan_getnextslot;
+	scan_fetch_tuple_from_offset_hook scan_fetch_tuple_from_offset;
 	scan_end_hook scan_end;
 	scan_getpage_hook scan_getpage;
 	scan_rescan_hook scan_rescan;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index c19698089b..a0fb626f36 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "access/storageam.h"
 #include "access/tupconvert.h"
 #include "executor/instrument.h"
 #include "lib/pairingheap.h"
@@ -1099,7 +1100,7 @@ typedef struct ScanState
 {
 	PlanState	ps;				/* its first field is NodeTag */
 	Relation	ss_currentRelation;
-	HeapScanDesc ss_currentScanDesc;
+	StorageScanDesc ss_currentScanDesc;
 	TupleTableSlot *ss_ScanTupleSlot;
 } ScanState;
 
@@ -1120,6 +1121,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 */
@@ -1344,6 +1346,7 @@ typedef struct ParallelBitmapHeapState
 typedef struct BitmapHeapScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
+	HeapPageScanDesc pagescan;
 	ExprState  *bitmapqualorig;
 	TIDBitmap  *tbm;
 	TBMIterator *tbmiterator;
-- 
2.14.1.windows.1

0001-Change-Create-Access-method-to-include-storage-handl.patchapplication/octet-stream; name=0001-Change-Create-Access-method-to-include-storage-handl.patchDownload
From d31f37bb0b2ece2884ee71d91c8b51802f9197f6 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
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              |  4 ++++
 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, 63 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 c303818c9b..d4ac340a54 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
@@ -5172,16 +5174,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..2c3e33c104 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
@@ -83,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 f73c6c6201..6b675b0454 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3879,6 +3879,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 ffdb452b02..ea352fa247 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 fcf8bd7565..3bc960741b 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1711,11 +1711,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 2945966c0e..f1f58a3efc 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1153,15 +1153,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.14.1.windows.1

0002-Storage-AM-API-hooks-and-related-functions.patchapplication/octet-stream; name=0002-Storage-AM-API-hooks-and-related-functions.patchDownload
From e2f36e838c7aea7112999766383fb6196b1b7b72 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Wed, 30 Aug 2017 12:41:15 +1000
Subject: [PATCH 2/8] Storage AM API hooks and related functions

---
 src/backend/access/heap/Makefile         |   3 +-
 src/backend/access/heap/heapam_storage.c |  58 ++++++++
 src/backend/access/heap/storageamapi.c   | 103 ++++++++++++++
 src/include/access/htup.h                |  21 +++
 src/include/access/storageamapi.h        | 237 +++++++++++++++++++++++++++++++
 src/include/catalog/pg_proc.h            |   5 +
 src/include/nodes/nodes.h                |   1 +
 src/include/utils/tqual.h                |   9 --
 8 files changed, 427 insertions(+), 10 deletions(-)
 create mode 100644 src/backend/access/heap/heapam_storage.c
 create mode 100644 src/backend/access/heap/storageamapi.c
 create mode 100644 src/include/access/storageamapi.h

diff --git a/src/backend/access/heap/Makefile b/src/backend/access/heap/Makefile
index b83d496bcd..02a3909e7c 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 storageamapi.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..88827e7957
--- /dev/null
+++ b/src/backend/access/heap/heapam_storage.c
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/heapam.h"
+#include "access/heapam_xlog.h"
+#include "access/hio.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/relscan.h"
+#include "access/storageamapi.h"
+#include "access/subtrans.h"
+#include "access/tuptoaster.h"
+#include "access/valid.h"
+#include "access/visibilitymap.h"
+#include "access/xloginsert.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/bufmgr.h"
+#include "storage/lmgr.h"
+#include "storage/predicate.h"
+#include "storage/procarray.h"
+#include "storage/smgr.h"
+#include "storage/spin.h"
+#include "utils/builtins.h"
+#include "utils/inval.h"
+#include "utils/rel.h"
+#include "utils/tqual.h"
+
+
+Datum
+heapam_storage_handler(PG_FUNCTION_ARGS)
+{
+	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
+
+
+	PG_RETURN_POINTER(amroutine);
+}
diff --git a/src/backend/access/heap/storageamapi.c b/src/backend/access/heap/storageamapi.c
new file mode 100644
index 0000000000..def2029287
--- /dev/null
+++ b/src/backend/access/heap/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/htup.h b/src/include/access/htup.h
index 61b3e68639..6459435c78 100644
--- a/src/include/access/htup.h
+++ b/src/include/access/htup.h
@@ -26,6 +26,27 @@ typedef struct MinimalTupleData MinimalTupleData;
 
 typedef MinimalTupleData *MinimalTuple;
 
+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 */
+
+	END_OF_VISIBILITY
+} tuple_visibility_type;
+
+/* 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;
 
 /*
  * HeapTupleData is an in-memory data structure that points to a tuple.
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
new file mode 100644
index 0000000000..95fe02888f
--- /dev/null
+++ b/src/include/access/storageamapi.h
@@ -0,0 +1,237 @@
+/*---------------------------------------------------------------------
+ *
+ * storageamapi.h
+ *		API for Postgres storage access methods
+ *
+ * Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * src/include/access/storageamapi.h
+ *---------------------------------------------------------------------
+ */
+#ifndef STORAGEAMAPI_H
+#define STORAGEAMAPI_H
+
+#include "access/htup.h"
+#include "access/heapam.h"
+#include "access/sdir.h"
+#include "access/skey.h"
+#include "executor/tuptable.h"
+#include "utils/relcache.h"
+#include "utils/snapshot.h"
+
+/* A physical tuple coming from a storage AM scan */
+typedef void *StorageTuple;
+
+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;
+
+
+typedef HeapScanDesc (*scan_begin_hook) (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 (*scan_setlimits_hook) (HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks);
+
+/* must return a TupleTableSlot? */
+typedef StorageTuple (*scan_getnext_hook) (HeapScanDesc scan,
+											ScanDirection direction);
+
+typedef TupleTableSlot* (*scan_getnext_slot_hook) (HeapScanDesc scan,
+										ScanDirection direction, TupleTableSlot *slot);
+
+typedef void (*scan_end_hook) (HeapScanDesc scan);
+
+
+typedef void (*scan_getpage_hook) (HeapScanDesc scan, BlockNumber page);
+typedef void (*scan_rescan_hook) (HeapScanDesc scan, ScanKey key, bool set_params,
+					bool allow_strat, bool allow_sync, bool allow_pagemode);
+typedef void (*scan_update_snapshot_hook) (HeapScanDesc scan, Snapshot snapshot);
+
+typedef bool (*hot_search_buffer_hook) (ItemPointer tid, Relation relation,
+					   Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
+					   bool *all_dead, bool first_call);
+
+typedef Oid (*tuple_insert_hook) (Relation relation,
+									  TupleTableSlot *tupslot,
+									  CommandId cid,
+									  int options,
+									  BulkInsertState bistate);
+
+typedef HTSU_Result (*tuple_delete_hook) (Relation relation,
+											  ItemPointer tid,
+											  CommandId cid,
+											  Snapshot crosscheck,
+											  bool wait,
+											  HeapUpdateFailureData *hufd);
+
+typedef HTSU_Result (*tuple_update_hook) (Relation relation,
+											  ItemPointer otid,
+											  TupleTableSlot *slot,
+											  CommandId cid,
+											  Snapshot crosscheck,
+											  bool wait,
+											  HeapUpdateFailureData *hufd,
+											  LockTupleMode *lockmode);
+
+typedef bool (*tuple_fetch_hook) (Relation relation,
+									ItemPointer tid,
+									Snapshot snapshot,
+									StorageTuple *tuple,
+									Buffer *userbuf,
+									bool keep_buf,
+									Relation stats_relation);
+
+typedef HTSU_Result (*tuple_lock_hook) (Relation relation,
+											ItemPointer tid,
+											StorageTuple *tuple,
+											CommandId cid,
+											LockTupleMode mode,
+											LockWaitPolicy wait_policy,
+											bool follow_update,
+											Buffer *buffer,
+											HeapUpdateFailureData *hufd);
+
+typedef void (*multi_insert_hook) (Relation relation, HeapTuple *tuples, int ntuples,
+				  CommandId cid, int options, BulkInsertState bistate);
+
+typedef bool (*tuple_freeze_hook) (HeapTupleHeader tuple, TransactionId cutoff_xid,
+				  TransactionId cutoff_multi);
+
+typedef void (*tuple_get_latest_tid_hook) (Relation relation,
+											Snapshot snapshot,
+											ItemPointer tid);
+
+typedef tuple_data (*get_tuple_data_hook) (StorageTuple tuple, tuple_data_flags flags);
+
+typedef StorageTuple (*tuple_from_datum_hook) (Datum data, Oid tableoid);
+
+typedef bool (*tuple_is_heaponly_hook) (StorageTuple tuple);
+
+typedef void (*slot_store_tuple_hook) (TupleTableSlot *slot,
+										   StorageTuple tuple,
+										   bool shouldFree,
+										   bool minumumtuple);
+typedef void (*slot_clear_tuple_hook) (TupleTableSlot *slot);
+typedef Datum (*slot_getattr_hook) (TupleTableSlot *slot,
+										int attnum, bool *isnull);
+typedef void (*slot_virtualize_tuple_hook) (TupleTableSlot *slot, int16 upto);
+
+typedef HeapTuple (*slot_tuple_hook) (TupleTableSlot *slot, bool palloc_copy);
+typedef MinimalTuple (*slot_min_tuple_hook) (TupleTableSlot *slot, bool palloc_copy);
+
+typedef void (*slot_update_tableoid_hook) (TupleTableSlot *slot, Oid tableoid);
+
+typedef void (*speculative_finish_hook) (Relation rel,
+											 TupleTableSlot *slot);
+typedef void (*speculative_abort_hook) (Relation rel,
+											TupleTableSlot *slot);
+
+typedef void (*relation_sync_hook) (Relation relation);
+
+typedef bool (*snapshot_satisfies_hook) (StorageTuple htup, Snapshot snapshot, Buffer buffer);
+typedef HTSU_Result (*snapshot_satisfies_update_hook) (StorageTuple htup, CommandId curcid, Buffer buffer);
+typedef HTSV_Result (*snapshot_satisfies_vacuum_hook) (StorageTuple htup, TransactionId OldestXmin, Buffer buffer);
+
+typedef struct StorageSlotAmRoutine
+{
+	/* Operations on TupleTableSlot */
+	slot_store_tuple_hook	slot_store_tuple;
+	slot_virtualize_tuple_hook	slot_virtualize_tuple;
+	slot_clear_tuple_hook	slot_clear_tuple;
+	slot_getattr_hook	slot_getattr;
+	slot_tuple_hook     slot_tuple;
+	slot_min_tuple_hook      slot_min_tuple;
+	slot_update_tableoid_hook slot_update_tableoid;
+} StorageSlotAmRoutine;
+
+typedef StorageSlotAmRoutine* (*slot_storageam_hook) (void);
+
+/*
+ * 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;
+
+	/* Operations on relation scans */
+	scan_begin_hook scan_begin;
+	scan_setlimits_hook scansetlimits;
+	scan_getnext_hook scan_getnext;
+	scan_getnext_slot_hook scan_getnextslot;
+	scan_end_hook scan_end;
+	scan_getpage_hook scan_getpage;
+	scan_rescan_hook scan_rescan;
+	scan_update_snapshot_hook scan_update_snapshot;
+	hot_search_buffer_hook hot_search_buffer; /* heap_hot_search_buffer */
+
+	// heap_sync_function		heap_sync;		/* heap_sync */
+	/* not implemented */
+	//	parallelscan_estimate_function	parallelscan_estimate;	/* heap_parallelscan_estimate */
+	//	parallelscan_initialize_function parallelscan_initialize;	/* heap_parallelscan_initialize */
+	//	parallelscan_begin_function	parallelscan_begin;	/* heap_beginscan_parallel */
+
+	/* Operations on physical tuples */
+	tuple_insert_hook		tuple_insert;	/* heap_insert */
+	tuple_update_hook		tuple_update;	/* heap_update */
+	tuple_delete_hook		tuple_delete;	/* heap_delete */
+	tuple_fetch_hook		tuple_fetch;	/* heap_fetch */
+	tuple_lock_hook			tuple_lock;		/* heap_lock_tuple */
+	multi_insert_hook		multi_insert;	/* heap_multi_insert */
+	tuple_freeze_hook       tuple_freeze;	/* heap_freeze_tuple */
+	tuple_get_latest_tid_hook tuple_get_latest_tid; /* heap_get_latest_tid */
+
+	get_tuple_data_hook		get_tuple_data;
+	tuple_is_heaponly_hook	tuple_is_heaponly;
+	tuple_from_datum_hook	tuple_from_datum;
+
+
+	slot_storageam_hook	slot_storageam;
+
+	/*
+	 * 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:
+	 */
+	speculative_finish_hook	speculative_finish;
+	speculative_abort_hook	speculative_abort;
+
+
+	relation_sync_hook	relation_sync;	/* heap_sync */
+
+	snapshot_satisfies_hook snapshot_satisfies[END_OF_VISIBILITY];
+	snapshot_satisfies_update_hook snapshot_satisfiesUpdate; /* HeapTupleSatisfiesUpdate */
+	snapshot_satisfies_vacuum_hook snapshot_satisfiesVacuum; /* HeapTupleSatisfiesVacuum */
+} 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_proc.h b/src/include/catalog/pg_proc.h
index 6b675b0454..1dfb711be2 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 27bd4f3363..108df9b757 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -496,6 +496,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;
diff --git a/src/include/utils/tqual.h b/src/include/utils/tqual.h
index 9a3b56e5f0..de75e01706 100644
--- a/src/include/utils/tqual.h
+++ b/src/include/utils/tqual.h
@@ -45,15 +45,6 @@ extern PGDLLIMPORT SnapshotData CatalogSnapshotData;
 #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,
-- 
2.14.1.windows.1

0003-Adding-storageam-hanlder-to-relation-structure.patchapplication/octet-stream; name=0003-Adding-storageam-hanlder-to-relation-structure.patchDownload
From 6f73928b1d1f0c1ae96a8455baeb89e947eac228 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
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 | 117 ++++++++++++++++++++++++++++++++++++-
 src/include/utils/rel.h            |  12 ++++
 src/include/utils/relcache.h       |   2 +
 3 files changed, 128 insertions(+), 3 deletions(-)

diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index b8e37809b0..40cbc677c7 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -34,6 +34,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"
@@ -1368,10 +1369,26 @@ 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);
@@ -1868,6 +1885,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
@@ -2026,6 +2108,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
 	 */
@@ -2354,6 +2441,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)
@@ -3368,6 +3457,13 @@ 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.
 	 *
@@ -3889,6 +3985,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);
 
@@ -5607,6 +5715,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 4bc61e5380..82d8a534b1 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)
 	 *
@@ -427,6 +433,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 3c53cefe4b..03d996f5a1 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -71,6 +71,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.14.1.windows.1

0004-Adding-tuple-visibility-function-to-storage-AM.patchapplication/octet-stream; name=0004-Adding-tuple-visibility-function-to-storage-AM.patchDownload
From 289b45e52c1bb2e18baae4c63d43b2cad9280f40 Mon Sep 17 00:00:00 2001
From: Hari Babu Kommi <kommi.haribabu@gmail.com>
Date: Fri, 8 Sep 2017 15:38:28 +1000
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 heapam storage is moved into heapam_common.c
and heapam_common.h files.
---
 contrib/pg_visibility/pg_visibility.c       |   10 +-
 contrib/pgrowlocks/pgrowlocks.c             |    3 +-
 contrib/pgstattuple/pgstatapprox.c          |    7 +-
 contrib/pgstattuple/pgstattuple.c           |    3 +-
 src/backend/access/heap/Makefile            |    3 +-
 src/backend/access/heap/heapam.c            |   23 +-
 src/backend/access/heap/heapam_common.c     |  162 +++
 src/backend/access/heap/heapam_storage.c    | 1622 ++++++++++++++++++++++++
 src/backend/access/heap/pruneheap.c         |    4 +-
 src/backend/access/heap/rewriteheap.c       |    1 +
 src/backend/access/index/genam.c            |    4 +-
 src/backend/catalog/index.c                 |    4 +-
 src/backend/commands/analyze.c              |    2 +-
 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/backend/utils/time/tqual.c              | 1809 ---------------------------
 src/include/access/heapam_common.h          |   96 ++
 src/include/access/htup.h                   |    3 +-
 src/include/storage/bufmgr.h                |    6 +-
 src/include/utils/snapshot.h                |    2 +-
 src/include/utils/tqual.h                   |   66 +-
 29 files changed, 1964 insertions(+), 1907 deletions(-)
 create mode 100644 src/backend/access/heap/heapam_common.c
 delete mode 100644 src/backend/utils/time/tqual.c
 create mode 100644 src/include/access/heapam_common.h

diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c
index 2cc9575d9f..10d47cd46f 100644
--- a/contrib/pg_visibility/pg_visibility.c
+++ b/contrib/pg_visibility/pg_visibility.c
@@ -51,7 +51,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 +656,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 +681,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 +739,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..5f076efe7c 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,7 +150,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
 		/* must hold a buffer lock to call HeapTupleSatisfiesUpdate */
 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 
-		htsu = HeapTupleSatisfiesUpdate(tuple,
+		htsu = rel->rd_stamroutine->snapshot_satisfiesUpdate(tuple,
 										GetCurrentCommandId(false),
 										scan->rs_cbuf);
 		xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
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 02a3909e7c..e6bc18e5ea 100644
--- a/src/backend/access/heap/Makefile
+++ b/src/backend/access/heap/Makefile
@@ -12,7 +12,8 @@ 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 storageamapi.o \
+OBJS = heapam.o heapam_common.o heapam_storage.o hio.o \
+	pruneheap.o rewriteheap.o storageamapi.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 d20f0381f3..c21d6f8559 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -40,11 +40,13 @@
 
 #include "access/bufmask.h"
 #include "access/heapam.h"
+#include "access/heapam_common.h"
 #include "access/heapam_xlog.h"
 #include "access/hio.h"
 #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 +440,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 +655,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);
 
@@ -1950,7 +1953,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 +2100,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 +2274,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;
@@ -3095,7 +3098,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)
 	{
@@ -3206,7 +3209,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;
 	}
 
@@ -3666,7 +3669,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);
@@ -3847,7 +3850,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;
 	}
 
@@ -4598,7 +4601,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_common.c b/src/backend/access/heap/heapam_common.c
new file mode 100644
index 0000000000..502f6dbccb
--- /dev/null
+++ b/src/backend/access/heap/heapam_common.c
@@ -0,0 +1,162 @@
+/*-------------------------------------------------------------------------
+ *
+ * heapam_common.c
+ *	  heapam 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/heap/heapam_common.c
+ *
+ *
+ * NOTES
+ *	  This file contains the storage_ routines which implement
+ *	  the POSTGRES storage access method used for all POSTGRES
+ *	  relations.
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/heapam_common.h"
+#include "access/htup_details.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);
+}
+
+
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 88827e7957..1bd4bfaa33 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -21,6 +21,7 @@
 #include "postgres.h"
 
 #include "access/heapam.h"
+#include "access/heapam_common.h"
 #include "access/heapam_xlog.h"
 #include "access/hio.h"
 #include "access/htup_details.h"
@@ -47,6 +48,1617 @@
 #include "utils/rel.h"
 #include "utils/tqual.h"
 
+/* local functions */
+static bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
+
+/*-------------------------------------------------------------------------
+ *
+ * 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
+ * has now committed or aborted (and it is safe to set the hint bits).
+ * If the hint bits are changed, MarkBufferDirtyHint is called on
+ * the passed-in buffer.  The caller must hold not only a pin, but at least
+ * shared buffer content lock on the buffer containing the tuple.
+ *
+ * NOTE: When using a non-MVCC snapshot, we must check
+ * TransactionIdIsInProgress (which looks in the PGXACT array)
+ * before TransactionIdDidCommit/TransactionIdDidAbort (which look in
+ * pg_xact).  Otherwise we have a race condition: we might decide that a
+ * just-committed transaction crashed, because none of the tests succeed.
+ * xact.c is careful to record commit/abort in pg_xact before it unsets
+ * MyPgXact->xid in the PGXACT array.  That fixes that problem, but it
+ * also means there is a window where TransactionIdIsInProgress and
+ * TransactionIdDidCommit will both return true.  If we check only
+ * TransactionIdDidCommit, we could consider a tuple committed when a
+ * later GetSnapshotData call will still think the originating transaction
+ * is in progress, which leads to application-level inconsistency.  The
+ * upshot is that we gotta check TransactionIdIsInProgress first in all
+ * code paths, except for a few cases where we are looking at
+ * subtransactions of our own main transaction and so there can't be any
+ * race condition.
+ *
+ * When using an MVCC snapshot, we rely on XidInMVCCSnapshot rather than
+ * TransactionIdIsInProgress, but the logic is otherwise the same: do not
+ * check pg_xact until after deciding that the xact is no longer in progress.
+ *
+ *
+ * Summary of visibility functions:
+ *
+ *	 HeapTupleSatisfiesMVCC()
+ *		  visible to supplied snapshot, excludes current command
+ *	 HeapTupleSatisfiesUpdate()
+ *		  visible to instant snapshot, with user-supplied command
+ *		  counter and more complex result
+ *	 HeapTupleSatisfiesSelf()
+ *		  visible to instant snapshot and current command
+ *	 HeapTupleSatisfiesDirty()
+ *		  like HeapTupleSatisfiesSelf(), but includes open transactions
+ *	 HeapTupleSatisfiesVacuum()
+ *		  visible to any running transaction, used by VACUUM
+ *   HeapTupleSatisfiesNonVacuumable()
+ *        Snapshot-style API for HeapTupleSatisfiesVacuum
+ *	 HeapTupleSatisfiesToast()
+ *		  visible unless part of interrupted vacuum, used for TOAST
+ *	 HeapTupleSatisfiesAny()
+ *		  all tuples are visible
+ *
+ * -------------------------------------------------------------------------
+ */
+
+/*
+ * HeapTupleSatisfiesSelf
+ *		True iff heap tuple is valid "for itself".
+ *
+ *	Here, we consider the effects of:
+ *		all committed transactions (as of the current instant)
+ *		previous commands of this transaction
+ *		changes made by the current command
+ *
+ * Note:
+ *		Assumes heap tuple is valid.
+ *
+ * The satisfaction of "itself" requires the following:
+ *
+ * ((Xmin == my-transaction &&				the row was updated by the current transaction, and
+ *		(Xmax is null						it was not deleted
+ *		 [|| Xmax != my-transaction)])			[or it was deleted by another transaction]
+ * ||
+ *
+ * (Xmin is committed &&					the row was modified by a committed transaction, and
+ *		(Xmax is null ||					the row has not been deleted, or
+ *			(Xmax != my-transaction &&			the row was deleted by another transaction
+ *			 Xmax is not committed)))			that has not been committed
+ */
+static bool
+HeapTupleSatisfiesSelf(StorageTuple stup, Snapshot snapshot, Buffer buffer)
+{
+	HeapTuple htup = (HeapTuple)stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return false;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return false;
+			if (!TransactionIdIsInProgress(xvac))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (TransactionIdIsInProgress(xvac))
+					return false;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return true;
+
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
+				return true;
+
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleGetUpdateXid(tuple);
+
+				/* not LOCKED_ONLY, so it has to have an xmax */
+				Assert(TransactionIdIsValid(xmax));
+
+				/* updating subtransaction must have aborted */
+				if (!TransactionIdIsCurrentTransactionId(xmax))
+					return true;
+				else
+					return false;
+			}
+
+			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+			{
+				/* deleting subtransaction must have aborted */
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+				return true;
+			}
+
+			return false;
+		}
+		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+			return false;
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return false;
+		}
+	}
+
+	/* by here, the inserting transaction has committed */
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
+		return true;
+
+	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+		return false;			/* updated by other */
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+
+		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 true;
+		if (TransactionIdDidCommit(xmax))
+			return false;
+		/* it must have aborted or crashed */
+		return true;
+	}
+
+	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+		return false;
+	}
+
+	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+		return true;
+
+	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		/* it must have aborted or crashed */
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return true;
+	}
+
+	/* xmax transaction committed */
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	{
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return true;
+	}
+
+	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+				HeapTupleHeaderGetRawXmax(tuple));
+	return false;
+}
+
+/*
+ * HeapTupleSatisfiesAny
+ *		Dummy "satisfies" routine: any tuple satisfies SnapshotAny.
+ */
+static bool
+HeapTupleSatisfiesAny(StorageTuple stup, Snapshot snapshot, Buffer buffer)
+{
+	return true;
+}
+
+/*
+ * HeapTupleSatisfiesToast
+ *		True iff heap tuple is valid as a TOAST row.
+ *
+ * This is a simplified version that only checks for VACUUM moving conditions.
+ * It's appropriate for TOAST usage because TOAST really doesn't want to do
+ * its own time qual checks; if you can see the main table row that contains
+ * a TOAST reference, you should be able to see the TOASTed value.  However,
+ * vacuuming a TOAST table is independent of the main table, and in case such
+ * a vacuum fails partway through, we'd better do this much checking.
+ *
+ * Among other things, this means you can't do UPDATEs of rows in a TOAST
+ * table.
+ */
+static bool
+HeapTupleSatisfiesToast(StorageTuple stup, Snapshot snapshot,
+						Buffer buffer)
+{
+	HeapTuple htup = (HeapTuple)stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return false;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return false;
+			if (!TransactionIdIsInProgress(xvac))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (TransactionIdIsInProgress(xvac))
+					return false;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+			}
+		}
+
+		/*
+		 * An invalid Xmin can be left behind by a speculative insertion that
+		 * is canceled by super-deleting the tuple.  This also applies to
+		 * TOAST tuples created during speculative insertion.
+		 */
+		else if (!TransactionIdIsValid(HeapTupleHeaderGetXmin(tuple)))
+			return false;
+	}
+
+	/* otherwise assume the tuple is valid for TOAST. */
+	return true;
+}
+
+/*
+ * HeapTupleSatisfiesUpdate
+ *
+ *	This function returns a more detailed result code than most of the
+ *	functions in this file, since UPDATE needs to know more than "is it
+ *	visible?".  It also allows for user-supplied CommandId rather than
+ *	relying on CurrentCommandId.
+ *
+ *	The possible return codes are:
+ *
+ *	HeapTupleInvisible: the tuple didn't exist at all when the scan started,
+ *	e.g. it was created by a later CommandId.
+ *
+ *	HeapTupleMayBeUpdated: The tuple is valid and visible, so it may be
+ *	updated.
+ *
+ *	HeapTupleSelfUpdated: The tuple was updated by the current transaction,
+ *	after the current scan started.
+ *
+ *	HeapTupleUpdated: The tuple was updated by a committed transaction.
+ *
+ *	HeapTupleBeingUpdated: The tuple is being updated by an in-progress
+ *	transaction other than the current transaction.  (Note: this includes
+ *	the case where the tuple is share-locked by a MultiXact, even if the
+ *	MultiXact includes the current transaction.  Callers that want to
+ *	distinguish that case must test for it themselves.)
+ */
+static HTSU_Result
+HeapTupleSatisfiesUpdate(StorageTuple stup, CommandId curcid,
+						 Buffer buffer)
+{
+	HeapTuple htup = (HeapTuple)stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return HeapTupleInvisible;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return HeapTupleInvisible;
+			if (!TransactionIdIsInProgress(xvac))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return HeapTupleInvisible;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (TransactionIdIsInProgress(xvac))
+					return HeapTupleInvisible;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return HeapTupleInvisible;
+				}
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (HeapTupleHeaderGetCmin(tuple) >= curcid)
+				return HeapTupleInvisible;	/* inserted after scan started */
+
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return HeapTupleMayBeUpdated;
+
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleHeaderGetRawXmax(tuple);
+
+				/*
+				 * Careful here: even though this tuple was created by our own
+				 * transaction, it might be locked by other transactions, if
+				 * the original version was key-share locked when we updated
+				 * it.
+				 */
+
+				if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+				{
+					if (MultiXactIdIsRunning(xmax, true))
+						return HeapTupleBeingUpdated;
+					else
+						return HeapTupleMayBeUpdated;
+				}
+
+				/*
+				 * If the locker is gone, then there is nothing of interest
+				 * left in this Xmax; otherwise, report the tuple as
+				 * locked/updated.
+				 */
+				if (!TransactionIdIsInProgress(xmax))
+					return HeapTupleMayBeUpdated;
+				return HeapTupleBeingUpdated;
+			}
+
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleGetUpdateXid(tuple);
+
+				/* not LOCKED_ONLY, so it has to have an xmax */
+				Assert(TransactionIdIsValid(xmax));
+
+				/* deleting subtransaction must have aborted */
+				if (!TransactionIdIsCurrentTransactionId(xmax))
+				{
+					if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
+											 false))
+						return HeapTupleBeingUpdated;
+					return HeapTupleMayBeUpdated;
+				}
+				else
+				{
+					if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+						return HeapTupleSelfUpdated;	/* updated after scan
+														 * started */
+					else
+						return HeapTupleInvisible;	/* updated before scan
+													 * started */
+				}
+			}
+
+			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+			{
+				/* deleting subtransaction must have aborted */
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+				return HeapTupleMayBeUpdated;
+			}
+
+			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+				return HeapTupleSelfUpdated;	/* updated after scan started */
+			else
+				return HeapTupleInvisible;	/* updated before scan started */
+		}
+		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+			return HeapTupleInvisible;
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return HeapTupleInvisible;
+		}
+	}
+
+	/* by here, the inserting transaction has committed */
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
+		return HeapTupleMayBeUpdated;
+
+	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return HeapTupleMayBeUpdated;
+		return HeapTupleUpdated;	/* updated by other */
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		if (HEAP_LOCKED_UPGRADED(tuple->t_infomask))
+			return HeapTupleMayBeUpdated;
+
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+		{
+			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), true))
+				return HeapTupleBeingUpdated;
+
+			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
+			return HeapTupleMayBeUpdated;
+		}
+
+		xmax = HeapTupleGetUpdateXid(tuple);
+		if (!TransactionIdIsValid(xmax))
+		{
+			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+				return HeapTupleBeingUpdated;
+		}
+
+		/* not LOCKED_ONLY, so it has to have an xmax */
+		Assert(TransactionIdIsValid(xmax));
+
+		if (TransactionIdIsCurrentTransactionId(xmax))
+		{
+			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+				return HeapTupleSelfUpdated;	/* updated after scan started */
+			else
+				return HeapTupleInvisible;	/* updated before scan started */
+		}
+
+		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+			return HeapTupleBeingUpdated;
+
+		if (TransactionIdDidCommit(xmax))
+			return HeapTupleUpdated;
+
+		/*
+		 * By here, the update in the Xmax is either aborted or crashed, but
+		 * what about the other members?
+		 */
+
+		if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+		{
+			/*
+			 * There's no member, even just a locker, alive anymore, so we can
+			 * mark the Xmax as invalid.
+			 */
+			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+						InvalidTransactionId);
+			return HeapTupleMayBeUpdated;
+		}
+		else
+		{
+			/* There are lockers running */
+			return HeapTupleBeingUpdated;
+		}
+	}
+
+	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return HeapTupleBeingUpdated;
+		if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+			return HeapTupleSelfUpdated;	/* updated after scan started */
+		else
+			return HeapTupleInvisible;	/* updated before scan started */
+	}
+
+	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+		return HeapTupleBeingUpdated;
+
+	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		/* it must have aborted or crashed */
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return HeapTupleMayBeUpdated;
+	}
+
+	/* xmax transaction committed */
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	{
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return HeapTupleMayBeUpdated;
+	}
+
+	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+				HeapTupleHeaderGetRawXmax(tuple));
+	return HeapTupleUpdated;	/* updated by other */
+}
+
+/*
+ * HeapTupleSatisfiesDirty
+ *		True iff heap tuple is valid including effects of open transactions.
+ *
+ *	Here, we consider the effects of:
+ *		all committed and in-progress transactions (as of the current instant)
+ *		previous commands of this transaction
+ *		changes made by the current command
+ *
+ * This is essentially like HeapTupleSatisfiesSelf as far as effects of
+ * the current transaction and committed/aborted xacts are concerned.
+ * However, we also include the effects of other xacts still in progress.
+ *
+ * A special hack is that the passed-in snapshot struct is used as an
+ * output argument to return the xids of concurrent xacts that affected the
+ * tuple.  snapshot->xmin is set to the tuple's xmin if that is another
+ * transaction that's still in progress; or to InvalidTransactionId if the
+ * tuple's xmin is committed good, committed dead, or my own xact.
+ * Similarly for snapshot->xmax and the tuple's xmax.  If the tuple was
+ * inserted speculatively, meaning that the inserter might still back down
+ * on the insertion without aborting the whole transaction, the associated
+ * token is also returned in snapshot->speculativeToken.
+ */
+static bool
+HeapTupleSatisfiesDirty(StorageTuple stup, Snapshot snapshot,
+						Buffer buffer)
+{
+	HeapTuple htup = (HeapTuple)stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	snapshot->xmin = snapshot->xmax = InvalidTransactionId;
+	snapshot->speculativeToken = 0;
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return false;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return false;
+			if (!TransactionIdIsInProgress(xvac))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (TransactionIdIsInProgress(xvac))
+					return false;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return true;
+
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
+				return true;
+
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleGetUpdateXid(tuple);
+
+				/* not LOCKED_ONLY, so it has to have an xmax */
+				Assert(TransactionIdIsValid(xmax));
+
+				/* updating subtransaction must have aborted */
+				if (!TransactionIdIsCurrentTransactionId(xmax))
+					return true;
+				else
+					return false;
+			}
+
+			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+			{
+				/* deleting subtransaction must have aborted */
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+				return true;
+			}
+
+			return false;
+		}
+		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			/*
+			 * Return the speculative token to caller.  Caller can worry about
+			 * xmax, since it requires a conclusively locked row version, and
+			 * a concurrent update to this tuple is a conflict of its
+			 * purposes.
+			 */
+			if (HeapTupleHeaderIsSpeculative(tuple))
+			{
+				snapshot->speculativeToken =
+					HeapTupleHeaderGetSpeculativeToken(tuple);
+
+				Assert(snapshot->speculativeToken != 0);
+			}
+
+			snapshot->xmin = HeapTupleHeaderGetRawXmin(tuple);
+			/* XXX shouldn't we fall through to look at xmax? */
+			return true;		/* in insertion by other */
+		}
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return false;
+		}
+	}
+
+	/* by here, the inserting transaction has committed */
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
+		return true;
+
+	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+		return false;			/* updated by other */
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+
+		xmax = HeapTupleGetUpdateXid(tuple);
+
+		/* not LOCKED_ONLY, so it has to have an xmax */
+		Assert(TransactionIdIsValid(xmax));
+
+		if (TransactionIdIsCurrentTransactionId(xmax))
+			return false;
+		if (TransactionIdIsInProgress(xmax))
+		{
+			snapshot->xmax = xmax;
+			return true;
+		}
+		if (TransactionIdDidCommit(xmax))
+			return false;
+		/* it must have aborted or crashed */
+		return true;
+	}
+
+	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+		return false;
+	}
+
+	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			snapshot->xmax = HeapTupleHeaderGetRawXmax(tuple);
+		return true;
+	}
+
+	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		/* it must have aborted or crashed */
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return true;
+	}
+
+	/* xmax transaction committed */
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	{
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return true;
+	}
+
+	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+				HeapTupleHeaderGetRawXmax(tuple));
+	return false;				/* updated by other */
+}
+
+/*
+ * HeapTupleSatisfiesMVCC
+ *		True iff heap tuple is valid for the given MVCC snapshot.
+ *
+ *	Here, we consider the effects of:
+ *		all transactions committed as of the time of the given snapshot
+ *		previous commands of this transaction
+ *
+ *	Does _not_ include:
+ *		transactions shown as in-progress by the snapshot
+ *		transactions started after the snapshot was taken
+ *		changes made by the current command
+ *
+ * Notice that here, we will not update the tuple status hint bits if the
+ * inserting/deleting transaction is still running according to our snapshot,
+ * even if in reality it's committed or aborted by now.  This is intentional.
+ * Checking the true transaction state would require access to high-traffic
+ * shared data structures, creating contention we'd rather do without, and it
+ * would not change the result of our visibility check anyway.  The hint bits
+ * will be updated by the first visitor that has a snapshot new enough to see
+ * the inserting/deleting transaction as done.  In the meantime, the cost of
+ * leaving the hint bits unset is basically that each HeapTupleSatisfiesMVCC
+ * call will need to run TransactionIdIsCurrentTransactionId in addition to
+ * XidInMVCCSnapshot (but it would have to do the latter anyway).  In the old
+ * coding where we tried to set the hint bits as soon as possible, we instead
+ * did TransactionIdIsInProgress in each call --- to no avail, as long as the
+ * inserting/deleting transaction was still running --- which was more cycles
+ * and more contention on the PGXACT array.
+ */
+static bool
+HeapTupleSatisfiesMVCC(StorageTuple stup, Snapshot snapshot,
+					   Buffer buffer)
+{
+	HeapTuple htup = (HeapTuple)stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return false;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return false;
+			if (!XidInMVCCSnapshot(xvac, snapshot))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (XidInMVCCSnapshot(xvac, snapshot))
+					return false;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (HeapTupleHeaderGetCmin(tuple) >= snapshot->curcid)
+				return false;	/* inserted after scan started */
+
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return true;
+
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
+				return true;
+
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleGetUpdateXid(tuple);
+
+				/* not LOCKED_ONLY, so it has to have an xmax */
+				Assert(TransactionIdIsValid(xmax));
+
+				/* updating subtransaction must have aborted */
+				if (!TransactionIdIsCurrentTransactionId(xmax))
+					return true;
+				else if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+					return true;	/* updated after scan started */
+				else
+					return false;	/* updated before scan started */
+			}
+
+			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+			{
+				/* deleting subtransaction must have aborted */
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+				return true;
+			}
+
+			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+				return true;	/* deleted after scan started */
+			else
+				return false;	/* deleted before scan started */
+		}
+		else if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
+			return false;
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return false;
+		}
+	}
+	else
+	{
+		/* xmin is committed, but maybe not according to our snapshot */
+		if (!HeapTupleHeaderXminFrozen(tuple) &&
+			XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
+			return false;		/* treat as still in progress */
+	}
+
+	/* by here, the inserting transaction has committed */
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
+		return true;
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+		return true;
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		/* already checked above */
+		Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
+
+		xmax = HeapTupleGetUpdateXid(tuple);
+
+		/* not LOCKED_ONLY, so it has to have an xmax */
+		Assert(TransactionIdIsValid(xmax));
+
+		if (TransactionIdIsCurrentTransactionId(xmax))
+		{
+			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+				return true;	/* deleted after scan started */
+			else
+				return false;	/* deleted before scan started */
+		}
+		if (XidInMVCCSnapshot(xmax, snapshot))
+			return true;
+		if (TransactionIdDidCommit(xmax))
+			return false;		/* updating transaction committed */
+		/* it must have aborted or crashed */
+		return true;
+	}
+
+	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
+	{
+		if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+		{
+			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+				return true;	/* deleted after scan started */
+			else
+				return false;	/* deleted before scan started */
+		}
+
+		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
+			return true;
+
+		if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+						InvalidTransactionId);
+			return true;
+		}
+
+		/* xmax transaction committed */
+		SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+					HeapTupleHeaderGetRawXmax(tuple));
+	}
+	else
+	{
+		/* xmax is committed, but maybe not according to our snapshot */
+		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
+			return true;		/* treat as still in progress */
+	}
+
+	/* xmax transaction committed */
+
+	return false;
+}
+
+
+/*
+ * HeapTupleSatisfiesVacuum
+ *
+ *	Determine the status of tuples for VACUUM purposes.  Here, what
+ *	we mainly want to know is if a tuple is potentially visible to *any*
+ *	running transaction.  If so, it can't be removed yet by VACUUM.
+ *
+ * OldestXmin is a cutoff XID (obtained from GetOldestXmin()).  Tuples
+ * deleted by XIDs >= OldestXmin are deemed "recently dead"; they might
+ * still be visible to some open transaction, so we can't remove them,
+ * even if we see that the deleting transaction has committed.
+ */
+static HTSV_Result
+HeapTupleSatisfiesVacuum(StorageTuple stup, TransactionId OldestXmin,
+						 Buffer buffer)
+{
+	HeapTuple htup = (HeapTuple)stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	/*
+	 * Has inserting transaction committed?
+	 *
+	 * If the inserting transaction aborted, then the tuple was never visible
+	 * to any other transaction, so we can delete it immediately.
+	 */
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return HEAPTUPLE_DEAD;
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return HEAPTUPLE_DELETE_IN_PROGRESS;
+			if (TransactionIdIsInProgress(xvac))
+				return HEAPTUPLE_DELETE_IN_PROGRESS;
+			if (TransactionIdDidCommit(xvac))
+			{
+				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+							InvalidTransactionId);
+				return HEAPTUPLE_DEAD;
+			}
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						InvalidTransactionId);
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return HEAPTUPLE_INSERT_IN_PROGRESS;
+			if (TransactionIdIsInProgress(xvac))
+				return HEAPTUPLE_INSERT_IN_PROGRESS;
+			if (TransactionIdDidCommit(xvac))
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			else
+			{
+				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+							InvalidTransactionId);
+				return HEAPTUPLE_DEAD;
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return HEAPTUPLE_INSERT_IN_PROGRESS;
+			/* only locked? run infomask-only check first, for performance */
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask) ||
+				HeapTupleHeaderIsOnlyLocked(tuple))
+				return HEAPTUPLE_INSERT_IN_PROGRESS;
+			/* inserted and then deleted by same xact */
+			if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple)))
+				return HEAPTUPLE_DELETE_IN_PROGRESS;
+			/* deleting subtransaction must have aborted */
+			return HEAPTUPLE_INSERT_IN_PROGRESS;
+		}
+		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			/*
+			 * It'd be possible to discern between INSERT/DELETE in progress
+			 * here by looking at xmax - but that doesn't seem beneficial for
+			 * the majority of callers and even detrimental for some. We'd
+			 * rather have callers look at/wait for xmin than xmax. It's
+			 * always correct to return INSERT_IN_PROGRESS because that's
+			 * what's happening from the view of other backends.
+			 */
+			return HEAPTUPLE_INSERT_IN_PROGRESS;
+		}
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/*
+			 * Not in Progress, Not Committed, so either Aborted or crashed
+			 */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return HEAPTUPLE_DEAD;
+		}
+
+		/*
+		 * At this point the xmin is known committed, but we might not have
+		 * been able to set the hint bit yet; so we can no longer Assert that
+		 * it's set.
+		 */
+	}
+
+	/*
+	 * Okay, the inserter committed, so it was good at some point.  Now what
+	 * about the deleting transaction?
+	 */
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)
+		return HEAPTUPLE_LIVE;
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	{
+		/*
+		 * "Deleting" xact really only locked it, so the tuple is live in any
+		 * case.  However, we should make sure that either XMAX_COMMITTED or
+		 * XMAX_INVALID gets set once the xact is gone, to reduce the costs of
+		 * examining the tuple for future xacts.
+		 */
+		if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
+		{
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				/*
+				 * If it's a pre-pg_upgrade tuple, the multixact cannot
+				 * possibly be running; otherwise have to check.
+				 */
+				if (!HEAP_LOCKED_UPGRADED(tuple->t_infomask) &&
+					MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
+										 true))
+					return HEAPTUPLE_LIVE;
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
+			}
+			else
+			{
+				if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+					return HEAPTUPLE_LIVE;
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+			}
+		}
+
+		/*
+		 * We don't really care whether xmax did commit, abort or crash. We
+		 * know that xmax did lock the tuple, but it did not and will never
+		 * actually update it.
+		 */
+
+		return HEAPTUPLE_LIVE;
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+		{
+			/* already checked above */
+			Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
+
+			xmax = HeapTupleGetUpdateXid(tuple);
+
+			/* not LOCKED_ONLY, so it has to have an xmax */
+			Assert(TransactionIdIsValid(xmax));
+
+			if (TransactionIdIsInProgress(xmax))
+				return HEAPTUPLE_DELETE_IN_PROGRESS;
+			else if (TransactionIdDidCommit(xmax))
+				/* there are still lockers around -- can't return DEAD here */
+				return HEAPTUPLE_RECENTLY_DEAD;
+			/* updating transaction aborted */
+			return HEAPTUPLE_LIVE;
+		}
+
+		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED));
+
+		xmax = HeapTupleGetUpdateXid(tuple);
+
+		/* not LOCKED_ONLY, so it has to have an xmax */
+		Assert(TransactionIdIsValid(xmax));
+
+		/* multi is not running -- updating xact cannot be */
+		Assert(!TransactionIdIsInProgress(xmax));
+		if (TransactionIdDidCommit(xmax))
+		{
+			if (!TransactionIdPrecedes(xmax, OldestXmin))
+				return HEAPTUPLE_RECENTLY_DEAD;
+			else
+				return HEAPTUPLE_DEAD;
+		}
+
+		/*
+		 * Not in Progress, Not Committed, so either Aborted or crashed.
+		 * Remove the Xmax.
+		 */
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
+		return HEAPTUPLE_LIVE;
+	}
+
+	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
+	{
+		if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+			return HEAPTUPLE_DELETE_IN_PROGRESS;
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+						HeapTupleHeaderGetRawXmax(tuple));
+		else
+		{
+			/*
+			 * Not in Progress, Not Committed, so either Aborted or crashed
+			 */
+			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+						InvalidTransactionId);
+			return HEAPTUPLE_LIVE;
+		}
+
+		/*
+		 * At this point the xmax is known committed, but we might not have
+		 * been able to set the hint bit yet; so we can no longer Assert that
+		 * it's set.
+		 */
+	}
+
+	/*
+	 * Deleter committed, but perhaps it was recent enough that some open
+	 * transactions could still see the tuple.
+	 */
+	if (!TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin))
+		return HEAPTUPLE_RECENTLY_DEAD;
+
+	/* Otherwise, it's dead and removable */
+	return HEAPTUPLE_DEAD;
+}
+
+/*
+ * HeapTupleSatisfiesNonVacuumable
+ *
+ *     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.
+ */
+static bool
+HeapTupleSatisfiesNonVacuumable(StorageTuple htup, Snapshot snapshot,
+                                                               Buffer buffer)
+{
+	return HeapTupleSatisfiesVacuum(htup, snapshot->xmin, buffer)
+				!= HEAPTUPLE_DEAD;
+}
+
+/*
+ * 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 for known-committed
+ * XIDs which could not be ours anyway.
+ */
+static 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;
+}
+
+/*
+ * check whether the transaction id 'xid' is in the pre-sorted array 'xip'.
+ */
+static bool
+TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num)
+{
+	return bsearch(&xid, xip, num,
+				   sizeof(TransactionId), xidComparator) != NULL;
+}
+
+/*
+ * See the comments for HeapTupleSatisfiesMVCC for the semantics this function
+ * obeys.
+ *
+ * Only usable on tuples from catalog tables!
+ *
+ * We don't need to support HEAP_MOVED_(IN|OFF) for now because we only support
+ * reading catalog pages which couldn't have been created in an older version.
+ *
+ * We don't set any hint bits in here as it seems unlikely to be beneficial as
+ * those should already be set by normal access and it seems to be too
+ * dangerous to do so as the semantics of doing so during timetravel are more
+ * complicated than when dealing "only" with the present.
+ */
+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);
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	/* inserting transaction aborted */
+	if (HeapTupleHeaderXminInvalid(tuple))
+	{
+		Assert(!TransactionIdDidCommit(xmin));
+		return false;
+	}
+	/* check if it's one of our txids, toplevel is also in there */
+	else if (TransactionIdInArray(xmin, snapshot->subxip, snapshot->subxcnt))
+	{
+		bool		resolved;
+		CommandId	cmin = HeapTupleHeaderGetRawCommandId(tuple);
+		CommandId	cmax = InvalidCommandId;
+
+		/*
+		 * another transaction might have (tried to) delete this tuple or
+		 * cmin/cmax was stored in a combocid. So we need to lookup the actual
+		 * values externally.
+		 */
+		resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot,
+												 htup, buffer,
+												 &cmin, &cmax);
+
+		if (!resolved)
+			elog(ERROR, "could not resolve cmin/cmax of catalog tuple");
+
+		Assert(cmin != InvalidCommandId);
+
+		if (cmin >= snapshot->curcid)
+			return false;		/* inserted after scan started */
+		/* fall through */
+	}
+	/* committed before our xmin horizon. Do a normal visibility check. */
+	else if (TransactionIdPrecedes(xmin, snapshot->xmin))
+	{
+		Assert(!(HeapTupleHeaderXminCommitted(tuple) &&
+				 !TransactionIdDidCommit(xmin)));
+
+		/* check for hint bit first, consult clog afterwards */
+		if (!HeapTupleHeaderXminCommitted(tuple) &&
+			!TransactionIdDidCommit(xmin))
+			return false;
+		/* fall through */
+	}
+	/* beyond our xmax horizon, i.e. invisible */
+	else if (TransactionIdFollowsOrEquals(xmin, snapshot->xmax))
+	{
+		return false;
+	}
+	/* check if it's a committed transaction in [xmin, xmax) */
+	else if (TransactionIdInArray(xmin, snapshot->xip, snapshot->xcnt))
+	{
+		/* fall through */
+	}
+
+	/*
+	 * none of the above, i.e. between [xmin, xmax) but hasn't committed. I.e.
+	 * invisible.
+	 */
+	else
+	{
+		return false;
+	}
+
+	/* at this point we know xmin is visible, go on to check xmax */
+
+	/* xid invalid or aborted */
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)
+		return true;
+	/* locked tuples are always visible */
+	else if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+		return true;
+
+	/*
+	 * We can see multis here if we're looking at user tables or if somebody
+	 * SELECT ... FOR SHARE/UPDATE a system table.
+	 */
+	else if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		xmax = HeapTupleGetUpdateXid(tuple);
+	}
+
+	/* check if it's one of our txids, toplevel is also in there */
+	if (TransactionIdInArray(xmax, snapshot->subxip, snapshot->subxcnt))
+	{
+		bool		resolved;
+		CommandId	cmin;
+		CommandId	cmax = HeapTupleHeaderGetRawCommandId(tuple);
+
+		/* Lookup actual cmin/cmax values */
+		resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot,
+												 htup, buffer,
+												 &cmin, &cmax);
+
+		if (!resolved)
+			elog(ERROR, "could not resolve combocid to cmax");
+
+		Assert(cmax != InvalidCommandId);
+
+		if (cmax >= snapshot->curcid)
+			return true;		/* deleted after scan started */
+		else
+			return false;		/* deleted before scan started */
+	}
+	/* below xmin horizon, normal transaction state is valid */
+	else if (TransactionIdPrecedes(xmax, snapshot->xmin))
+	{
+		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED &&
+				 !TransactionIdDidCommit(xmax)));
+
+		/* check hint bit first */
+		if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+			return false;
+
+		/* check clog */
+		return !TransactionIdDidCommit(xmax);
+	}
+	/* above xmax horizon, we cannot possibly see the deleting transaction */
+	else if (TransactionIdFollowsOrEquals(xmax, snapshot->xmax))
+		return true;
+	/* xmax is between [xmin, xmax), check known committed array */
+	else if (TransactionIdInArray(xmax, snapshot->xip, snapshot->xcnt))
+		return false;
+	/* xmax is between [xmin, xmax), but known not to have committed yet */
+	else
+		return true;
+}
 
 Datum
 heapam_storage_handler(PG_FUNCTION_ARGS)
@@ -54,5 +1666,15 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
 
 
+	amroutine->snapshot_satisfies[MVCC_VISIBILITY] = HeapTupleSatisfiesMVCC;
+	amroutine->snapshot_satisfies[SELF_VISIBILITY] = HeapTupleSatisfiesSelf;
+	amroutine->snapshot_satisfies[ANY_VISIBILITY] = HeapTupleSatisfiesAny;
+	amroutine->snapshot_satisfies[TOAST_VISIBILITY] = HeapTupleSatisfiesToast;
+	amroutine->snapshot_satisfies[DIRTY_VISIBILITY] = HeapTupleSatisfiesDirty;
+	amroutine->snapshot_satisfies[HISTORIC_MVCC_VISIBILITY] = HeapTupleSatisfiesHistoricMVCC;
+	amroutine->snapshot_satisfies[NON_VACUUMABLE_VISIBILTY] = HeapTupleSatisfiesNonVacuumable;
+
+	amroutine->snapshot_satisfiesUpdate = HeapTupleSatisfiesUpdate;
+	amroutine->snapshot_satisfiesVacuum = HeapTupleSatisfiesVacuum;
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 52231ac417..74250688a8 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/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index bd560e47e1..191f088703 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -108,6 +108,7 @@
 #include "miscadmin.h"
 
 #include "access/heapam.h"
+#include "access/heapam_common.h"
 #include "access/heapam_xlog.h"
 #include "access/rewriteheap.h"
 #include "access/transam.h"
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/catalog/index.c b/src/backend/catalog/index.c
index c7b2f031f0..0240df7a08 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2222,6 +2222,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	TransactionId OldestXmin;
 	BlockNumber root_blkno = InvalidBlockNumber;
 	OffsetNumber root_offsets[MaxHeapTuplesPerPage];
+	StorageAmRoutine *method;
 
 	/*
 	 * sanity checks
@@ -2277,6 +2278,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 */
@@ -2357,7 +2359,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 			 */
 			LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
 
-			switch (HeapTupleSatisfiesVacuum(heapTuple, OldestXmin,
+			switch (method->snapshot_satisfiesVacuum(heapTuple, OldestXmin,
 											 scan->rs_cbuf))
 			{
 				case HEAPTUPLE_DEAD:
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 08fc18e96b..2611cca6d4 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1069,7 +1069,7 @@ acquire_sample_rows(Relation onerel, int elevel,
 			targtuple.t_data = (HeapTupleHeader) PageGetItem(targpage, itemid);
 			targtuple.t_len = ItemIdGetLength(itemid);
 
-			switch (HeapTupleSatisfiesVacuum(&targtuple,
+			switch (onerel->rd_stamroutine->snapshot_satisfiesVacuum(&targtuple,
 											 OldestXmin,
 											 targbuffer))
 			{
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 45b1859475..dccda5ab32 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -975,7 +975,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:
 
@@ -2140,7 +2140,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 f7e55e0b45..60a6cb03d8 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -428,7 +428,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 845c409540..b39e48b222 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -190,6 +190,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
  */
 static void
 ExecCheckHeapTupleVisible(EState *estate,
+						  Relation rel,
 						  HeapTuple tuple,
 						  Buffer buffer)
 {
@@ -201,7 +202,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
@@ -236,7 +237,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);
 }
 
@@ -1312,7 +1313,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 fba57a0470..095d1edd12 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 c2891e6fa1..5a6d2168e1 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -289,7 +289,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 294ab705f1..f823da09f4 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;
@@ -2040,7 +2040,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),
@@ -2137,7 +2137,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/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c
deleted file mode 100644
index bbac4083c9..0000000000
--- a/src/backend/utils/time/tqual.c
+++ /dev/null
@@ -1,1809 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * tqual.c
- *	  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
- * has now committed or aborted (and it is safe to set the hint bits).
- * If the hint bits are changed, MarkBufferDirtyHint is called on
- * the passed-in buffer.  The caller must hold not only a pin, but at least
- * shared buffer content lock on the buffer containing the tuple.
- *
- * NOTE: When using a non-MVCC snapshot, we must check
- * TransactionIdIsInProgress (which looks in the PGXACT array)
- * before TransactionIdDidCommit/TransactionIdDidAbort (which look in
- * pg_xact).  Otherwise we have a race condition: we might decide that a
- * just-committed transaction crashed, because none of the tests succeed.
- * xact.c is careful to record commit/abort in pg_xact before it unsets
- * MyPgXact->xid in the PGXACT array.  That fixes that problem, but it
- * also means there is a window where TransactionIdIsInProgress and
- * TransactionIdDidCommit will both return true.  If we check only
- * TransactionIdDidCommit, we could consider a tuple committed when a
- * later GetSnapshotData call will still think the originating transaction
- * is in progress, which leads to application-level inconsistency.  The
- * upshot is that we gotta check TransactionIdIsInProgress first in all
- * code paths, except for a few cases where we are looking at
- * subtransactions of our own main transaction and so there can't be any
- * race condition.
- *
- * When using an MVCC snapshot, we rely on XidInMVCCSnapshot rather than
- * TransactionIdIsInProgress, but the logic is otherwise the same: do not
- * check pg_xact until after deciding that the xact is no longer in progress.
- *
- *
- * Summary of visibility functions:
- *
- *	 HeapTupleSatisfiesMVCC()
- *		  visible to supplied snapshot, excludes current command
- *	 HeapTupleSatisfiesUpdate()
- *		  visible to instant snapshot, with user-supplied command
- *		  counter and more complex result
- *	 HeapTupleSatisfiesSelf()
- *		  visible to instant snapshot and current command
- *	 HeapTupleSatisfiesDirty()
- *		  like HeapTupleSatisfiesSelf(), but includes open transactions
- *	 HeapTupleSatisfiesVacuum()
- *		  visible to any running transaction, used by VACUUM
- *	 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};
-
-/* local functions */
-static 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);
-}
-
-/*
- * 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
- *		True iff heap tuple is valid "for itself".
- *
- *	Here, we consider the effects of:
- *		all committed transactions (as of the current instant)
- *		previous commands of this transaction
- *		changes made by the current command
- *
- * Note:
- *		Assumes heap tuple is valid.
- *
- * The satisfaction of "itself" requires the following:
- *
- * ((Xmin == my-transaction &&				the row was updated by the current transaction, and
- *		(Xmax is null						it was not deleted
- *		 [|| Xmax != my-transaction)])			[or it was deleted by another transaction]
- * ||
- *
- * (Xmin is committed &&					the row was modified by a committed transaction, and
- *		(Xmax is null ||					the row has not been deleted, or
- *			(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)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return false;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return false;
-			if (!TransactionIdIsInProgress(xvac))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (TransactionIdIsInProgress(xvac))
-					return false;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return true;
-
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
-				return true;
-
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleGetUpdateXid(tuple);
-
-				/* not LOCKED_ONLY, so it has to have an xmax */
-				Assert(TransactionIdIsValid(xmax));
-
-				/* updating subtransaction must have aborted */
-				if (!TransactionIdIsCurrentTransactionId(xmax))
-					return true;
-				else
-					return false;
-			}
-
-			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-			{
-				/* deleting subtransaction must have aborted */
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-				return true;
-			}
-
-			return false;
-		}
-		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
-			return false;
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return false;
-		}
-	}
-
-	/* by here, the inserting transaction has committed */
-
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
-		return true;
-
-	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-		return false;			/* updated by other */
-	}
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-
-		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 true;
-		if (TransactionIdDidCommit(xmax))
-			return false;
-		/* it must have aborted or crashed */
-		return true;
-	}
-
-	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-		return false;
-	}
-
-	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-		return true;
-
-	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		/* it must have aborted or crashed */
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return true;
-	}
-
-	/* xmax transaction committed */
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-	{
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return true;
-	}
-
-	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-				HeapTupleHeaderGetRawXmax(tuple));
-	return false;
-}
-
-/*
- * HeapTupleSatisfiesAny
- *		Dummy "satisfies" routine: any tuple satisfies SnapshotAny.
- */
-bool
-HeapTupleSatisfiesAny(HeapTuple htup, Snapshot snapshot, Buffer buffer)
-{
-	return true;
-}
-
-/*
- * HeapTupleSatisfiesToast
- *		True iff heap tuple is valid as a TOAST row.
- *
- * This is a simplified version that only checks for VACUUM moving conditions.
- * It's appropriate for TOAST usage because TOAST really doesn't want to do
- * its own time qual checks; if you can see the main table row that contains
- * a TOAST reference, you should be able to see the TOASTed value.  However,
- * vacuuming a TOAST table is independent of the main table, and in case such
- * a vacuum fails partway through, we'd better do this much checking.
- *
- * Among other things, this means you can't do UPDATEs of rows in a TOAST
- * table.
- */
-bool
-HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot,
-						Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return false;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return false;
-			if (!TransactionIdIsInProgress(xvac))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (TransactionIdIsInProgress(xvac))
-					return false;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-			}
-		}
-
-		/*
-		 * An invalid Xmin can be left behind by a speculative insertion that
-		 * is canceled by super-deleting the tuple.  This also applies to
-		 * TOAST tuples created during speculative insertion.
-		 */
-		else if (!TransactionIdIsValid(HeapTupleHeaderGetXmin(tuple)))
-			return false;
-	}
-
-	/* otherwise assume the tuple is valid for TOAST. */
-	return true;
-}
-
-/*
- * HeapTupleSatisfiesUpdate
- *
- *	This function returns a more detailed result code than most of the
- *	functions in this file, since UPDATE needs to know more than "is it
- *	visible?".  It also allows for user-supplied CommandId rather than
- *	relying on CurrentCommandId.
- *
- *	The possible return codes are:
- *
- *	HeapTupleInvisible: the tuple didn't exist at all when the scan started,
- *	e.g. it was created by a later CommandId.
- *
- *	HeapTupleMayBeUpdated: The tuple is valid and visible, so it may be
- *	updated.
- *
- *	HeapTupleSelfUpdated: The tuple was updated by the current transaction,
- *	after the current scan started.
- *
- *	HeapTupleUpdated: The tuple was updated by a committed transaction.
- *
- *	HeapTupleBeingUpdated: The tuple is being updated by an in-progress
- *	transaction other than the current transaction.  (Note: this includes
- *	the case where the tuple is share-locked by a MultiXact, even if the
- *	MultiXact includes the current transaction.  Callers that want to
- *	distinguish that case must test for it themselves.)
- */
-HTSU_Result
-HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
-						 Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return HeapTupleInvisible;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return HeapTupleInvisible;
-			if (!TransactionIdIsInProgress(xvac))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return HeapTupleInvisible;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (TransactionIdIsInProgress(xvac))
-					return HeapTupleInvisible;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return HeapTupleInvisible;
-				}
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (HeapTupleHeaderGetCmin(tuple) >= curcid)
-				return HeapTupleInvisible;	/* inserted after scan started */
-
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return HeapTupleMayBeUpdated;
-
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleHeaderGetRawXmax(tuple);
-
-				/*
-				 * Careful here: even though this tuple was created by our own
-				 * transaction, it might be locked by other transactions, if
-				 * the original version was key-share locked when we updated
-				 * it.
-				 */
-
-				if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-				{
-					if (MultiXactIdIsRunning(xmax, true))
-						return HeapTupleBeingUpdated;
-					else
-						return HeapTupleMayBeUpdated;
-				}
-
-				/*
-				 * If the locker is gone, then there is nothing of interest
-				 * left in this Xmax; otherwise, report the tuple as
-				 * locked/updated.
-				 */
-				if (!TransactionIdIsInProgress(xmax))
-					return HeapTupleMayBeUpdated;
-				return HeapTupleBeingUpdated;
-			}
-
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleGetUpdateXid(tuple);
-
-				/* not LOCKED_ONLY, so it has to have an xmax */
-				Assert(TransactionIdIsValid(xmax));
-
-				/* deleting subtransaction must have aborted */
-				if (!TransactionIdIsCurrentTransactionId(xmax))
-				{
-					if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
-											 false))
-						return HeapTupleBeingUpdated;
-					return HeapTupleMayBeUpdated;
-				}
-				else
-				{
-					if (HeapTupleHeaderGetCmax(tuple) >= curcid)
-						return HeapTupleSelfUpdated;	/* updated after scan
-														 * started */
-					else
-						return HeapTupleInvisible;	/* updated before scan
-													 * started */
-				}
-			}
-
-			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-			{
-				/* deleting subtransaction must have aborted */
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-				return HeapTupleMayBeUpdated;
-			}
-
-			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
-				return HeapTupleSelfUpdated;	/* updated after scan started */
-			else
-				return HeapTupleInvisible;	/* updated before scan started */
-		}
-		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
-			return HeapTupleInvisible;
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return HeapTupleInvisible;
-		}
-	}
-
-	/* by here, the inserting transaction has committed */
-
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
-		return HeapTupleMayBeUpdated;
-
-	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return HeapTupleMayBeUpdated;
-		return HeapTupleUpdated;	/* updated by other */
-	}
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		if (HEAP_LOCKED_UPGRADED(tuple->t_infomask))
-			return HeapTupleMayBeUpdated;
-
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-		{
-			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), true))
-				return HeapTupleBeingUpdated;
-
-			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
-			return HeapTupleMayBeUpdated;
-		}
-
-		xmax = HeapTupleGetUpdateXid(tuple);
-		if (!TransactionIdIsValid(xmax))
-		{
-			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
-				return HeapTupleBeingUpdated;
-		}
-
-		/* not LOCKED_ONLY, so it has to have an xmax */
-		Assert(TransactionIdIsValid(xmax));
-
-		if (TransactionIdIsCurrentTransactionId(xmax))
-		{
-			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
-				return HeapTupleSelfUpdated;	/* updated after scan started */
-			else
-				return HeapTupleInvisible;	/* updated before scan started */
-		}
-
-		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
-			return HeapTupleBeingUpdated;
-
-		if (TransactionIdDidCommit(xmax))
-			return HeapTupleUpdated;
-
-		/*
-		 * By here, the update in the Xmax is either aborted or crashed, but
-		 * what about the other members?
-		 */
-
-		if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
-		{
-			/*
-			 * There's no member, even just a locker, alive anymore, so we can
-			 * mark the Xmax as invalid.
-			 */
-			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-						InvalidTransactionId);
-			return HeapTupleMayBeUpdated;
-		}
-		else
-		{
-			/* There are lockers running */
-			return HeapTupleBeingUpdated;
-		}
-	}
-
-	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return HeapTupleBeingUpdated;
-		if (HeapTupleHeaderGetCmax(tuple) >= curcid)
-			return HeapTupleSelfUpdated;	/* updated after scan started */
-		else
-			return HeapTupleInvisible;	/* updated before scan started */
-	}
-
-	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-		return HeapTupleBeingUpdated;
-
-	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		/* it must have aborted or crashed */
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return HeapTupleMayBeUpdated;
-	}
-
-	/* xmax transaction committed */
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-	{
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return HeapTupleMayBeUpdated;
-	}
-
-	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-				HeapTupleHeaderGetRawXmax(tuple));
-	return HeapTupleUpdated;	/* updated by other */
-}
-
-/*
- * HeapTupleSatisfiesDirty
- *		True iff heap tuple is valid including effects of open transactions.
- *
- *	Here, we consider the effects of:
- *		all committed and in-progress transactions (as of the current instant)
- *		previous commands of this transaction
- *		changes made by the current command
- *
- * This is essentially like HeapTupleSatisfiesSelf as far as effects of
- * the current transaction and committed/aborted xacts are concerned.
- * However, we also include the effects of other xacts still in progress.
- *
- * A special hack is that the passed-in snapshot struct is used as an
- * output argument to return the xids of concurrent xacts that affected the
- * tuple.  snapshot->xmin is set to the tuple's xmin if that is another
- * transaction that's still in progress; or to InvalidTransactionId if the
- * tuple's xmin is committed good, committed dead, or my own xact.
- * Similarly for snapshot->xmax and the tuple's xmax.  If the tuple was
- * inserted speculatively, meaning that the inserter might still back down
- * on the insertion without aborting the whole transaction, the associated
- * token is also returned in snapshot->speculativeToken.
- */
-bool
-HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
-						Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	snapshot->xmin = snapshot->xmax = InvalidTransactionId;
-	snapshot->speculativeToken = 0;
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return false;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return false;
-			if (!TransactionIdIsInProgress(xvac))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (TransactionIdIsInProgress(xvac))
-					return false;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return true;
-
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
-				return true;
-
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleGetUpdateXid(tuple);
-
-				/* not LOCKED_ONLY, so it has to have an xmax */
-				Assert(TransactionIdIsValid(xmax));
-
-				/* updating subtransaction must have aborted */
-				if (!TransactionIdIsCurrentTransactionId(xmax))
-					return true;
-				else
-					return false;
-			}
-
-			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-			{
-				/* deleting subtransaction must have aborted */
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-				return true;
-			}
-
-			return false;
-		}
-		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			/*
-			 * Return the speculative token to caller.  Caller can worry about
-			 * xmax, since it requires a conclusively locked row version, and
-			 * a concurrent update to this tuple is a conflict of its
-			 * purposes.
-			 */
-			if (HeapTupleHeaderIsSpeculative(tuple))
-			{
-				snapshot->speculativeToken =
-					HeapTupleHeaderGetSpeculativeToken(tuple);
-
-				Assert(snapshot->speculativeToken != 0);
-			}
-
-			snapshot->xmin = HeapTupleHeaderGetRawXmin(tuple);
-			/* XXX shouldn't we fall through to look at xmax? */
-			return true;		/* in insertion by other */
-		}
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return false;
-		}
-	}
-
-	/* by here, the inserting transaction has committed */
-
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
-		return true;
-
-	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-		return false;			/* updated by other */
-	}
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-
-		xmax = HeapTupleGetUpdateXid(tuple);
-
-		/* not LOCKED_ONLY, so it has to have an xmax */
-		Assert(TransactionIdIsValid(xmax));
-
-		if (TransactionIdIsCurrentTransactionId(xmax))
-			return false;
-		if (TransactionIdIsInProgress(xmax))
-		{
-			snapshot->xmax = xmax;
-			return true;
-		}
-		if (TransactionIdDidCommit(xmax))
-			return false;
-		/* it must have aborted or crashed */
-		return true;
-	}
-
-	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-		return false;
-	}
-
-	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			snapshot->xmax = HeapTupleHeaderGetRawXmax(tuple);
-		return true;
-	}
-
-	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		/* it must have aborted or crashed */
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return true;
-	}
-
-	/* xmax transaction committed */
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-	{
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return true;
-	}
-
-	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-				HeapTupleHeaderGetRawXmax(tuple));
-	return false;				/* updated by other */
-}
-
-/*
- * HeapTupleSatisfiesMVCC
- *		True iff heap tuple is valid for the given MVCC snapshot.
- *
- *	Here, we consider the effects of:
- *		all transactions committed as of the time of the given snapshot
- *		previous commands of this transaction
- *
- *	Does _not_ include:
- *		transactions shown as in-progress by the snapshot
- *		transactions started after the snapshot was taken
- *		changes made by the current command
- *
- * Notice that here, we will not update the tuple status hint bits if the
- * inserting/deleting transaction is still running according to our snapshot,
- * even if in reality it's committed or aborted by now.  This is intentional.
- * Checking the true transaction state would require access to high-traffic
- * shared data structures, creating contention we'd rather do without, and it
- * would not change the result of our visibility check anyway.  The hint bits
- * will be updated by the first visitor that has a snapshot new enough to see
- * the inserting/deleting transaction as done.  In the meantime, the cost of
- * leaving the hint bits unset is basically that each HeapTupleSatisfiesMVCC
- * call will need to run TransactionIdIsCurrentTransactionId in addition to
- * XidInMVCCSnapshot (but it would have to do the latter anyway).  In the old
- * coding where we tried to set the hint bits as soon as possible, we instead
- * did TransactionIdIsInProgress in each call --- to no avail, as long as the
- * inserting/deleting transaction was still running --- which was more cycles
- * and more contention on the PGXACT array.
- */
-bool
-HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
-					   Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return false;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return false;
-			if (!XidInMVCCSnapshot(xvac, snapshot))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (XidInMVCCSnapshot(xvac, snapshot))
-					return false;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (HeapTupleHeaderGetCmin(tuple) >= snapshot->curcid)
-				return false;	/* inserted after scan started */
-
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return true;
-
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
-				return true;
-
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleGetUpdateXid(tuple);
-
-				/* not LOCKED_ONLY, so it has to have an xmax */
-				Assert(TransactionIdIsValid(xmax));
-
-				/* updating subtransaction must have aborted */
-				if (!TransactionIdIsCurrentTransactionId(xmax))
-					return true;
-				else if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
-					return true;	/* updated after scan started */
-				else
-					return false;	/* updated before scan started */
-			}
-
-			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-			{
-				/* deleting subtransaction must have aborted */
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-				return true;
-			}
-
-			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
-				return true;	/* deleted after scan started */
-			else
-				return false;	/* deleted before scan started */
-		}
-		else if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
-			return false;
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return false;
-		}
-	}
-	else
-	{
-		/* xmin is committed, but maybe not according to our snapshot */
-		if (!HeapTupleHeaderXminFrozen(tuple) &&
-			XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
-			return false;		/* treat as still in progress */
-	}
-
-	/* by here, the inserting transaction has committed */
-
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
-		return true;
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-		return true;
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		/* already checked above */
-		Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
-
-		xmax = HeapTupleGetUpdateXid(tuple);
-
-		/* not LOCKED_ONLY, so it has to have an xmax */
-		Assert(TransactionIdIsValid(xmax));
-
-		if (TransactionIdIsCurrentTransactionId(xmax))
-		{
-			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
-				return true;	/* deleted after scan started */
-			else
-				return false;	/* deleted before scan started */
-		}
-		if (XidInMVCCSnapshot(xmax, snapshot))
-			return true;
-		if (TransactionIdDidCommit(xmax))
-			return false;		/* updating transaction committed */
-		/* it must have aborted or crashed */
-		return true;
-	}
-
-	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
-	{
-		if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-		{
-			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
-				return true;	/* deleted after scan started */
-			else
-				return false;	/* deleted before scan started */
-		}
-
-		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
-			return true;
-
-		if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-						InvalidTransactionId);
-			return true;
-		}
-
-		/* xmax transaction committed */
-		SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-					HeapTupleHeaderGetRawXmax(tuple));
-	}
-	else
-	{
-		/* xmax is committed, but maybe not according to our snapshot */
-		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
-			return true;		/* treat as still in progress */
-	}
-
-	/* xmax transaction committed */
-
-	return false;
-}
-
-
-/*
- * HeapTupleSatisfiesVacuum
- *
- *	Determine the status of tuples for VACUUM purposes.  Here, what
- *	we mainly want to know is if a tuple is potentially visible to *any*
- *	running transaction.  If so, it can't be removed yet by VACUUM.
- *
- * OldestXmin is a cutoff XID (obtained from GetOldestXmin()).  Tuples
- * deleted by XIDs >= OldestXmin are deemed "recently dead"; they might
- * still be visible to some open transaction, so we can't remove them,
- * even if we see that the deleting transaction has committed.
- */
-HTSV_Result
-HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
-						 Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	/*
-	 * Has inserting transaction committed?
-	 *
-	 * If the inserting transaction aborted, then the tuple was never visible
-	 * to any other transaction, so we can delete it immediately.
-	 */
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return HEAPTUPLE_DEAD;
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return HEAPTUPLE_DELETE_IN_PROGRESS;
-			if (TransactionIdIsInProgress(xvac))
-				return HEAPTUPLE_DELETE_IN_PROGRESS;
-			if (TransactionIdDidCommit(xvac))
-			{
-				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-							InvalidTransactionId);
-				return HEAPTUPLE_DEAD;
-			}
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						InvalidTransactionId);
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return HEAPTUPLE_INSERT_IN_PROGRESS;
-			if (TransactionIdIsInProgress(xvac))
-				return HEAPTUPLE_INSERT_IN_PROGRESS;
-			if (TransactionIdDidCommit(xvac))
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			else
-			{
-				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-							InvalidTransactionId);
-				return HEAPTUPLE_DEAD;
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return HEAPTUPLE_INSERT_IN_PROGRESS;
-			/* only locked? run infomask-only check first, for performance */
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask) ||
-				HeapTupleHeaderIsOnlyLocked(tuple))
-				return HEAPTUPLE_INSERT_IN_PROGRESS;
-			/* inserted and then deleted by same xact */
-			if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple)))
-				return HEAPTUPLE_DELETE_IN_PROGRESS;
-			/* deleting subtransaction must have aborted */
-			return HEAPTUPLE_INSERT_IN_PROGRESS;
-		}
-		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			/*
-			 * It'd be possible to discern between INSERT/DELETE in progress
-			 * here by looking at xmax - but that doesn't seem beneficial for
-			 * the majority of callers and even detrimental for some. We'd
-			 * rather have callers look at/wait for xmin than xmax. It's
-			 * always correct to return INSERT_IN_PROGRESS because that's
-			 * what's happening from the view of other backends.
-			 */
-			return HEAPTUPLE_INSERT_IN_PROGRESS;
-		}
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/*
-			 * Not in Progress, Not Committed, so either Aborted or crashed
-			 */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return HEAPTUPLE_DEAD;
-		}
-
-		/*
-		 * At this point the xmin is known committed, but we might not have
-		 * been able to set the hint bit yet; so we can no longer Assert that
-		 * it's set.
-		 */
-	}
-
-	/*
-	 * Okay, the inserter committed, so it was good at some point.  Now what
-	 * about the deleting transaction?
-	 */
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)
-		return HEAPTUPLE_LIVE;
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-	{
-		/*
-		 * "Deleting" xact really only locked it, so the tuple is live in any
-		 * case.  However, we should make sure that either XMAX_COMMITTED or
-		 * XMAX_INVALID gets set once the xact is gone, to reduce the costs of
-		 * examining the tuple for future xacts.
-		 */
-		if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
-		{
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				/*
-				 * If it's a pre-pg_upgrade tuple, the multixact cannot
-				 * possibly be running; otherwise have to check.
-				 */
-				if (!HEAP_LOCKED_UPGRADED(tuple->t_infomask) &&
-					MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
-										 true))
-					return HEAPTUPLE_LIVE;
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
-			}
-			else
-			{
-				if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-					return HEAPTUPLE_LIVE;
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-			}
-		}
-
-		/*
-		 * We don't really care whether xmax did commit, abort or crash. We
-		 * know that xmax did lock the tuple, but it did not and will never
-		 * actually update it.
-		 */
-
-		return HEAPTUPLE_LIVE;
-	}
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
-		{
-			/* already checked above */
-			Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
-
-			xmax = HeapTupleGetUpdateXid(tuple);
-
-			/* not LOCKED_ONLY, so it has to have an xmax */
-			Assert(TransactionIdIsValid(xmax));
-
-			if (TransactionIdIsInProgress(xmax))
-				return HEAPTUPLE_DELETE_IN_PROGRESS;
-			else if (TransactionIdDidCommit(xmax))
-				/* there are still lockers around -- can't return DEAD here */
-				return HEAPTUPLE_RECENTLY_DEAD;
-			/* updating transaction aborted */
-			return HEAPTUPLE_LIVE;
-		}
-
-		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED));
-
-		xmax = HeapTupleGetUpdateXid(tuple);
-
-		/* not LOCKED_ONLY, so it has to have an xmax */
-		Assert(TransactionIdIsValid(xmax));
-
-		/* multi is not running -- updating xact cannot be */
-		Assert(!TransactionIdIsInProgress(xmax));
-		if (TransactionIdDidCommit(xmax))
-		{
-			if (!TransactionIdPrecedes(xmax, OldestXmin))
-				return HEAPTUPLE_RECENTLY_DEAD;
-			else
-				return HEAPTUPLE_DEAD;
-		}
-
-		/*
-		 * Not in Progress, Not Committed, so either Aborted or crashed.
-		 * Remove the Xmax.
-		 */
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
-		return HEAPTUPLE_LIVE;
-	}
-
-	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
-	{
-		if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-			return HEAPTUPLE_DELETE_IN_PROGRESS;
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-						HeapTupleHeaderGetRawXmax(tuple));
-		else
-		{
-			/*
-			 * Not in Progress, Not Committed, so either Aborted or crashed
-			 */
-			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-						InvalidTransactionId);
-			return HEAPTUPLE_LIVE;
-		}
-
-		/*
-		 * At this point the xmax is known committed, but we might not have
-		 * been able to set the hint bit yet; so we can no longer Assert that
-		 * it's set.
-		 */
-	}
-
-	/*
-	 * Deleter committed, but perhaps it was recent enough that some open
-	 * transactions could still see the tuple.
-	 */
-	if (!TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin))
-		return HEAPTUPLE_RECENTLY_DEAD;
-
-	/* Otherwise, it's dead and removable */
-	return HEAPTUPLE_DEAD;
-}
-
-
-/*
- * HeapTupleSatisfiesNonVacuumable
- *
- *	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.
- */
-bool
-HeapTupleSatisfiesNonVacuumable(HeapTuple 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 for known-committed
- * XIDs which could not be ours anyway.
- */
-static 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'.
- */
-static bool
-TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num)
-{
-	return bsearch(&xid, xip, num,
-				   sizeof(TransactionId), xidComparator) != NULL;
-}
-
-/*
- * See the comments for HeapTupleSatisfiesMVCC for the semantics this function
- * obeys.
- *
- * Only usable on tuples from catalog tables!
- *
- * We don't need to support HEAP_MOVED_(IN|OFF) for now because we only support
- * reading catalog pages which couldn't have been created in an older version.
- *
- * We don't set any hint bits in here as it seems unlikely to be beneficial as
- * those should already be set by normal access and it seems to be too
- * 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,
-							   Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-	TransactionId xmin = HeapTupleHeaderGetXmin(tuple);
-	TransactionId xmax = HeapTupleHeaderGetRawXmax(tuple);
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	/* inserting transaction aborted */
-	if (HeapTupleHeaderXminInvalid(tuple))
-	{
-		Assert(!TransactionIdDidCommit(xmin));
-		return false;
-	}
-	/* check if it's one of our txids, toplevel is also in there */
-	else if (TransactionIdInArray(xmin, snapshot->subxip, snapshot->subxcnt))
-	{
-		bool		resolved;
-		CommandId	cmin = HeapTupleHeaderGetRawCommandId(tuple);
-		CommandId	cmax = InvalidCommandId;
-
-		/*
-		 * another transaction might have (tried to) delete this tuple or
-		 * cmin/cmax was stored in a combocid. So we need to lookup the actual
-		 * values externally.
-		 */
-		resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot,
-												 htup, buffer,
-												 &cmin, &cmax);
-
-		if (!resolved)
-			elog(ERROR, "could not resolve cmin/cmax of catalog tuple");
-
-		Assert(cmin != InvalidCommandId);
-
-		if (cmin >= snapshot->curcid)
-			return false;		/* inserted after scan started */
-		/* fall through */
-	}
-	/* committed before our xmin horizon. Do a normal visibility check. */
-	else if (TransactionIdPrecedes(xmin, snapshot->xmin))
-	{
-		Assert(!(HeapTupleHeaderXminCommitted(tuple) &&
-				 !TransactionIdDidCommit(xmin)));
-
-		/* check for hint bit first, consult clog afterwards */
-		if (!HeapTupleHeaderXminCommitted(tuple) &&
-			!TransactionIdDidCommit(xmin))
-			return false;
-		/* fall through */
-	}
-	/* beyond our xmax horizon, i.e. invisible */
-	else if (TransactionIdFollowsOrEquals(xmin, snapshot->xmax))
-	{
-		return false;
-	}
-	/* check if it's a committed transaction in [xmin, xmax) */
-	else if (TransactionIdInArray(xmin, snapshot->xip, snapshot->xcnt))
-	{
-		/* fall through */
-	}
-
-	/*
-	 * none of the above, i.e. between [xmin, xmax) but hasn't committed. I.e.
-	 * invisible.
-	 */
-	else
-	{
-		return false;
-	}
-
-	/* at this point we know xmin is visible, go on to check xmax */
-
-	/* xid invalid or aborted */
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)
-		return true;
-	/* locked tuples are always visible */
-	else if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-		return true;
-
-	/*
-	 * We can see multis here if we're looking at user tables or if somebody
-	 * SELECT ... FOR SHARE/UPDATE a system table.
-	 */
-	else if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		xmax = HeapTupleGetUpdateXid(tuple);
-	}
-
-	/* check if it's one of our txids, toplevel is also in there */
-	if (TransactionIdInArray(xmax, snapshot->subxip, snapshot->subxcnt))
-	{
-		bool		resolved;
-		CommandId	cmin;
-		CommandId	cmax = HeapTupleHeaderGetRawCommandId(tuple);
-
-		/* Lookup actual cmin/cmax values */
-		resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot,
-												 htup, buffer,
-												 &cmin, &cmax);
-
-		if (!resolved)
-			elog(ERROR, "could not resolve combocid to cmax");
-
-		Assert(cmax != InvalidCommandId);
-
-		if (cmax >= snapshot->curcid)
-			return true;		/* deleted after scan started */
-		else
-			return false;		/* deleted before scan started */
-	}
-	/* below xmin horizon, normal transaction state is valid */
-	else if (TransactionIdPrecedes(xmax, snapshot->xmin))
-	{
-		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED &&
-				 !TransactionIdDidCommit(xmax)));
-
-		/* check hint bit first */
-		if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
-			return false;
-
-		/* check clog */
-		return !TransactionIdDidCommit(xmax);
-	}
-	/* above xmax horizon, we cannot possibly see the deleting transaction */
-	else if (TransactionIdFollowsOrEquals(xmax, snapshot->xmax))
-		return true;
-	/* xmax is between [xmin, xmax), check known committed array */
-	else if (TransactionIdInArray(xmax, snapshot->xip, snapshot->xcnt))
-		return false;
-	/* xmax is between [xmin, xmax), but known not to have committed yet */
-	else
-		return true;
-}
diff --git a/src/include/access/heapam_common.h b/src/include/access/heapam_common.h
new file mode 100644
index 0000000000..ff63cf3615
--- /dev/null
+++ b/src/include/access/heapam_common.h
@@ -0,0 +1,96 @@
+/*-------------------------------------------------------------------------
+ *
+ * heapam_shared.h
+ *	  POSTGRES heap access method definitions shared across
+ *	  server and heapam methods.
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/heapam_shared.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef HEAPAM_SHARED_H
+#define HEAPAM_SHARED_H
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/sdir.h"
+#include "access/skey.h"
+#include "access/transam.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "nodes/lockoptions.h"
+#include "nodes/primnodes.h"
+#include "storage/bufpage.h"
+#include "storage/bufmgr.h"
+#include "storage/lockdefs.h"
+#include "storage/lmgr.h"
+#include "utils/relcache.h"
+#include "utils/snapshot.h"
+
+
+/* in heap/heapam_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);
+
+/*
+ * 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							/* HEAPAM_H */
diff --git a/src/include/access/htup.h b/src/include/access/htup.h
index 6459435c78..5b0068a907 100644
--- a/src/include/access/htup.h
+++ b/src/include/access/htup.h
@@ -29,11 +29,12 @@ typedef MinimalTupleData *MinimalTuple;
 typedef enum tuple_visibility_type
 {
 	MVCC_VISIBILITY = 0, 		/* HeapTupleSatisfiesMVCC */
-	SELF_VISIBILITY,				/* HeapTupleSatisfiesSelf */
+	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;
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 98b63fc5ba..b8b823bce9 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -14,13 +14,13 @@
 #ifndef BUFMGR_H
 #define BUFMGR_H
 
+#include "access/storageamapi.h"
 #include "storage/block.h"
 #include "storage/buf.h"
 #include "storage/bufpage.h"
 #include "storage/relfilenode.h"
 #include "utils/relcache.h"
 #include "utils/snapmgr.h"
-#include "utils/tqual.h"
 
 typedef void *Block;
 
@@ -268,8 +268,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..5752ee4731 100644
--- a/src/include/utils/snapshot.h
+++ b/src/include/utils/snapshot.h
@@ -52,7 +52,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 de75e01706..67a8b1ea98 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,48 +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))
-
-
-/* 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 void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
-					 uint16 infomask, TransactionId xid);
-extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
-
-/*
- * To avoid leaking too much knowledge about reorderbuffer implementation
- * details this is implemented in reorderbuffer.c not tqual.c.
- */
-struct HTAB;
-extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data,
-							  Snapshot snapshot,
-							  HeapTuple htup,
-							  Buffer buffer,
-							  CommandId *cmin, CommandId *cmax);
+#define HeapTupleSatisfiesVisibility(method, tuple, snapshot, buffer) \
+       (((method)->snapshot_satisfies[(snapshot)->visibility_type]) (tuple, snapshot, buffer))
 
 /*
  * We don't provide a static SnapshotDirty variable because it would be
@@ -91,14 +52,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))
 
 /*
@@ -106,8 +67,19 @@ 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))
 
+/*
+ * To avoid leaking too much knowledge about reorderbuffer implementation
+ * details this is implemented in reorderbuffer.c not tqual.c.
+ */
+struct HTAB;
+extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data,
+                                                         Snapshot snapshot,
+                                                         HeapTuple htup,
+                                                         Buffer buffer,
+                                                         CommandId *cmin, CommandId *cmax);
+
 #endif							/* TQUAL_H */
-- 
2.14.1.windows.1

0005-slot-hooks-are-added-to-storage-AM.patchapplication/octet-stream; name=0005-slot-hooks-are-added-to-storage-AM.patchDownload
From 441b8930f0467e50a928329a6b9889c487408e0f Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Wed, 30 Aug 2017 17:24:35 +1000
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_common.c  | 404 +++++++++++++++++++++++++++++++
 src/backend/access/heap/heapam_storage.c |   1 +
 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    |  26 +-
 src/backend/executor/execReplication.c   |  92 ++-----
 src/backend/executor/execTuples.c        | 269 +++++++++-----------
 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/heapam_common.h       |   2 +
 src/include/access/htup_details.h        |  15 +-
 src/include/executor/tuptable.h          |  54 +++--
 17 files changed, 644 insertions(+), 575 deletions(-)

diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 13ee528e26..5ed0f15ac4 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_common.c b/src/backend/access/heap/heapam_common.c
index 502f6dbccb..6cb6c5be30 100644
--- a/src/backend/access/heap/heapam_common.c
+++ b/src/backend/access/heap/heapam_common.c
@@ -159,4 +159,408 @@ HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin)
 	return TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin);
 }
 
+/*-----------------------
+ *
+ * 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;
+}
 
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 1bd4bfaa33..7d7ac759e3 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -1665,6 +1665,7 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 {
 	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
 
+	amroutine->slot_storageam = heapam_storage_slot_handler;
 
 	amroutine->snapshot_satisfies[MVCC_VISIBILITY] = HeapTupleSatisfiesMVCC;
 	amroutine->snapshot_satisfies[SELF_VISIBILITY] = HeapTupleSatisfiesSelf;
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index c6fa44563c..f1f546a321 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2697,7 +2697,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 e60210cb24..a0ec444d33 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 e75a59d299..2f530169b8 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;
@@ -4013,14 +4013,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 bd8a15d6c3..47757386ee 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -503,12 +503,12 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			int			attnum = op->d.var.attnum;
 
 			/* 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 */
 
-			*op->resvalue = heap_getsysattr(innerslot->tts_tuple, attnum,
-											innerslot->tts_tupleDescriptor,
+			*op->resvalue = slot_getattr(innerslot,
+											attnum,
 											op->resnull);
 
 			EEO_NEXT();
@@ -519,12 +519,12 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			int			attnum = op->d.var.attnum;
 
 			/* 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 */
-			*op->resvalue = heap_getsysattr(outerslot->tts_tuple, attnum,
-											outerslot->tts_tupleDescriptor,
+
+			*op->resvalue = slot_getattr(outerslot,
+											attnum,
 											op->resnull);
 
 			EEO_NEXT();
@@ -535,12 +535,12 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			int			attnum = op->d.var.attnum;
 
 			/* 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 */
 
-			*op->resvalue = heap_getsysattr(scanslot->tts_tuple, attnum,
-											scanslot->tts_tupleDescriptor,
+			*op->resvalue = slot_getattr(scanslot,
+											attnum,
 											op->resnull);
 
 			EEO_NEXT();
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 5a75e0211f..6700f0ad80 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,7 +226,8 @@ bool
 RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 						 TupleTableSlot *searchslot, TupleTableSlot *outslot)
 {
-	HeapTuple	scantuple;
+    TupleTableSlot *scanslot;
+    HeapTuple	scantuple;
 	HeapScanDesc scan;
 	SnapshotData snap;
 	TransactionId xwait;
@@ -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_update_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..b7a2cbc023 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -81,6 +81,7 @@
  */
 #include "postgres.h"
 
+#include "access/heapam_common.h"
 #include "access/htup_details.h"
 #include "access/tuptoaster.h"
 #include "funcapi.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,18 @@ 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 +459,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 +540,7 @@ ExecStoreAllNullTuple(TupleTableSlot *slot)
  *		however the "system columns" of the result will not be meaningful.
  * --------------------------------
  */
-HeapTuple
+StorageTuple
 ExecCopySlotTuple(TupleTableSlot *slot)
 {
 	/*
@@ -550,20 +549,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 +568,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 +598,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 +645,7 @@ MinimalTuple
 ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 {
 	MemoryContext oldContext;
+	MinimalTuple tup;
 
 	/*
 	 * sanity checks
@@ -659,11 +653,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 +664,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 +697,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 +717,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 +728,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 +741,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 20892d6d5f..02f6c816aa 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 b39e48b222..dd8f792404 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -171,7 +171,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;
@@ -271,7 +271,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
@@ -406,7 +406,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -419,7 +419,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		newId = InvalidOid;
 	}
@@ -437,7 +437,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
@@ -746,7 +746,7 @@ ExecDelete(ModifyTableState *mtstate,
 		 */
 		if (slot->tts_isempty)
 			ExecStoreAllNullTuple(slot);
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
 	}
 	else
@@ -897,7 +897,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))
@@ -958,7 +958,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
@@ -977,7 +977,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW UPDATE Triggers */
@@ -991,7 +991,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
@@ -1007,7 +1007,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
@@ -1123,7 +1123,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 e9a5d5a1a5..9a47276274 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 bc6d8246a7..a60be0049d 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -724,9 +724,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/heapam_common.h b/src/include/access/heapam_common.h
index ff63cf3615..1fe15ede56 100644
--- a/src/include/access/heapam_common.h
+++ b/src/include/access/heapam_common.h
@@ -40,6 +40,8 @@ extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
 					 uint16 infomask, TransactionId xid);
 extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
 extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin);
+typedef struct StorageSlotAmRoutine StorageSlotAmRoutine;
+extern StorageSlotAmRoutine* heapam_storage_slot_handler(void);
 
 /*
  * SetHintBits()
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index fa04a63b76..9539d67bec 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
@@ -653,7 +666,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/executor/tuptable.h b/src/include/executor/tuptable.h
index 55f4cce4ee..84dc293122 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.14.1.windows.1

0006-Tuple-Insert-API-is-added-to-Storage-AM.patchapplication/octet-stream; name=0006-Tuple-Insert-API-is-added-to-Storage-AM.patchDownload
From 58e0f867076b3ea20cc4aab1c2bebb50e040a6b5 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Thu, 7 Sep 2017 14:48:24 +1000
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.

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/Makefile         |    2 +-
 src/backend/access/heap/heapam.c         | 2737 ++++--------------------------
 src/backend/access/heap/heapam_storage.c | 2153 ++++++++++++++++++++++-
 src/backend/access/heap/rewriteheap.c    |    5 +-
 src/backend/access/heap/storageam.c      |  306 ++++
 src/backend/access/heap/tuptoaster.c     |    8 +-
 src/backend/commands/copy.c              |   29 +-
 src/backend/commands/createas.c          |   18 +-
 src/backend/commands/matview.c           |   10 +-
 src/backend/commands/tablecmds.c         |    5 +-
 src/backend/commands/trigger.c           |   43 +-
 src/backend/executor/execMain.c          |  120 +-
 src/backend/executor/execReplication.c   |   35 +-
 src/backend/executor/nodeLockRows.c      |   39 +-
 src/backend/executor/nodeModifyTable.c   |  177 +-
 src/backend/executor/nodeTidscan.c       |   23 +-
 src/backend/utils/adt/tid.c              |    5 +-
 src/include/access/heapam.h              |   26 +-
 src/include/access/heapam_common.h       |  127 ++
 src/include/access/htup_details.h        |    1 +
 src/include/access/storageam.h           |   81 +
 src/include/access/storageamapi.h        |   21 +-
 src/include/commands/trigger.h           |    2 +-
 src/include/executor/executor.h          |    6 +-
 src/include/nodes/execnodes.h            |    4 +-
 26 files changed, 3290 insertions(+), 2717 deletions(-)
 create mode 100644 src/backend/access/heap/storageam.c
 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 5ed0f15ac4..714c4d862c 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/Makefile b/src/backend/access/heap/Makefile
index e6bc18e5ea..162736ff15 100644
--- a/src/backend/access/heap/Makefile
+++ b/src/backend/access/heap/Makefile
@@ -13,7 +13,7 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = heapam.o heapam_common.o heapam_storage.o hio.o \
-	pruneheap.o rewriteheap.o storageamapi.o \
+	pruneheap.o rewriteheap.o storageam.o storageamapi.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 c21d6f8559..d20f211a08 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -94,8 +94,6 @@ static HeapScanDesc heap_beginscan_internal(Relation relation,
 						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,
-					TransactionId xid, CommandId cid, int options);
 static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 				Buffer newbuf, HeapTuple oldtup,
 				HeapTuple newtup, HeapTuple old_key_tup,
@@ -103,108 +101,17 @@ static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 static Bitmapset *HeapDetermineModifiedColumns(Relation relation,
 							 Bitmapset *interesting_cols,
 							 HeapTuple oldtup, HeapTuple newtup);
-static bool heap_acquire_tuplock(Relation relation, ItemPointer tid,
-					 LockTupleMode mode, LockWaitPolicy wait_policy,
-					 bool *have_tuple_lock);
-static void compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
-						  uint16 old_infomask2, TransactionId add_to_xmax,
-						  LockTupleMode mode, bool is_update,
-						  TransactionId *result_xmax, uint16 *result_infomask,
-						  uint16 *result_infomask2);
-static HTSU_Result heap_lock_updated_tuple(Relation rel, HeapTuple tuple,
-						ItemPointer ctid, TransactionId xid,
-						LockTupleMode mode);
 static void GetMultiXactIdHintBits(MultiXactId multi, uint16 *new_infomask,
 					   uint16 *new_infomask2);
 static TransactionId MultiXactIdGetUpdateXid(TransactionId xmax,
 						uint16 t_infomask);
-static bool DoesMultiXactIdConflict(MultiXactId multi, uint16 infomask,
-						LockTupleMode lockmode);
-static void MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
-				Relation rel, ItemPointer ctid, XLTW_Oper oper,
-				int *remaining);
-static bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
-						   uint16 infomask, Relation rel, int *remaining);
-static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
+static bool Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
+				   uint16 infomask, bool nowait,
+				   Relation rel, ItemPointer ctid, XLTW_Oper oper,
+				   int *remaining);
 static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_modified,
 					   bool *copy);
 
-
-/*
- * Each tuple lock mode has a corresponding heavyweight lock, and one or two
- * corresponding MultiXactStatuses (one to merely lock tuples, another one to
- * update them).  This table (and the macros below) helps us determine the
- * heavyweight lock mode and MultiXactStatus values to use for any particular
- * tuple lock strength.
- *
- * Don't look at lockstatus/updstatus directly!  Use get_mxact_status_for_lock
- * instead.
- */
-static const struct
-{
-	LOCKMODE	hwlock;
-	int			lockstatus;
-	int			updstatus;
-}
-
-			tupleLockExtraInfo[MaxLockTupleMode + 1] =
-{
-	{							/* LockTupleKeyShare */
-		AccessShareLock,
-		MultiXactStatusForKeyShare,
-		-1						/* KeyShare does not allow updating tuples */
-	},
-	{							/* LockTupleShare */
-		RowShareLock,
-		MultiXactStatusForShare,
-		-1						/* Share does not allow updating tuples */
-	},
-	{							/* LockTupleNoKeyExclusive */
-		ExclusiveLock,
-		MultiXactStatusForNoKeyUpdate,
-		MultiXactStatusNoKeyUpdate
-	},
-	{							/* LockTupleExclusive */
-		AccessExclusiveLock,
-		MultiXactStatusForUpdate,
-		MultiXactStatusUpdate
-	}
-};
-
-/* Get the LOCKMODE for a given MultiXactStatus */
-#define LOCKMODE_from_mxstatus(status) \
-			(tupleLockExtraInfo[TUPLOCK_from_mxstatus((status))].hwlock)
-
-/*
- * Acquire heavyweight locks on tuples, using a LockTupleMode strength value.
- * This is more readable than having every caller translate it to lock.h's
- * LOCKMODE.
- */
-#define LockTupleTuplock(rel, tup, mode) \
-	LockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
-#define UnlockTupleTuplock(rel, tup, mode) \
-	UnlockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
-#define ConditionalLockTupleTuplock(rel, tup, mode) \
-	ConditionalLockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
-
-/*
- * This table maps tuple lock strength values for each particular
- * MultiXactStatus value.
- */
-static const int MultiXactStatusLock[MaxMultiXactStatus + 1] =
-{
-	LockTupleKeyShare,			/* ForKeyShare */
-	LockTupleShare,				/* ForShare */
-	LockTupleNoKeyExclusive,	/* ForNoKeyUpdate */
-	LockTupleExclusive,			/* ForUpdate */
-	LockTupleNoKeyExclusive,	/* NoKeyUpdate */
-	LockTupleExclusive			/* Update */
-};
-
-/* Get the LockTupleMode for a given MultiXactStatus */
-#define TUPLOCK_from_mxstatus(status) \
-			(MultiXactStatusLock[(status)])
-
 /* ----------------------------------------------------------------
  *						 heap support routines
  * ----------------------------------------------------------------
@@ -1837,158 +1744,6 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 	return &(scan->rs_ctup);
 }
 
-/*
- *	heap_fetch		- retrieve tuple with given tid
- *
- * On entry, tuple->t_self is the TID to fetch.  We pin the buffer holding
- * the tuple, fill in the remaining fields of *tuple, and check the tuple
- * against the specified snapshot.
- *
- * If successful (tuple found and passes snapshot time qual), then *userbuf
- * is set to the buffer holding the tuple and TRUE is returned.  The caller
- * must unpin the buffer when done with the tuple.
- *
- * If the tuple is not found (ie, item number references a deleted slot),
- * then tuple->t_data is set to NULL and FALSE is returned.
- *
- * If the tuple is found but fails the time qual check, then FALSE is returned
- * but tuple->t_data is left pointing to the tuple.
- *
- * keep_buf determines what is done with the buffer in the FALSE-result cases.
- * When the caller specifies keep_buf = true, we retain the pin on the buffer
- * and return it in *userbuf (so the caller must eventually unpin it); when
- * keep_buf = false, the pin is released and *userbuf is set to InvalidBuffer.
- *
- * stats_relation is the relation to charge the heap_fetch operation against
- * for statistical purposes.  (This could be the heap rel itself, an
- * associated index, or NULL to not count the fetch at all.)
- *
- * heap_fetch does not follow HOT chains: only the exact TID requested will
- * be fetched.
- *
- * It is somewhat inconsistent that we ereport() on invalid block number but
- * return false on invalid item number.  There are a couple of reasons though.
- * One is that the caller can relatively easily check the block number for
- * validity, but cannot check the item number without reading the page
- * himself.  Another is that when we are following a t_ctid link, we can be
- * reasonably confident that the page number is valid (since VACUUM shouldn't
- * truncate off the destination page without having killed the referencing
- * tuple first), but the item number might well not be good.
- */
-bool
-heap_fetch(Relation relation,
-		   Snapshot snapshot,
-		   HeapTuple tuple,
-		   Buffer *userbuf,
-		   bool keep_buf,
-		   Relation stats_relation)
-{
-	ItemPointer tid = &(tuple->t_self);
-	ItemId		lp;
-	Buffer		buffer;
-	Page		page;
-	OffsetNumber offnum;
-	bool		valid;
-
-	/*
-	 * Fetch and pin the appropriate page of the relation.
-	 */
-	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
-
-	/*
-	 * Need share lock on buffer to examine tuple commit status.
-	 */
-	LockBuffer(buffer, BUFFER_LOCK_SHARE);
-	page = BufferGetPage(buffer);
-	TestForOldSnapshot(snapshot, relation, page);
-
-	/*
-	 * We'd better check for out-of-range offnum in case of VACUUM since the
-	 * TID was obtained.
-	 */
-	offnum = ItemPointerGetOffsetNumber(tid);
-	if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
-	{
-		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-		if (keep_buf)
-			*userbuf = buffer;
-		else
-		{
-			ReleaseBuffer(buffer);
-			*userbuf = InvalidBuffer;
-		}
-		tuple->t_data = NULL;
-		return false;
-	}
-
-	/*
-	 * get the item line pointer corresponding to the requested tid
-	 */
-	lp = PageGetItemId(page, offnum);
-
-	/*
-	 * Must check for deleted tuple.
-	 */
-	if (!ItemIdIsNormal(lp))
-	{
-		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-		if (keep_buf)
-			*userbuf = buffer;
-		else
-		{
-			ReleaseBuffer(buffer);
-			*userbuf = InvalidBuffer;
-		}
-		tuple->t_data = NULL;
-		return false;
-	}
-
-	/*
-	 * fill in *tuple fields
-	 */
-	tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	tuple->t_len = ItemIdGetLength(lp);
-	tuple->t_tableOid = RelationGetRelid(relation);
-
-	/*
-	 * check time qualification of tuple, then release lock
-	 */
-	valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, tuple, snapshot, buffer);
-
-	if (valid)
-		PredicateLockTuple(relation, tuple, snapshot);
-
-	CheckForSerializableConflictOut(valid, relation, tuple, buffer, snapshot);
-
-	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-
-	if (valid)
-	{
-		/*
-		 * All checks passed, so return the tuple as valid. Caller is now
-		 * responsible for releasing the buffer.
-		 */
-		*userbuf = buffer;
-
-		/* Count the successful fetch against appropriate rel, if any */
-		if (stats_relation != NULL)
-			pgstat_count_heap_fetch(stats_relation);
-
-		return true;
-	}
-
-	/* Tuple failed time qual, but maybe caller wants to see it anyway. */
-	if (keep_buf)
-		*userbuf = buffer;
-	else
-	{
-		ReleaseBuffer(buffer);
-		*userbuf = InvalidBuffer;
-	}
-
-	return false;
-}
-
 /*
  *	heap_hot_search_buffer	- search HOT chain for tuple satisfying snapshot
  *
@@ -2172,130 +1927,6 @@ heap_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot,
 	return result;
 }
 
-/*
- *	heap_get_latest_tid -  get the latest tid of a specified tuple
- *
- * Actually, this gets the latest version that is visible according to
- * the passed snapshot.  You can pass SnapshotDirty to get the very latest,
- * possibly uncommitted version.
- *
- * *tid is both an input and an output parameter: it is updated to
- * show the latest version of the row.  Note that it will not be changed
- * if no version of the row passes the snapshot test.
- */
-void
-heap_get_latest_tid(Relation relation,
-					Snapshot snapshot,
-					ItemPointer tid)
-{
-	BlockNumber blk;
-	ItemPointerData ctid;
-	TransactionId priorXmax;
-
-	/* this is to avoid Assert failures on bad input */
-	if (!ItemPointerIsValid(tid))
-		return;
-
-	/*
-	 * Since this can be called with user-supplied TID, don't trust the input
-	 * too much.  (RelationGetNumberOfBlocks is an expensive check, so we
-	 * don't check t_ctid links again this way.  Note that it would not do to
-	 * call it just once and save the result, either.)
-	 */
-	blk = ItemPointerGetBlockNumber(tid);
-	if (blk >= RelationGetNumberOfBlocks(relation))
-		elog(ERROR, "block number %u is out of range for relation \"%s\"",
-			 blk, RelationGetRelationName(relation));
-
-	/*
-	 * Loop to chase down t_ctid links.  At top of loop, ctid is the tuple we
-	 * need to examine, and *tid is the TID we will return if ctid turns out
-	 * to be bogus.
-	 *
-	 * Note that we will loop until we reach the end of the t_ctid chain.
-	 * Depending on the snapshot passed, there might be at most one visible
-	 * version of the row, but we don't try to optimize for that.
-	 */
-	ctid = *tid;
-	priorXmax = InvalidTransactionId;	/* cannot check first XMIN */
-	for (;;)
-	{
-		Buffer		buffer;
-		Page		page;
-		OffsetNumber offnum;
-		ItemId		lp;
-		HeapTupleData tp;
-		bool		valid;
-
-		/*
-		 * Read, pin, and lock the page.
-		 */
-		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&ctid));
-		LockBuffer(buffer, BUFFER_LOCK_SHARE);
-		page = BufferGetPage(buffer);
-		TestForOldSnapshot(snapshot, relation, page);
-
-		/*
-		 * Check for bogus item number.  This is not treated as an error
-		 * condition because it can happen while following a t_ctid link. We
-		 * just assume that the prior tid is OK and return it unchanged.
-		 */
-		offnum = ItemPointerGetOffsetNumber(&ctid);
-		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-		lp = PageGetItemId(page, offnum);
-		if (!ItemIdIsNormal(lp))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-
-		/* OK to access the tuple */
-		tp.t_self = ctid;
-		tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-		tp.t_len = ItemIdGetLength(lp);
-		tp.t_tableOid = RelationGetRelid(relation);
-
-		/*
-		 * After following a t_ctid link, we might arrive at an unrelated
-		 * tuple.  Check for XMIN match.
-		 */
-		if (TransactionIdIsValid(priorXmax) &&
-			!TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(tp.t_data)))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-
-		/*
-		 * Check time qualification of tuple; if visible, set it as the new
-		 * result candidate.
-		 */
-		valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, &tp, snapshot, buffer);
-		CheckForSerializableConflictOut(valid, relation, &tp, buffer, snapshot);
-		if (valid)
-			*tid = ctid;
-
-		/*
-		 * If there's a valid t_ctid link, follow it, else we're done.
-		 */
-		if ((tp.t_data->t_infomask & HEAP_XMAX_INVALID) ||
-			HeapTupleHeaderIsOnlyLocked(tp.t_data) ||
-			ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-
-		ctid = tp.t_data->t_ctid;
-		priorXmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
-		UnlockReleaseBuffer(buffer);
-	}							/* end of loop */
-}
-
 
 /*
  * UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends
@@ -2313,7 +1944,7 @@ heap_get_latest_tid(Relation relation,
  *
  * Note this is not allowed for tuples whose xmax is a multixact.
  */
-static void
+void
 UpdateXmaxHintBits(HeapTupleHeader tuple, Buffer buffer, TransactionId xid)
 {
 	Assert(TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple), xid));
@@ -2596,7 +2227,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
  * tuple if not. Note that in any case, the header fields are also set in
  * the original tuple.
  */
-static HeapTuple
+HeapTuple
 heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
 					CommandId cid, int options)
 {
@@ -2664,412 +2295,110 @@ heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
 }
 
 /*
- *	heap_multi_insert	- insert multiple tuple into a heap
+ *	simple_heap_insert - insert a tuple
+ *
+ * Currently, this routine differs from heap_insert only in supplying
+ * a default command ID and not allowing access to the speedup options.
  *
- * This is like heap_insert(), but inserts multiple tuples in one operation.
- * That's faster than calling heap_insert() in a loop, because when multiple
- * tuples can be inserted on a single page, we can write just a single WAL
- * record covering all of them, and only need to lock/unlock the page once.
+ * This should be used rather than using heap_insert directly in most places
+ * where we are modifying system catalogs.
+ */
+Oid
+simple_heap_insert(Relation relation, HeapTuple tup)
+{
+	return heap_insert(relation, tup, GetCurrentCommandId(true), 0, NULL);
+}
+
+/*
+ * Given infomask/infomask2, compute the bits that must be saved in the
+ * "infobits" field of xl_heap_delete, xl_heap_update, xl_heap_lock,
+ * xl_heap_lock_updated WAL records.
  *
- * Note: this leaks memory into the current memory context. You can create a
- * temporary context before calling this, if that's a problem.
+ * See fix_infomask_from_infobits.
  */
-void
-heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
-				  CommandId cid, int options, BulkInsertState bistate)
+uint8
+compute_infobits(uint16 infomask, uint16 infomask2)
+{
+	return
+		((infomask & HEAP_XMAX_IS_MULTI) != 0 ? XLHL_XMAX_IS_MULTI : 0) |
+		((infomask & HEAP_XMAX_LOCK_ONLY) != 0 ? XLHL_XMAX_LOCK_ONLY : 0) |
+		((infomask & HEAP_XMAX_EXCL_LOCK) != 0 ? XLHL_XMAX_EXCL_LOCK : 0) |
+	/* note we ignore HEAP_XMAX_SHR_LOCK here */
+		((infomask & HEAP_XMAX_KEYSHR_LOCK) != 0 ? XLHL_XMAX_KEYSHR_LOCK : 0) |
+		((infomask2 & HEAP_KEYS_UPDATED) != 0 ?
+		 XLHL_KEYS_UPDATED : 0);
+}
+
+
+
+/*
+ *	heap_delete - delete a tuple
+ *
+ * NB: do not call this directly unless you are prepared to deal with
+ * concurrent-update conditions.  Use simple_heap_delete instead.
+ *
+ *	relation - table to be modified (caller must hold suitable lock)
+ *	tid - TID of tuple to be deleted
+ *	cid - delete command ID (used for visibility test, and stored into
+ *		cmax if successful)
+ *	crosscheck - if not InvalidSnapshot, also check tuple against this
+ *	wait - true if should wait for any conflicting update to commit/abort
+ *	hufd - output parameter, filled in failure cases (see below)
+ *
+ * Normal, successful return value is HeapTupleMayBeUpdated, which
+ * actually means we did delete it.  Failure return codes are
+ * HeapTupleSelfUpdated, HeapTupleUpdated, or HeapTupleBeingUpdated
+ * (the last only possible if wait == false).
+ *
+ * In the failure cases, 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.
+ */
+HTSU_Result
+heap_delete(Relation relation, ItemPointer tid,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			HeapUpdateFailureData *hufd)
 {
+	HTSU_Result result;
 	TransactionId xid = GetCurrentTransactionId();
-	HeapTuple  *heaptuples;
-	int			i;
-	int			ndone;
-	char	   *scratch = NULL;
+	ItemId		lp;
+	HeapTupleData tp;
 	Page		page;
-	bool		needwal;
-	Size		saveFreeSpace;
-	bool		need_tuple_data = RelationIsLogicallyLogged(relation);
-	bool		need_cids = RelationIsAccessibleInLogicalDecoding(relation);
-
-	needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation);
-	saveFreeSpace = RelationGetTargetPageFreeSpace(relation,
-												   HEAP_DEFAULT_FILLFACTOR);
+	BlockNumber block;
+	Buffer		buffer;
+	Buffer		vmbuffer = InvalidBuffer;
+	TransactionId new_xmax;
+	uint16		new_infomask,
+				new_infomask2;
+	bool		have_tuple_lock = false;
+	bool		iscombo;
+	bool		all_visible_cleared = false;
+	HeapTuple	old_key_tuple = NULL;	/* replica identity of the tuple */
+	bool		old_key_copied = false;
 
-	/* Toast and set header data in all the tuples */
-	heaptuples = palloc(ntuples * sizeof(HeapTuple));
-	for (i = 0; i < ntuples; i++)
-		heaptuples[i] = heap_prepare_insert(relation, tuples[i],
-											xid, cid, options);
+	Assert(ItemPointerIsValid(tid));
 
 	/*
-	 * Allocate some memory to use for constructing the WAL record. Using
-	 * palloc() within a critical section is not safe, so we allocate this
-	 * beforehand.
+	 * Forbid this during a parallel operation, lest it allocate a combocid.
+	 * Other workers might need that combocid for visibility checks, and we
+	 * have no provision for broadcasting it to them.
 	 */
-	if (needwal)
-		scratch = palloc(BLCKSZ);
+	if (IsInParallelMode())
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+				 errmsg("cannot delete tuples during a parallel operation")));
+
+	block = ItemPointerGetBlockNumber(tid);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
 
 	/*
-	 * We're about to do the actual inserts -- but check for conflict first,
-	 * to minimize the possibility of having to roll back work we've just
-	 * done.
-	 *
-	 * A check here does not definitively prevent a serialization anomaly;
-	 * that check MUST be done at least past the point of acquiring an
-	 * exclusive buffer content lock on every buffer that will be affected,
-	 * and MAY be done after all inserts are reflected in the buffers and
-	 * those locks are released; otherwise there race condition.  Since
-	 * multiple buffers can be locked and unlocked in the loop below, and it
-	 * would not be feasible to identify and lock all of those buffers before
-	 * the loop, we must do a final check at the end.
-	 *
-	 * The check here could be omitted with no loss of correctness; it is
-	 * present strictly as an optimization.
-	 *
-	 * For heap inserts, we only need to check for table-level SSI locks. Our
-	 * new tuples can't possibly conflict with existing tuple locks, and heap
-	 * page locks are only consolidated versions of tuple locks; they do not
-	 * lock "gaps" as index page locks do.  So we don't need to specify a
-	 * buffer when making the call, which makes for a faster check.
-	 */
-	CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
-
-	ndone = 0;
-	while (ndone < ntuples)
-	{
-		Buffer		buffer;
-		Buffer		vmbuffer = InvalidBuffer;
-		bool		all_visible_cleared = false;
-		int			nthispage;
-
-		CHECK_FOR_INTERRUPTS();
-
-		/*
-		 * Find buffer where at least the next tuple will fit.  If the page is
-		 * all-visible, this will also pin the requisite visibility map page.
-		 */
-		buffer = RelationGetBufferForTuple(relation, heaptuples[ndone]->t_len,
-										   InvalidBuffer, options, bistate,
-										   &vmbuffer, NULL);
-		page = BufferGetPage(buffer);
-
-		/* NO EREPORT(ERROR) from here till changes are logged */
-		START_CRIT_SECTION();
-
-		/*
-		 * RelationGetBufferForTuple has ensured that the first tuple fits.
-		 * Put that on the page, and then as many other tuples as fit.
-		 */
-		RelationPutHeapTuple(relation, buffer, heaptuples[ndone], false);
-		for (nthispage = 1; ndone + nthispage < ntuples; nthispage++)
-		{
-			HeapTuple	heaptup = heaptuples[ndone + nthispage];
-
-			if (PageGetHeapFreeSpace(page) < MAXALIGN(heaptup->t_len) + saveFreeSpace)
-				break;
-
-			RelationPutHeapTuple(relation, buffer, heaptup, false);
-
-			/*
-			 * We don't use heap_multi_insert for catalog tuples yet, but
-			 * better be prepared...
-			 */
-			if (needwal && need_cids)
-				log_heap_new_cid(relation, heaptup);
-		}
-
-		if (PageIsAllVisible(page))
-		{
-			all_visible_cleared = true;
-			PageClearAllVisible(page);
-			visibilitymap_clear(relation,
-								BufferGetBlockNumber(buffer),
-								vmbuffer, VISIBILITYMAP_VALID_BITS);
-		}
-
-		/*
-		 * XXX Should we set PageSetPrunable on this page ? See heap_insert()
-		 */
-
-		MarkBufferDirty(buffer);
-
-		/* XLOG stuff */
-		if (needwal)
-		{
-			XLogRecPtr	recptr;
-			xl_heap_multi_insert *xlrec;
-			uint8		info = XLOG_HEAP2_MULTI_INSERT;
-			char	   *tupledata;
-			int			totaldatalen;
-			char	   *scratchptr = scratch;
-			bool		init;
-			int			bufflags = 0;
-
-			/*
-			 * If the page was previously empty, we can reinit the page
-			 * instead of restoring the whole thing.
-			 */
-			init = (ItemPointerGetOffsetNumber(&(heaptuples[ndone]->t_self)) == FirstOffsetNumber &&
-					PageGetMaxOffsetNumber(page) == FirstOffsetNumber + nthispage - 1);
-
-			/* allocate xl_heap_multi_insert struct from the scratch area */
-			xlrec = (xl_heap_multi_insert *) scratchptr;
-			scratchptr += SizeOfHeapMultiInsert;
-
-			/*
-			 * Allocate offsets array. Unless we're reinitializing the page,
-			 * in that case the tuples are stored in order starting at
-			 * FirstOffsetNumber and we don't need to store the offsets
-			 * explicitly.
-			 */
-			if (!init)
-				scratchptr += nthispage * sizeof(OffsetNumber);
-
-			/* the rest of the scratch space is used for tuple data */
-			tupledata = scratchptr;
-
-			xlrec->flags = all_visible_cleared ? XLH_INSERT_ALL_VISIBLE_CLEARED : 0;
-			xlrec->ntuples = nthispage;
-
-			/*
-			 * Write out an xl_multi_insert_tuple and the tuple data itself
-			 * for each tuple.
-			 */
-			for (i = 0; i < nthispage; i++)
-			{
-				HeapTuple	heaptup = heaptuples[ndone + i];
-				xl_multi_insert_tuple *tuphdr;
-				int			datalen;
-
-				if (!init)
-					xlrec->offsets[i] = ItemPointerGetOffsetNumber(&heaptup->t_self);
-				/* xl_multi_insert_tuple needs two-byte alignment. */
-				tuphdr = (xl_multi_insert_tuple *) SHORTALIGN(scratchptr);
-				scratchptr = ((char *) tuphdr) + SizeOfMultiInsertTuple;
-
-				tuphdr->t_infomask2 = heaptup->t_data->t_infomask2;
-				tuphdr->t_infomask = heaptup->t_data->t_infomask;
-				tuphdr->t_hoff = heaptup->t_data->t_hoff;
-
-				/* write bitmap [+ padding] [+ oid] + data */
-				datalen = heaptup->t_len - SizeofHeapTupleHeader;
-				memcpy(scratchptr,
-					   (char *) heaptup->t_data + SizeofHeapTupleHeader,
-					   datalen);
-				tuphdr->datalen = datalen;
-				scratchptr += datalen;
-			}
-			totaldatalen = scratchptr - tupledata;
-			Assert((scratchptr - scratch) < BLCKSZ);
-
-			if (need_tuple_data)
-				xlrec->flags |= XLH_INSERT_CONTAINS_NEW_TUPLE;
-
-			/*
-			 * Signal that this is the last xl_heap_multi_insert record
-			 * emitted by this call to heap_multi_insert(). Needed for logical
-			 * decoding so it knows when to cleanup temporary data.
-			 */
-			if (ndone + nthispage == ntuples)
-				xlrec->flags |= XLH_INSERT_LAST_IN_MULTI;
-
-			if (init)
-			{
-				info |= XLOG_HEAP_INIT_PAGE;
-				bufflags |= REGBUF_WILL_INIT;
-			}
-
-			/*
-			 * If we're doing logical decoding, include the new tuple data
-			 * even if we take a full-page image of the page.
-			 */
-			if (need_tuple_data)
-				bufflags |= REGBUF_KEEP_DATA;
-
-			XLogBeginInsert();
-			XLogRegisterData((char *) xlrec, tupledata - scratch);
-			XLogRegisterBuffer(0, buffer, REGBUF_STANDARD | bufflags);
-
-			XLogRegisterBufData(0, tupledata, totaldatalen);
-
-			/* filtering by origin on a row level is much more efficient */
-			XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
-
-			recptr = XLogInsert(RM_HEAP2_ID, info);
-
-			PageSetLSN(page, recptr);
-		}
-
-		END_CRIT_SECTION();
-
-		UnlockReleaseBuffer(buffer);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
-
-		ndone += nthispage;
-	}
-
-	/*
-	 * We're done with the actual inserts.  Check for conflicts again, to
-	 * ensure that all rw-conflicts in to these inserts are detected.  Without
-	 * this final check, a sequential scan of the heap may have locked the
-	 * table after the "before" check, missing one opportunity to detect the
-	 * conflict, and then scanned the table before the new tuples were there,
-	 * missing the other chance to detect the conflict.
-	 *
-	 * For heap inserts, we only need to check for table-level SSI locks. Our
-	 * new tuples can't possibly conflict with existing tuple locks, and heap
-	 * page locks are only consolidated versions of tuple locks; they do not
-	 * lock "gaps" as index page locks do.  So we don't need to specify a
-	 * buffer when making the call.
-	 */
-	CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
-
-	/*
-	 * If tuples are cachable, mark them for invalidation from the caches in
-	 * case we abort.  Note it is OK to do this after releasing the buffer,
-	 * because the heaptuples data structure is all in local memory, not in
-	 * the shared buffer.
-	 */
-	if (IsCatalogRelation(relation))
-	{
-		for (i = 0; i < ntuples; i++)
-			CacheInvalidateHeapTuple(relation, heaptuples[i], NULL);
-	}
-
-	/*
-	 * Copy t_self fields back to the caller's original tuples. This does
-	 * nothing for untoasted tuples (tuples[i] == heaptuples[i)], but it's
-	 * probably faster to always copy than check.
-	 */
-	for (i = 0; i < ntuples; i++)
-		tuples[i]->t_self = heaptuples[i]->t_self;
-
-	pgstat_count_heap_insert(relation, ntuples);
-}
-
-/*
- *	simple_heap_insert - insert a tuple
- *
- * Currently, this routine differs from heap_insert only in supplying
- * a default command ID and not allowing access to the speedup options.
- *
- * This should be used rather than using heap_insert directly in most places
- * where we are modifying system catalogs.
- */
-Oid
-simple_heap_insert(Relation relation, HeapTuple tup)
-{
-	return heap_insert(relation, tup, GetCurrentCommandId(true), 0, NULL);
-}
-
-/*
- * Given infomask/infomask2, compute the bits that must be saved in the
- * "infobits" field of xl_heap_delete, xl_heap_update, xl_heap_lock,
- * xl_heap_lock_updated WAL records.
- *
- * See fix_infomask_from_infobits.
- */
-static uint8
-compute_infobits(uint16 infomask, uint16 infomask2)
-{
-	return
-		((infomask & HEAP_XMAX_IS_MULTI) != 0 ? XLHL_XMAX_IS_MULTI : 0) |
-		((infomask & HEAP_XMAX_LOCK_ONLY) != 0 ? XLHL_XMAX_LOCK_ONLY : 0) |
-		((infomask & HEAP_XMAX_EXCL_LOCK) != 0 ? XLHL_XMAX_EXCL_LOCK : 0) |
-	/* note we ignore HEAP_XMAX_SHR_LOCK here */
-		((infomask & HEAP_XMAX_KEYSHR_LOCK) != 0 ? XLHL_XMAX_KEYSHR_LOCK : 0) |
-		((infomask2 & HEAP_KEYS_UPDATED) != 0 ?
-		 XLHL_KEYS_UPDATED : 0);
-}
-
-/*
- * Given two versions of the same t_infomask for a tuple, compare them and
- * return whether the relevant status for a tuple Xmax has changed.  This is
- * used after a buffer lock has been released and reacquired: we want to ensure
- * that the tuple state continues to be the same it was when we previously
- * examined it.
- *
- * Note the Xmax field itself must be compared separately.
- */
-static inline bool
-xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask)
-{
-	const uint16 interesting =
-	HEAP_XMAX_IS_MULTI | HEAP_XMAX_LOCK_ONLY | HEAP_LOCK_MASK;
-
-	if ((new_infomask & interesting) != (old_infomask & interesting))
-		return true;
-
-	return false;
-}
-
-/*
- *	heap_delete - delete a tuple
- *
- * NB: do not call this directly unless you are prepared to deal with
- * concurrent-update conditions.  Use simple_heap_delete instead.
- *
- *	relation - table to be modified (caller must hold suitable lock)
- *	tid - TID of tuple to be deleted
- *	cid - delete command ID (used for visibility test, and stored into
- *		cmax if successful)
- *	crosscheck - if not InvalidSnapshot, also check tuple against this
- *	wait - true if should wait for any conflicting update to commit/abort
- *	hufd - output parameter, filled in failure cases (see below)
- *
- * Normal, successful return value is HeapTupleMayBeUpdated, which
- * actually means we did delete it.  Failure return codes are
- * HeapTupleSelfUpdated, HeapTupleUpdated, or HeapTupleBeingUpdated
- * (the last only possible if wait == false).
- *
- * In the failure cases, 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.
- */
-HTSU_Result
-heap_delete(Relation relation, ItemPointer tid,
-			CommandId cid, Snapshot crosscheck, bool wait,
-			HeapUpdateFailureData *hufd)
-{
-	HTSU_Result result;
-	TransactionId xid = GetCurrentTransactionId();
-	ItemId		lp;
-	HeapTupleData tp;
-	Page		page;
-	BlockNumber block;
-	Buffer		buffer;
-	Buffer		vmbuffer = InvalidBuffer;
-	TransactionId new_xmax;
-	uint16		new_infomask,
-				new_infomask2;
-	bool		have_tuple_lock = false;
-	bool		iscombo;
-	bool		all_visible_cleared = false;
-	HeapTuple	old_key_tuple = NULL;	/* replica identity of the tuple */
-	bool		old_key_copied = false;
-
-	Assert(ItemPointerIsValid(tid));
-
-	/*
-	 * Forbid this during a parallel operation, lest it allocate a combocid.
-	 * Other workers might need that combocid for visibility checks, and we
-	 * have no provision for broadcasting it to them.
-	 */
-	if (IsInParallelMode())
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
-				 errmsg("cannot delete tuples during a parallel operation")));
-
-	block = ItemPointerGetBlockNumber(tid);
-	buffer = ReadBuffer(relation, block);
-	page = BufferGetPage(buffer);
-
-	/*
-	 * Before locking the buffer, pin the visibility map page if it appears to
-	 * be necessary.  Since we haven't got the lock yet, someone else might be
-	 * in the middle of changing this, so we'll need to recheck after we have
-	 * the lock.
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
 	 */
 	if (PageIsAllVisible(page))
 		visibilitymap_pin(relation, block, &vmbuffer);
@@ -4504,7 +3833,7 @@ simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup)
 /*
  * Return the MultiXactStatus corresponding to the given tuple lock mode.
  */
-static MultiXactStatus
+MultiXactStatus
 get_mxact_status_for_lock(LockTupleMode mode, bool is_update)
 {
 	int			retval;
@@ -4522,724 +3851,34 @@ get_mxact_status_for_lock(LockTupleMode mode, bool is_update)
 }
 
 /*
- *	heap_lock_tuple - lock a tuple in shared or exclusive mode
- *
- * Note that this acquires a buffer pin, which the caller must release.
- *
- * Input parameters:
- *	relation: relation containing tuple (caller must hold suitable lock)
- *	tuple->t_self: TID of tuple to lock (rest of struct need not be valid)
- *	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
- *	follow_updates: if true, follow the update chain to also lock descendant
- *		tuples.
- *
- * Output parameters:
- *	*tuple: all fields filled in
- *	*buffer: set to buffer holding tuple (pinned but not locked at exit)
- *	*hufd: filled in failure cases (see below)
- *
- * 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
+ * Acquire heavyweight lock on the given tuple, in preparation for acquiring
+ * its normal, Xmax-based tuple lock.
  *
- * 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.
+ * have_tuple_lock is an input and output parameter: on input, it indicates
+ * whether the lock has previously been acquired (and this function does
+ * nothing in that case).  If this function returns success, have_tuple_lock
+ * has been flipped to true.
  *
- * See README.tuplock for a thorough explanation of this mechanism.
+ * Returns false if it was unable to obtain the lock; this can only happen if
+ * wait_policy is Skip.
  */
-HTSU_Result
-heap_lock_tuple(Relation relation, HeapTuple tuple,
-				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
-				bool follow_updates,
-				Buffer *buffer, HeapUpdateFailureData *hufd)
+bool
+heap_acquire_tuplock(Relation relation, ItemPointer tid, LockTupleMode mode,
+					 LockWaitPolicy wait_policy, bool *have_tuple_lock)
 {
-	HTSU_Result result;
-	ItemPointer tid = &(tuple->t_self);
-	ItemId		lp;
-	Page		page;
-	Buffer		vmbuffer = InvalidBuffer;
-	BlockNumber block;
-	TransactionId xid,
-				xmax;
-	uint16		old_infomask,
-				new_infomask,
-				new_infomask2;
-	bool		first_time = true;
-	bool		have_tuple_lock = false;
-	bool		cleared_all_frozen = false;
-
-	*buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
-	block = ItemPointerGetBlockNumber(tid);
-
-	/*
-	 * Before locking the buffer, pin the visibility map page if it appears to
-	 * be necessary.  Since we haven't got the lock yet, someone else might be
-	 * in the middle of changing this, so we'll need to recheck after we have
-	 * the lock.
-	 */
-	if (PageIsAllVisible(BufferGetPage(*buffer)))
-		visibilitymap_pin(relation, block, &vmbuffer);
-
-	LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-	page = BufferGetPage(*buffer);
-	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
-	Assert(ItemIdIsNormal(lp));
+	if (*have_tuple_lock)
+		return true;
 
-	tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	tuple->t_len = ItemIdGetLength(lp);
-	tuple->t_tableOid = RelationGetRelid(relation);
+	switch (wait_policy)
+	{
+		case LockWaitBlock:
+			LockTupleTuplock(relation, tid, mode);
+			break;
 
-l3:
-	result = relation->rd_stamroutine->snapshot_satisfiesUpdate(tuple, cid, *buffer);
-
-	if (result == HeapTupleInvisible)
-	{
-		/*
-		 * This is possible, but only when locking a tuple for ON CONFLICT
-		 * UPDATE.  We return this value here rather than throwing an error in
-		 * order to give that case the opportunity to throw a more specific
-		 * error.
-		 */
-		result = HeapTupleInvisible;
-		goto out_locked;
-	}
-	else if (result == HeapTupleBeingUpdated || result == HeapTupleUpdated)
-	{
-		TransactionId xwait;
-		uint16		infomask;
-		uint16		infomask2;
-		bool		require_sleep;
-		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);
-
-		LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
-
-		/*
-		 * If any subtransaction of the current top transaction already holds
-		 * a lock as strong as or stronger than what we're requesting, we
-		 * effectively hold the desired lock already.  We *must* succeed
-		 * without trying to take the tuple lock, else we will deadlock
-		 * against anyone wanting to acquire a stronger lock.
-		 *
-		 * Note we only do this the first time we loop on the HTSU result;
-		 * there is no point in testing in subsequent passes, because
-		 * evidently our own transaction cannot have acquired a new lock after
-		 * the first time we checked.
-		 */
-		if (first_time)
-		{
-			first_time = false;
-
-			if (infomask & HEAP_XMAX_IS_MULTI)
-			{
-				int			i;
-				int			nmembers;
-				MultiXactMember *members;
-
-				/*
-				 * We don't need to allow old multixacts here; if that had
-				 * been the case, HeapTupleSatisfiesUpdate would have returned
-				 * MayBeUpdated and we wouldn't be here.
-				 */
-				nmembers =
-					GetMultiXactIdMembers(xwait, &members, false,
-										  HEAP_XMAX_IS_LOCKED_ONLY(infomask));
-
-				for (i = 0; i < nmembers; i++)
-				{
-					/* only consider members of our own transaction */
-					if (!TransactionIdIsCurrentTransactionId(members[i].xid))
-						continue;
-
-					if (TUPLOCK_from_mxstatus(members[i].status) >= mode)
-					{
-						pfree(members);
-						result = HeapTupleMayBeUpdated;
-						goto out_unlocked;
-					}
-				}
-
-				if (members)
-					pfree(members);
-			}
-			else if (TransactionIdIsCurrentTransactionId(xwait))
-			{
-				switch (mode)
-				{
-					case LockTupleKeyShare:
-						Assert(HEAP_XMAX_IS_KEYSHR_LOCKED(infomask) ||
-							   HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
-							   HEAP_XMAX_IS_EXCL_LOCKED(infomask));
-						result = HeapTupleMayBeUpdated;
-						goto out_unlocked;
-					case LockTupleShare:
-						if (HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
-							HEAP_XMAX_IS_EXCL_LOCKED(infomask))
-						{
-							result = HeapTupleMayBeUpdated;
-							goto out_unlocked;
-						}
-						break;
-					case LockTupleNoKeyExclusive:
-						if (HEAP_XMAX_IS_EXCL_LOCKED(infomask))
-						{
-							result = HeapTupleMayBeUpdated;
-							goto out_unlocked;
-						}
-						break;
-					case LockTupleExclusive:
-						if (HEAP_XMAX_IS_EXCL_LOCKED(infomask) &&
-							infomask2 & HEAP_KEYS_UPDATED)
-						{
-							result = HeapTupleMayBeUpdated;
-							goto out_unlocked;
-						}
-						break;
-				}
-			}
-		}
-
-		/*
-		 * Initially assume that we will have to wait for the locking
-		 * transaction(s) to finish.  We check various cases below in which
-		 * this can be turned off.
-		 */
-		require_sleep = true;
-		if (mode == LockTupleKeyShare)
-		{
-			/*
-			 * If we're requesting KeyShare, and there's no update present, we
-			 * don't need to wait.  Even if there is an update, we can still
-			 * continue if the key hasn't been modified.
-			 *
-			 * However, if there are updates, we need to walk the update chain
-			 * to mark future versions of the row as locked, too.  That way,
-			 * if somebody deletes that future version, we're protected
-			 * against the key going away.  This locking of future versions
-			 * could block momentarily, if a concurrent transaction is
-			 * deleting a key; or it could return a value to the effect that
-			 * the transaction deleting the key has already committed.  So we
-			 * do this before re-locking the buffer; otherwise this would be
-			 * prone to deadlocks.
-			 *
-			 * Note that the TID we're locking was grabbed before we unlocked
-			 * the buffer.  For it to change while we're not looking, the
-			 * other properties we're testing for below after re-locking the
-			 * buffer would also change, in which case we would restart this
-			 * loop above.
-			 */
-			if (!(infomask2 & HEAP_KEYS_UPDATED))
-			{
-				bool		updated;
-
-				updated = !HEAP_XMAX_IS_LOCKED_ONLY(infomask);
-
-				/*
-				 * If there are updates, follow the update chain; bail out if
-				 * that cannot be done.
-				 */
-				if (follow_updates && updated)
-				{
-					HTSU_Result res;
-
-					res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
-												  GetCurrentTransactionId(),
-												  mode);
-					if (res != HeapTupleMayBeUpdated)
-					{
-						result = res;
-						/* recovery code expects to have buffer lock held */
-						LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-						goto failed;
-					}
-				}
-
-				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-				/*
-				 * Make sure it's still an appropriate lock, else start over.
-				 * Also, if it wasn't updated before we released the lock, but
-				 * is updated now, we start over too; the reason is that we
-				 * 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) ||
-					 !updated))
-					goto l3;
-
-				/* Things look okay, so we can skip sleeping */
-				require_sleep = false;
-
-				/*
-				 * Note we allow Xmax to change here; other updaters/lockers
-				 * could have modified it before we grabbed the buffer lock.
-				 * However, this is not a problem, because with the recheck we
-				 * just did we ensure that they still don't conflict with the
-				 * lock we want.
-				 */
-			}
-		}
-		else if (mode == LockTupleShare)
-		{
-			/*
-			 * If we're requesting Share, we can similarly avoid sleeping if
-			 * there's no update and no exclusive lock present.
-			 */
-			if (HEAP_XMAX_IS_LOCKED_ONLY(infomask) &&
-				!HEAP_XMAX_IS_EXCL_LOCKED(infomask))
-			{
-				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-				/*
-				 * 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))
-					goto l3;
-				require_sleep = false;
-			}
-		}
-		else if (mode == LockTupleNoKeyExclusive)
-		{
-			/*
-			 * If we're requesting NoKeyExclusive, we might also be able to
-			 * avoid sleeping; just ensure that there no conflicting lock
-			 * already acquired.
-			 */
-			if (infomask & HEAP_XMAX_IS_MULTI)
-			{
-				if (!DoesMultiXactIdConflict((MultiXactId) xwait, infomask,
-											 mode))
-				{
-					/*
-					 * No conflict, but if the xmax changed under us 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),
-											 xwait))
-						goto l3;
-
-					/* otherwise, we're good */
-					require_sleep = false;
-				}
-			}
-			else if (HEAP_XMAX_IS_KEYSHR_LOCKED(infomask))
-			{
-				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-				/* if the xmax changed in the meantime, start over */
-				if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
-					!TransactionIdEquals(
-										 HeapTupleHeaderGetRawXmax(tuple->t_data),
-										 xwait))
-					goto l3;
-				/* otherwise, we're good */
-				require_sleep = false;
-			}
-		}
-
-		/*
-		 * As a check independent from those above, we can also avoid sleeping
-		 * if the current transaction is the sole locker of the tuple.  Note
-		 * that the strength of the lock already held is irrelevant; this is
-		 * not about recording the lock in Xmax (which will be done regardless
-		 * of this optimization, below).  Also, note that the cases where we
-		 * hold a lock stronger than we are requesting are already handled
-		 * above by not doing anything.
-		 *
-		 * Note we only deal with the non-multixact case here; MultiXactIdWait
-		 * is well equipped to deal with this situation on its own.
-		 */
-		if (require_sleep && !(infomask & HEAP_XMAX_IS_MULTI) &&
-			TransactionIdIsCurrentTransactionId(xwait))
-		{
-			/* ... 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),
-									 xwait))
-				goto l3;
-			Assert(HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask));
-			require_sleep = false;
-		}
-
-		/*
-		 * Time to sleep on the other transaction/multixact, if necessary.
-		 *
-		 * If the other transaction is an update that's already committed,
-		 * then sleeping cannot possibly do any good: if we're required to
-		 * sleep, get out to raise an error instead.
-		 *
-		 * By here, we either have already acquired the buffer exclusive lock,
-		 * 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)
-		{
-			LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-			goto failed;
-		}
-		else if (require_sleep)
-		{
-			/*
-			 * Acquire tuple lock to establish our priority for the tuple, or
-			 * die trying.  LockTuple will release us when we are next-in-line
-			 * for the tuple.  We must do this even if we are share-locking.
-			 *
-			 * If we are forced to "start over" below, we keep the tuple lock;
-			 * this arranges that we stay at the head of the line while
-			 * rechecking tuple state.
-			 */
-			if (!heap_acquire_tuplock(relation, tid, mode, wait_policy,
-									  &have_tuple_lock))
-			{
-				/*
-				 * This can only happen if wait_policy is Skip and the lock
-				 * couldn't be obtained.
-				 */
-				result = HeapTupleWouldBlock;
-				/* recovery code expects to have buffer lock held */
-				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-				goto failed;
-			}
-
-			if (infomask & HEAP_XMAX_IS_MULTI)
-			{
-				MultiXactStatus status = get_mxact_status_for_lock(mode, false);
-
-				/* We only ever lock tuples, never update them */
-				if (status >= MultiXactStatusNoKeyUpdate)
-					elog(ERROR, "invalid lock mode in heap_lock_tuple");
-
-				/* wait for multixact to end, or die trying  */
-				switch (wait_policy)
-				{
-					case LockWaitBlock:
-						MultiXactIdWait((MultiXactId) xwait, status, infomask,
-										relation, &tuple->t_self, XLTW_Lock, NULL);
-						break;
-					case LockWaitSkip:
-						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
-														status, infomask, relation,
-														NULL))
-						{
-							result = HeapTupleWouldBlock;
-							/* recovery code expects to have buffer lock held */
-							LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-							goto failed;
-						}
-						break;
-					case LockWaitError:
-						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
-														status, infomask, relation,
-														NULL))
-							ereport(ERROR,
-									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-									 errmsg("could not obtain lock on row in relation \"%s\"",
-											RelationGetRelationName(relation))));
-
-						break;
-				}
-
-				/*
-				 * Of course, the multixact might not be done here: if we're
-				 * requesting a light lock mode, other transactions with light
-				 * locks could still be alive, as well as locks owned by our
-				 * own xact or other subxacts of this backend.  We need to
-				 * preserve the surviving MultiXact members.  Note that it
-				 * isn't absolutely necessary in the latter case, but doing so
-				 * is simpler.
-				 */
-			}
-			else
-			{
-				/* wait for regular transaction to end, or die trying */
-				switch (wait_policy)
-				{
-					case LockWaitBlock:
-						XactLockTableWait(xwait, relation, &tuple->t_self,
-										  XLTW_Lock);
-						break;
-					case LockWaitSkip:
-						if (!ConditionalXactLockTableWait(xwait))
-						{
-							result = HeapTupleWouldBlock;
-							/* recovery code expects to have buffer lock held */
-							LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-							goto failed;
-						}
-						break;
-					case LockWaitError:
-						if (!ConditionalXactLockTableWait(xwait))
-							ereport(ERROR,
-									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-									 errmsg("could not obtain lock on row in relation \"%s\"",
-											RelationGetRelationName(relation))));
-						break;
-				}
-			}
-
-			/* if there are updates, follow the update chain */
-			if (follow_updates && !HEAP_XMAX_IS_LOCKED_ONLY(infomask))
-			{
-				HTSU_Result res;
-
-				res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
-											  GetCurrentTransactionId(),
-											  mode);
-				if (res != HeapTupleMayBeUpdated)
-				{
-					result = res;
-					/* recovery code expects to have buffer lock held */
-					LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-					goto failed;
-				}
-			}
-
-			LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-			/*
-			 * xwait is done, but if xwait had just locked the tuple then some
-			 * 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),
-									 xwait))
-				goto l3;
-
-			if (!(infomask & HEAP_XMAX_IS_MULTI))
-			{
-				/*
-				 * Otherwise check if it committed or aborted.  Note we cannot
-				 * be here if the tuple was only locked by somebody who didn't
-				 * conflict with us; that would have been handled above.  So
-				 * that transaction must necessarily be gone by now.  But
-				 * don't check for this in the multixact case, because some
-				 * locker transactions might still be running.
-				 */
-				UpdateXmaxHintBits(tuple->t_data, *buffer, xwait);
-			}
-		}
-
-		/* By here, we're certain that we hold buffer exclusive lock again */
-
-		/*
-		 * We may lock if previous xmax aborted, or if it committed but only
-		 * locked the tuple without updating it; or if we didn't have to wait
-		 * 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))
-			result = HeapTupleMayBeUpdated;
-		else
-			result = HeapTupleUpdated;
-	}
-
-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);
-		if (result == HeapTupleSelfUpdated)
-			hufd->cmax = HeapTupleHeaderGetCmax(tuple->t_data);
-		else
-			hufd->cmax = InvalidCommandId;
-		goto out_locked;
-	}
-
-	/*
-	 * If we didn't pin the visibility map page and the page has become all
-	 * visible while we were busy locking the buffer, or during some
-	 * subsequent window during which we had it unlocked, we'll have to unlock
-	 * and re-lock, to avoid holding the buffer lock across I/O.  That's a bit
-	 * unfortunate, especially since we'll now have to recheck whether the
-	 * tuple has been locked or updated under us, but hopefully it won't
-	 * happen very often.
-	 */
-	if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
-	{
-		LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
-		visibilitymap_pin(relation, block, &vmbuffer);
-		LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-		goto l3;
-	}
-
-	xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
-	old_infomask = tuple->t_data->t_infomask;
-
-	/*
-	 * If this is the first possibly-multixact-able operation in the current
-	 * transaction, set my per-backend OldestMemberMXactId setting. We can be
-	 * certain that the transaction will never become a member of any older
-	 * MultiXactIds than that.  (We have to do this even if we end up just
-	 * using our own TransactionId below, since some other backend could
-	 * incorporate our XID into a MultiXact immediately afterwards.)
-	 */
-	MultiXactIdSetOldestMember();
-
-	/*
-	 * Compute the new xmax and infomask to store into the tuple.  Note we do
-	 * 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,
-							  GetCurrentTransactionId(), mode, false,
-							  &xid, &new_infomask, &new_infomask2);
-
-	START_CRIT_SECTION();
-
-	/*
-	 * Store transaction information of xact locking the tuple.
-	 *
-	 * Note: Cmax is meaningless in this context, so don't set it; this avoids
-	 * possibly generating a useless combo CID.  Moreover, if we're locking a
-	 * previously updated tuple, it's important to preserve the Cmax.
-	 *
-	 * 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;
-	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
-		HeapTupleHeaderClearHotUpdated(tuple->t_data);
-	HeapTupleHeaderSetXmax(tuple->t_data, xid);
-
-	/*
-	 * Make sure there is no forward chain link in t_ctid.  Note that in the
-	 * cases where the tuple has been updated, we must not overwrite t_ctid,
-	 * because it was set by the updater.  Moreover, if the tuple has been
-	 * updated, we need to follow the update chain to lock the new versions of
-	 * the tuple as well.
-	 */
-	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
-		tuple->t_data->t_ctid = *tid;
-
-	/* Clear only the all-frozen bit on visibility map if needed */
-	if (PageIsAllVisible(page) &&
-		visibilitymap_clear(relation, block, vmbuffer,
-							VISIBILITYMAP_ALL_FROZEN))
-		cleared_all_frozen = true;
-
-
-	MarkBufferDirty(*buffer);
-
-	/*
-	 * XLOG stuff.  You might think that we don't need an XLOG record because
-	 * there is no state change worth restoring after a crash.  You would be
-	 * wrong however: we have just written either a TransactionId or a
-	 * MultiXactId that may never have been seen on disk before, and we need
-	 * to make sure that there are XLOG entries covering those ID numbers.
-	 * Else the same IDs might be re-used after a crash, which would be
-	 * disastrous if this page made it to disk before the crash.  Essentially
-	 * we have to enforce the WAL log-before-data rule even in this case.
-	 * (Also, in a PITR log-shipping or 2PC environment, we have to have XLOG
-	 * entries for everything anyway.)
-	 */
-	if (RelationNeedsWAL(relation))
-	{
-		xl_heap_lock xlrec;
-		XLogRecPtr	recptr;
-
-		XLogBeginInsert();
-		XLogRegisterBuffer(0, *buffer, REGBUF_STANDARD);
-
-		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
-		xlrec.locking_xid = xid;
-		xlrec.infobits_set = compute_infobits(new_infomask,
-											  tuple->t_data->t_infomask2);
-		xlrec.flags = cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
-		XLogRegisterData((char *) &xlrec, SizeOfHeapLock);
-
-		/* we don't decode row locks atm, so no need to log the origin */
-
-		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_LOCK);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	result = HeapTupleMayBeUpdated;
-
-out_locked:
-	LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
-
-out_unlocked:
-	if (BufferIsValid(vmbuffer))
-		ReleaseBuffer(vmbuffer);
-
-	/*
-	 * Don't update the visibility map here. Locking a tuple doesn't change
-	 * visibility info.
-	 */
-
-	/*
-	 * Now that we have successfully marked the tuple as locked, we can
-	 * release the lmgr tuple lock, if we had it.
-	 */
-	if (have_tuple_lock)
-		UnlockTupleTuplock(relation, tid, mode);
-
-	return result;
-}
-
-/*
- * Acquire heavyweight lock on the given tuple, in preparation for acquiring
- * its normal, Xmax-based tuple lock.
- *
- * have_tuple_lock is an input and output parameter: on input, it indicates
- * whether the lock has previously been acquired (and this function does
- * nothing in that case).  If this function returns success, have_tuple_lock
- * has been flipped to true.
- *
- * Returns false if it was unable to obtain the lock; this can only happen if
- * wait_policy is Skip.
- */
-static bool
-heap_acquire_tuplock(Relation relation, ItemPointer tid, LockTupleMode mode,
-					 LockWaitPolicy wait_policy, bool *have_tuple_lock)
-{
-	if (*have_tuple_lock)
-		return true;
-
-	switch (wait_policy)
-	{
-		case LockWaitBlock:
-			LockTupleTuplock(relation, tid, mode);
-			break;
-
-		case LockWaitSkip:
-			if (!ConditionalLockTupleTuplock(relation, tid, mode))
-				return false;
-			break;
+		case LockWaitSkip:
+			if (!ConditionalLockTupleTuplock(relation, tid, mode))
+				return false;
+			break;
 
 		case LockWaitError:
 			if (!ConditionalLockTupleTuplock(relation, tid, mode))
@@ -5272,7 +3911,7 @@ heap_acquire_tuplock(Relation relation, ItemPointer tid, LockTupleMode mode,
  * window, but it's still possible to end up creating an unnecessary
  * MultiXactId.  Fortunately this is harmless.
  */
-static void
+void
 compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
 						  uint16 old_infomask2, TransactionId add_to_xmax,
 						  LockTupleMode mode, bool is_update,
@@ -5319,916 +3958,226 @@ l5:
 					break;
 				case LockTupleNoKeyExclusive:
 					new_xmax = add_to_xmax;
-					new_infomask |= HEAP_XMAX_EXCL_LOCK;
-					break;
-				case LockTupleExclusive:
-					new_xmax = add_to_xmax;
-					new_infomask |= HEAP_XMAX_EXCL_LOCK;
-					new_infomask2 |= HEAP_KEYS_UPDATED;
-					break;
-				default:
-					new_xmax = InvalidTransactionId;	/* silence compiler */
-					elog(ERROR, "invalid lock mode");
-			}
-		}
-	}
-	else if (old_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		MultiXactStatus new_status;
-
-		/*
-		 * Currently we don't allow XMAX_COMMITTED to be set for multis, so
-		 * cross-check.
-		 */
-		Assert(!(old_infomask & HEAP_XMAX_COMMITTED));
-
-		/*
-		 * A multixact together with LOCK_ONLY set but neither lock bit set
-		 * (i.e. a pg_upgraded share locked tuple) cannot possibly be running
-		 * anymore.  This check is critical for databases upgraded by
-		 * pg_upgrade; both MultiXactIdIsRunning and MultiXactIdExpand assume
-		 * that such multis are never passed.
-		 */
-		if (HEAP_LOCKED_UPGRADED(old_infomask))
-		{
-			old_infomask &= ~HEAP_XMAX_IS_MULTI;
-			old_infomask |= HEAP_XMAX_INVALID;
-			goto l5;
-		}
-
-		/*
-		 * If the XMAX is already a MultiXactId, then we need to expand it to
-		 * include add_to_xmax; but if all the members were lockers and are
-		 * all gone, we can do away with the IS_MULTI bit and just set
-		 * add_to_xmax as the only locker/updater.  If all lockers are gone
-		 * and we have an updater that aborted, we can also do without a
-		 * multi.
-		 *
-		 * The cost of doing GetMultiXactIdMembers would be paid by
-		 * MultiXactIdExpand if we weren't to do this, so this check is not
-		 * incurring extra work anyhow.
-		 */
-		if (!MultiXactIdIsRunning(xmax, HEAP_XMAX_IS_LOCKED_ONLY(old_infomask)))
-		{
-			if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) ||
-				!TransactionIdDidCommit(MultiXactIdGetUpdateXid(xmax,
-																old_infomask)))
-			{
-				/*
-				 * Reset these bits and restart; otherwise fall through to
-				 * create a new multi below.
-				 */
-				old_infomask &= ~HEAP_XMAX_IS_MULTI;
-				old_infomask |= HEAP_XMAX_INVALID;
-				goto l5;
-			}
-		}
-
-		new_status = get_mxact_status_for_lock(mode, is_update);
-
-		new_xmax = MultiXactIdExpand((MultiXactId) xmax, add_to_xmax,
-									 new_status);
-		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
-	}
-	else if (old_infomask & HEAP_XMAX_COMMITTED)
-	{
-		/*
-		 * It's a committed update, so we need to preserve him as updater of
-		 * the tuple.
-		 */
-		MultiXactStatus status;
-		MultiXactStatus new_status;
-
-		if (old_infomask2 & HEAP_KEYS_UPDATED)
-			status = MultiXactStatusUpdate;
-		else
-			status = MultiXactStatusNoKeyUpdate;
-
-		new_status = get_mxact_status_for_lock(mode, is_update);
-
-		/*
-		 * since it's not running, it's obviously impossible for the old
-		 * updater to be identical to the current one, so we need not check
-		 * for that case as we do in the block above.
-		 */
-		new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
-		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
-	}
-	else if (TransactionIdIsInProgress(xmax))
-	{
-		/*
-		 * If the XMAX is a valid, in-progress TransactionId, then we need to
-		 * create a new MultiXactId that includes both the old locker or
-		 * updater and our own TransactionId.
-		 */
-		MultiXactStatus new_status;
-		MultiXactStatus old_status;
-		LockTupleMode old_mode;
-
-		if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
-		{
-			if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
-				old_status = MultiXactStatusForKeyShare;
-			else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
-				old_status = MultiXactStatusForShare;
-			else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
-			{
-				if (old_infomask2 & HEAP_KEYS_UPDATED)
-					old_status = MultiXactStatusForUpdate;
-				else
-					old_status = MultiXactStatusForNoKeyUpdate;
-			}
-			else
-			{
-				/*
-				 * LOCK_ONLY can be present alone only when a page has been
-				 * upgraded by pg_upgrade.  But in that case,
-				 * TransactionIdIsInProgress() should have returned false.  We
-				 * assume it's no longer locked in this case.
-				 */
-				elog(WARNING, "LOCK_ONLY found for Xid in progress %u", xmax);
-				old_infomask |= HEAP_XMAX_INVALID;
-				old_infomask &= ~HEAP_XMAX_LOCK_ONLY;
-				goto l5;
-			}
-		}
-		else
-		{
-			/* it's an update, but which kind? */
-			if (old_infomask2 & HEAP_KEYS_UPDATED)
-				old_status = MultiXactStatusUpdate;
-			else
-				old_status = MultiXactStatusNoKeyUpdate;
-		}
-
-		old_mode = TUPLOCK_from_mxstatus(old_status);
-
-		/*
-		 * If the lock to be acquired is for the same TransactionId as the
-		 * existing lock, there's an optimization possible: consider only the
-		 * strongest of both locks as the only one present, and restart.
-		 */
-		if (xmax == add_to_xmax)
-		{
-			/*
-			 * Note that it's not possible for the original tuple to be
-			 * updated: we wouldn't be here because the tuple would have been
-			 * invisible and we wouldn't try to update it.  As a subtlety,
-			 * this code can also run when traversing an update chain to lock
-			 * future versions of a tuple.  But we wouldn't be here either,
-			 * because the add_to_xmax would be different from the original
-			 * updater.
-			 */
-			Assert(HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
-
-			/* acquire the strongest of both */
-			if (mode < old_mode)
-				mode = old_mode;
-			/* mustn't touch is_update */
-
-			old_infomask |= HEAP_XMAX_INVALID;
-			goto l5;
-		}
-
-		/* otherwise, just fall back to creating a new multixact */
-		new_status = get_mxact_status_for_lock(mode, is_update);
-		new_xmax = MultiXactIdCreate(xmax, old_status,
-									 add_to_xmax, new_status);
-		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
-	}
-	else if (!HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) &&
-			 TransactionIdDidCommit(xmax))
-	{
-		/*
-		 * It's a committed update, so we gotta preserve him as updater of the
-		 * tuple.
-		 */
-		MultiXactStatus status;
-		MultiXactStatus new_status;
-
-		if (old_infomask2 & HEAP_KEYS_UPDATED)
-			status = MultiXactStatusUpdate;
-		else
-			status = MultiXactStatusNoKeyUpdate;
-
-		new_status = get_mxact_status_for_lock(mode, is_update);
-
-		/*
-		 * since it's not running, it's obviously impossible for the old
-		 * updater to be identical to the current one, so we need not check
-		 * for that case as we do in the block above.
-		 */
-		new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
-		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
-	}
-	else
-	{
-		/*
-		 * Can get here iff the locking/updating transaction was running when
-		 * the infomask was extracted from the tuple, but finished before
-		 * TransactionIdIsInProgress got to run.  Deal with it as if there was
-		 * no locker at all in the first place.
-		 */
-		old_infomask |= HEAP_XMAX_INVALID;
-		goto l5;
-	}
-
-	*result_infomask = new_infomask;
-	*result_infomask2 = new_infomask2;
-	*result_xmax = new_xmax;
-}
-
-/*
- * Subroutine for heap_lock_updated_tuple_rec.
- *
- * Given a hypothetical multixact status held by the transaction identified
- * with the given xid, does the current transaction need to wait, fail, or can
- * it continue if it wanted to acquire a lock of the given mode?  "needwait"
- * is set to true if waiting is necessary; if it can continue, then
- * HeapTupleMayBeUpdated is returned.  If the lock is already held by the
- * current transaction, return HeapTupleSelfUpdated.  In case of a conflict
- * with another transaction, a different HeapTupleSatisfiesUpdate return code
- * is returned.
- *
- * The held status is said to be hypothetical because it might correspond to a
- * lock held by a single Xid, i.e. not a real MultiXactId; we express it this
- * way for simplicity of API.
- */
-static HTSU_Result
-test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid,
-						   LockTupleMode mode, bool *needwait)
-{
-	MultiXactStatus wantedstatus;
-
-	*needwait = false;
-	wantedstatus = get_mxact_status_for_lock(mode, false);
-
-	/*
-	 * Note: we *must* check TransactionIdIsInProgress before
-	 * TransactionIdDidAbort/Commit; see comment at top of tqual.c for an
-	 * explanation.
-	 */
-	if (TransactionIdIsCurrentTransactionId(xid))
-	{
-		/*
-		 * The tuple has already been locked by our own transaction.  This is
-		 * very rare but can happen if multiple transactions are trying to
-		 * lock an ancient version of the same tuple.
-		 */
-		return HeapTupleSelfUpdated;
-	}
-	else if (TransactionIdIsInProgress(xid))
-	{
-		/*
-		 * If the locking transaction is running, what we do depends on
-		 * whether the lock modes conflict: if they do, then we must wait for
-		 * it to finish; otherwise we can fall through to lock this tuple
-		 * version without waiting.
-		 */
-		if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
-								LOCKMODE_from_mxstatus(wantedstatus)))
-		{
-			*needwait = true;
-		}
-
-		/*
-		 * If we set needwait above, then this value doesn't matter;
-		 * otherwise, this value signals to caller that it's okay to proceed.
-		 */
-		return HeapTupleMayBeUpdated;
-	}
-	else if (TransactionIdDidAbort(xid))
-		return HeapTupleMayBeUpdated;
-	else if (TransactionIdDidCommit(xid))
-	{
-		/*
-		 * The other transaction committed.  If it was only a locker, then the
-		 * lock is completely gone now and we can return success; but if it
-		 * was an update, then what we do depends on whether the two lock
-		 * modes conflict.  If they conflict, then we must report error to
-		 * caller. But if they don't, we can fall through to allow the current
-		 * transaction to lock the tuple.
-		 *
-		 * Note: the reason we worry about ISUPDATE here is because as soon as
-		 * a transaction ends, all its locks are gone and meaningless, and
-		 * thus we can ignore them; whereas its updates persist.  In the
-		 * TransactionIdIsInProgress case, above, we don't need to check
-		 * because we know the lock is still "alive" and thus a conflict needs
-		 * always be checked.
-		 */
-		if (!ISUPDATE_from_mxstatus(status))
-			return HeapTupleMayBeUpdated;
-
-		if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
-								LOCKMODE_from_mxstatus(wantedstatus)))
-			/* bummer */
-			return HeapTupleUpdated;
-
-		return HeapTupleMayBeUpdated;
-	}
-
-	/* Not in progress, not aborted, not committed -- must have crashed */
-	return HeapTupleMayBeUpdated;
-}
-
-
-/*
- * Recursive part of heap_lock_updated_tuple
- *
- * Fetch the tuple pointed to by tid in rel, and mark it as locked by the given
- * xid with the given mode; if this tuple is updated, recurse to lock the new
- * version as well.
- */
-static HTSU_Result
-heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid,
-							LockTupleMode mode)
-{
-	HTSU_Result result;
-	ItemPointerData tupid;
-	HeapTupleData mytup;
-	Buffer		buf;
-	uint16		new_infomask,
-				new_infomask2,
-				old_infomask,
-				old_infomask2;
-	TransactionId xmax,
-				new_xmax;
-	TransactionId priorXmax = InvalidTransactionId;
-	bool		cleared_all_frozen = false;
-	Buffer		vmbuffer = InvalidBuffer;
-	BlockNumber block;
-
-	ItemPointerCopy(tid, &tupid);
-
-	for (;;)
-	{
-		new_infomask = 0;
-		new_xmax = InvalidTransactionId;
-		block = ItemPointerGetBlockNumber(&tupid);
-		ItemPointerCopy(&tupid, &(mytup.t_self));
-
-		if (!heap_fetch(rel, SnapshotAny, &mytup, &buf, false, NULL))
-		{
-			/*
-			 * if we fail to find the updated version of the tuple, it's
-			 * because it was vacuumed/pruned away after its creator
-			 * transaction aborted.  So behave as if we got to the end of the
-			 * chain, and there's no further tuple to lock: return success to
-			 * caller.
-			 */
-			return HeapTupleMayBeUpdated;
-		}
-
-l4:
-		CHECK_FOR_INTERRUPTS();
-
-		/*
-		 * Before locking the buffer, pin the visibility map page if it
-		 * appears to be necessary.  Since we haven't got the lock yet,
-		 * someone else might be in the middle of changing this, so we'll need
-		 * to recheck after we have the lock.
-		 */
-		if (PageIsAllVisible(BufferGetPage(buf)))
-			visibilitymap_pin(rel, block, &vmbuffer);
-		else
-			vmbuffer = InvalidBuffer;
-
-		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
-
-		/*
-		 * If we didn't pin the visibility map page and the page has become
-		 * all visible while we were busy locking the buffer, we'll have to
-		 * unlock and re-lock, to avoid holding the buffer lock across I/O.
-		 * That's a bit unfortunate, but hopefully shouldn't happen often.
-		 */
-		if (vmbuffer == InvalidBuffer && PageIsAllVisible(BufferGetPage(buf)))
-		{
-			LockBuffer(buf, BUFFER_LOCK_UNLOCK);
-			visibilitymap_pin(rel, block, &vmbuffer);
-			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
-		}
-
-		/*
-		 * Check the tuple XMIN against prior XMAX, if any.  If we reached the
-		 * end of the chain, we're done, so return success.
-		 */
-		if (TransactionIdIsValid(priorXmax) &&
-			!TransactionIdEquals(HeapTupleHeaderGetXmin(mytup.t_data),
-								 priorXmax))
-		{
-			result = HeapTupleMayBeUpdated;
-			goto out_locked;
-		}
-
-		/*
-		 * Also check Xmin: if this tuple was created by an aborted
-		 * (sub)transaction, then we already locked the last live one in the
-		 * chain, thus we're done, so return success.
-		 */
-		if (TransactionIdDidAbort(HeapTupleHeaderGetXmin(mytup.t_data)))
-		{
-			UnlockReleaseBuffer(buf);
-			return HeapTupleMayBeUpdated;
-		}
-
-		old_infomask = mytup.t_data->t_infomask;
-		old_infomask2 = mytup.t_data->t_infomask2;
-		xmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
-
-		/*
-		 * If this tuple version has been updated or locked by some concurrent
-		 * transaction(s), what we do depends on whether our lock mode
-		 * conflicts with what those other transactions hold, and also on the
-		 * status of them.
-		 */
-		if (!(old_infomask & HEAP_XMAX_INVALID))
-		{
-			TransactionId rawxmax;
-			bool		needwait;
-
-			rawxmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
-			if (old_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				int			nmembers;
-				int			i;
-				MultiXactMember *members;
-
-				/*
-				 * We don't need a test for pg_upgrade'd tuples: this is only
-				 * applied to tuples after the first in an update chain.  Said
-				 * first tuple in the chain may well be locked-in-9.2-and-
-				 * pg_upgraded, but that one was already locked by our caller,
-				 * not us; and any subsequent ones cannot be because our
-				 * caller must necessarily have obtained a snapshot later than
-				 * the pg_upgrade itself.
-				 */
-				Assert(!HEAP_LOCKED_UPGRADED(mytup.t_data->t_infomask));
-
-				nmembers = GetMultiXactIdMembers(rawxmax, &members, false,
-												 HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
-				for (i = 0; i < nmembers; i++)
-				{
-					result = test_lockmode_for_conflict(members[i].status,
-														members[i].xid,
-														mode, &needwait);
-
-					/*
-					 * If the tuple was already locked by ourselves in a
-					 * previous iteration of this (say heap_lock_tuple was
-					 * forced to restart the locking loop because of a change
-					 * in xmax), then we hold the lock already on this tuple
-					 * version and we don't need to do anything; and this is
-					 * not an error condition either.  We just need to skip
-					 * this tuple and continue locking the next version in the
-					 * update chain.
-					 */
-					if (result == HeapTupleSelfUpdated)
-					{
-						pfree(members);
-						goto next;
-					}
-
-					if (needwait)
-					{
-						LockBuffer(buf, BUFFER_LOCK_UNLOCK);
-						XactLockTableWait(members[i].xid, rel,
-										  &mytup.t_self,
-										  XLTW_LockUpdated);
-						pfree(members);
-						goto l4;
-					}
-					if (result != HeapTupleMayBeUpdated)
-					{
-						pfree(members);
-						goto out_locked;
-					}
-				}
-				if (members)
-					pfree(members);
-			}
-			else
-			{
-				MultiXactStatus status;
-
-				/*
-				 * For a non-multi Xmax, we first need to compute the
-				 * corresponding MultiXactStatus by using the infomask bits.
-				 */
-				if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
-				{
-					if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
-						status = MultiXactStatusForKeyShare;
-					else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
-						status = MultiXactStatusForShare;
-					else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
-					{
-						if (old_infomask2 & HEAP_KEYS_UPDATED)
-							status = MultiXactStatusForUpdate;
-						else
-							status = MultiXactStatusForNoKeyUpdate;
-					}
-					else
-					{
-						/*
-						 * LOCK_ONLY present alone (a pg_upgraded tuple marked
-						 * as share-locked in the old cluster) shouldn't be
-						 * seen in the middle of an update chain.
-						 */
-						elog(ERROR, "invalid lock status in tuple");
-					}
-				}
-				else
-				{
-					/* it's an update, but which kind? */
-					if (old_infomask2 & HEAP_KEYS_UPDATED)
-						status = MultiXactStatusUpdate;
-					else
-						status = MultiXactStatusNoKeyUpdate;
-				}
-
-				result = test_lockmode_for_conflict(status, rawxmax, mode,
-													&needwait);
-
-				/*
-				 * If the tuple was already locked by ourselves in a previous
-				 * iteration of this (say heap_lock_tuple was forced to
-				 * restart the locking loop because of a change in xmax), then
-				 * we hold the lock already on this tuple version and we don't
-				 * need to do anything; and this is not an error condition
-				 * either.  We just need to skip this tuple and continue
-				 * locking the next version in the update chain.
-				 */
-				if (result == HeapTupleSelfUpdated)
-					goto next;
-
-				if (needwait)
-				{
-					LockBuffer(buf, BUFFER_LOCK_UNLOCK);
-					XactLockTableWait(rawxmax, rel, &mytup.t_self,
-									  XLTW_LockUpdated);
-					goto l4;
-				}
-				if (result != HeapTupleMayBeUpdated)
-				{
-					goto out_locked;
-				}
-			}
-		}
-
-		/* compute the new Xmax and infomask values for the tuple ... */
-		compute_new_xmax_infomask(xmax, old_infomask, mytup.t_data->t_infomask2,
-								  xid, mode, false,
-								  &new_xmax, &new_infomask, &new_infomask2);
-
-		if (PageIsAllVisible(BufferGetPage(buf)) &&
-			visibilitymap_clear(rel, block, vmbuffer,
-								VISIBILITYMAP_ALL_FROZEN))
-			cleared_all_frozen = true;
-
-		START_CRIT_SECTION();
-
-		/* ... and set them */
-		HeapTupleHeaderSetXmax(mytup.t_data, new_xmax);
-		mytup.t_data->t_infomask &= ~HEAP_XMAX_BITS;
-		mytup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
-		mytup.t_data->t_infomask |= new_infomask;
-		mytup.t_data->t_infomask2 |= new_infomask2;
-
-		MarkBufferDirty(buf);
-
-		/* XLOG stuff */
-		if (RelationNeedsWAL(rel))
-		{
-			xl_heap_lock_updated xlrec;
-			XLogRecPtr	recptr;
-			Page		page = BufferGetPage(buf);
-
-			XLogBeginInsert();
-			XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
-
-			xlrec.offnum = ItemPointerGetOffsetNumber(&mytup.t_self);
-			xlrec.xmax = new_xmax;
-			xlrec.infobits_set = compute_infobits(new_infomask, new_infomask2);
-			xlrec.flags =
-				cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
-
-			XLogRegisterData((char *) &xlrec, SizeOfHeapLockUpdated);
-
-			recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_LOCK_UPDATED);
-
-			PageSetLSN(page, recptr);
-		}
-
-		END_CRIT_SECTION();
-
-next:
-		/* if we find the end of update chain, we're done. */
-		if (mytup.t_data->t_infomask & HEAP_XMAX_INVALID ||
-			ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid) ||
-			HeapTupleHeaderIsOnlyLocked(mytup.t_data))
-		{
-			result = HeapTupleMayBeUpdated;
-			goto out_locked;
-		}
-
-		/* tail recursion */
-		priorXmax = HeapTupleHeaderGetUpdateXid(mytup.t_data);
-		ItemPointerCopy(&(mytup.t_data->t_ctid), &tupid);
-		UnlockReleaseBuffer(buf);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
-	}
-
-	result = HeapTupleMayBeUpdated;
-
-out_locked:
-	UnlockReleaseBuffer(buf);
-
-	if (vmbuffer != InvalidBuffer)
-		ReleaseBuffer(vmbuffer);
-
-	return result;
-
-}
-
-/*
- * heap_lock_updated_tuple
- *		Follow update chain when locking an updated tuple, acquiring locks (row
- *		marks) on the updated versions.
- *
- * The initial tuple is assumed to be already locked.
- *
- * This function doesn't check visibility, it just unconditionally marks the
- * tuple(s) as locked.  If any tuple in the updated chain is being deleted
- * concurrently (or updated with the key being modified), sleep until the
- * transaction doing it is finished.
- *
- * Note that we don't acquire heavyweight tuple locks on the tuples we walk
- * when we have to wait for other transactions to release them, as opposed to
- * what heap_lock_tuple does.  The reason is that having more than one
- * transaction walking the chain is probably uncommon enough that risk of
- * starvation is not likely: one of the preconditions for being here is that
- * the snapshot in use predates the update that created this tuple (because we
- * started at an earlier version of the tuple), but at the same time such a
- * transaction cannot be using repeatable read or serializable isolation
- * levels, because that would lead to a serializability failure.
- */
-static HTSU_Result
-heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid,
-						TransactionId xid, LockTupleMode mode)
-{
-	if (!ItemPointerEquals(&tuple->t_self, ctid))
-	{
-		/*
-		 * If this is the first possibly-multixact-able operation in the
-		 * current transaction, set my per-backend OldestMemberMXactId
-		 * setting. We can be certain that the transaction will never become a
-		 * member of any older MultiXactIds than that.  (We have to do this
-		 * even if we end up just using our own TransactionId below, since
-		 * some other backend could incorporate our XID into a MultiXact
-		 * immediately afterwards.)
-		 */
-		MultiXactIdSetOldestMember();
-
-		return heap_lock_updated_tuple_rec(rel, ctid, xid, mode);
-	}
-
-	/* nothing to lock */
-	return HeapTupleMayBeUpdated;
-}
-
-/*
- *	heap_finish_speculative - mark speculative insertion as successful
- *
- * To successfully finish a speculative insertion we have to clear speculative
- * token from tuple.  To do so the t_ctid field, which will contain a
- * speculative token value, is modified in place to point to the tuple itself,
- * which is characteristic of a newly inserted ordinary tuple.
- *
- * NB: It is not ok to commit without either finishing or aborting a
- * speculative insertion.  We could treat speculative tuples of committed
- * transactions implicitly as completed, but then we would have to be prepared
- * to deal with speculative tokens on committed tuples.  That wouldn't be
- * difficult - no-one looks at the ctid field of a tuple with invalid xmax -
- * but clearing the token at completion isn't very expensive either.
- * An explicit confirmation WAL record also makes logical decoding simpler.
- */
-void
-heap_finish_speculative(Relation relation, HeapTuple tuple)
-{
-	Buffer		buffer;
-	Page		page;
-	OffsetNumber offnum;
-	ItemId		lp = NULL;
-	HeapTupleHeader htup;
-
-	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&(tuple->t_self)));
-	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
-	page = (Page) BufferGetPage(buffer);
-
-	offnum = ItemPointerGetOffsetNumber(&(tuple->t_self));
-	if (PageGetMaxOffsetNumber(page) >= offnum)
-		lp = PageGetItemId(page, offnum);
-
-	if (PageGetMaxOffsetNumber(page) < offnum || !ItemIdIsNormal(lp))
-		elog(ERROR, "invalid lp");
-
-	htup = (HeapTupleHeader) PageGetItem(page, lp);
-
-	/* SpecTokenOffsetNumber should be distinguishable from any real offset */
-	StaticAssertStmt(MaxOffsetNumber < SpecTokenOffsetNumber,
-					 "invalid speculative token constant");
-
-	/* NO EREPORT(ERROR) from here till changes are logged */
-	START_CRIT_SECTION();
-
-	Assert(HeapTupleHeaderIsSpeculative(tuple->t_data));
-
-	MarkBufferDirty(buffer);
-
-	/*
-	 * Replace the speculative insertion token with a real t_ctid, pointing to
-	 * itself like it does on regular tuples.
-	 */
-	htup->t_ctid = tuple->t_self;
-
-	/* XLOG stuff */
-	if (RelationNeedsWAL(relation))
-	{
-		xl_heap_confirm xlrec;
-		XLogRecPtr	recptr;
-
-		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
-
-		XLogBeginInsert();
-
-		/* We want the same filtering on this as on a plain insert */
-		XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
-
-		XLogRegisterData((char *) &xlrec, SizeOfHeapConfirm);
-		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
-
-		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_CONFIRM);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	UnlockReleaseBuffer(buffer);
-}
-
-/*
- *	heap_abort_speculative - kill a speculatively inserted tuple
- *
- * Marks a tuple that was speculatively inserted in the same command as dead,
- * by setting its xmin as invalid.  That makes it immediately appear as dead
- * to all transactions, including our own.  In particular, it makes
- * HeapTupleSatisfiesDirty() regard the tuple as dead, so that another backend
- * inserting a duplicate key value won't unnecessarily wait for our whole
- * transaction to finish (it'll just wait for our speculative insertion to
- * finish).
- *
- * Killing the tuple prevents "unprincipled deadlocks", which are deadlocks
- * that arise due to a mutual dependency that is not user visible.  By
- * definition, unprincipled deadlocks cannot be prevented by the user
- * reordering lock acquisition in client code, because the implementation level
- * lock acquisitions are not under the user's direct control.  If speculative
- * inserters did not take this precaution, then under high concurrency they
- * could deadlock with each other, which would not be acceptable.
- *
- * This is somewhat redundant with heap_delete, but we prefer to have a
- * dedicated routine with stripped down requirements.  Note that this is also
- * used to delete the TOAST tuples created during speculative insertion.
- *
- * This routine does not affect logical decoding as it only looks at
- * confirmation records.
- */
-void
-heap_abort_speculative(Relation relation, HeapTuple tuple)
-{
-	TransactionId xid = GetCurrentTransactionId();
-	ItemPointer tid = &(tuple->t_self);
-	ItemId		lp;
-	HeapTupleData tp;
-	Page		page;
-	BlockNumber block;
-	Buffer		buffer;
-
-	Assert(ItemPointerIsValid(tid));
-
-	block = ItemPointerGetBlockNumber(tid);
-	buffer = ReadBuffer(relation, block);
-	page = BufferGetPage(buffer);
-
-	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
-
-	/*
-	 * Page can't be all visible, we just inserted into it, and are still
-	 * running.
-	 */
-	Assert(!PageIsAllVisible(page));
-
-	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
-	Assert(ItemIdIsNormal(lp));
-
-	tp.t_tableOid = RelationGetRelid(relation);
-	tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	tp.t_len = ItemIdGetLength(lp);
-	tp.t_self = *tid;
-
-	/*
-	 * Sanity check that the tuple really is a speculatively inserted tuple,
-	 * inserted by us.
-	 */
-	if (tp.t_data->t_choice.t_heap.t_xmin != xid)
-		elog(ERROR, "attempted to kill a tuple inserted by another transaction");
-	if (!(IsToastRelation(relation) || HeapTupleHeaderIsSpeculative(tp.t_data)))
-		elog(ERROR, "attempted to kill a non-speculative tuple");
-	Assert(!HeapTupleHeaderIsHeapOnly(tp.t_data));
+					new_infomask |= HEAP_XMAX_EXCL_LOCK;
+					break;
+				case LockTupleExclusive:
+					new_xmax = add_to_xmax;
+					new_infomask |= HEAP_XMAX_EXCL_LOCK;
+					new_infomask2 |= HEAP_KEYS_UPDATED;
+					break;
+				default:
+					new_xmax = InvalidTransactionId;	/* silence compiler */
+					elog(ERROR, "invalid lock mode");
+			}
+		}
+	}
+	else if (old_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		MultiXactStatus new_status;
 
-	/*
-	 * No need to check for serializable conflicts here.  There is never a
-	 * need for a combocid, either.  No need to extract replica identity, or
-	 * do anything special with infomask bits.
-	 */
+		/*
+		 * Currently we don't allow XMAX_COMMITTED to be set for multis, so
+		 * cross-check.
+		 */
+		Assert(!(old_infomask & HEAP_XMAX_COMMITTED));
 
-	START_CRIT_SECTION();
+		/*
+		 * A multixact together with LOCK_ONLY set but neither lock bit set
+		 * (i.e. a pg_upgraded share locked tuple) cannot possibly be running
+		 * anymore.  This check is critical for databases upgraded by
+		 * pg_upgrade; both MultiXactIdIsRunning and MultiXactIdExpand assume
+		 * that such multis are never passed.
+		 */
+		if (HEAP_LOCKED_UPGRADED(old_infomask))
+		{
+			old_infomask &= ~HEAP_XMAX_IS_MULTI;
+			old_infomask |= HEAP_XMAX_INVALID;
+			goto l5;
+		}
 
-	/*
-	 * The tuple will become DEAD immediately.  Flag that this page
-	 * immediately is a candidate for pruning by setting xmin to
-	 * RecentGlobalXmin.  That's not pretty, but it doesn't seem worth
-	 * inventing a nicer API for this.
-	 */
-	Assert(TransactionIdIsValid(RecentGlobalXmin));
-	PageSetPrunable(page, RecentGlobalXmin);
+		/*
+		 * If the XMAX is already a MultiXactId, then we need to expand it to
+		 * include add_to_xmax; but if all the members were lockers and are
+		 * all gone, we can do away with the IS_MULTI bit and just set
+		 * add_to_xmax as the only locker/updater.  If all lockers are gone
+		 * and we have an updater that aborted, we can also do without a
+		 * multi.
+		 *
+		 * The cost of doing GetMultiXactIdMembers would be paid by
+		 * MultiXactIdExpand if we weren't to do this, so this check is not
+		 * incurring extra work anyhow.
+		 */
+		if (!MultiXactIdIsRunning(xmax, HEAP_XMAX_IS_LOCKED_ONLY(old_infomask)))
+		{
+			if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) ||
+				!TransactionIdDidCommit(MultiXactIdGetUpdateXid(xmax,
+																old_infomask)))
+			{
+				/*
+				 * Reset these bits and restart; otherwise fall through to
+				 * create a new multi below.
+				 */
+				old_infomask &= ~HEAP_XMAX_IS_MULTI;
+				old_infomask |= HEAP_XMAX_INVALID;
+				goto l5;
+			}
+		}
 
-	/* store transaction information of xact deleting the tuple */
-	tp.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
-	tp.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+		new_status = get_mxact_status_for_lock(mode, is_update);
 
-	/*
-	 * Set the tuple header xmin to InvalidTransactionId.  This makes the
-	 * tuple immediately invisible everyone.  (In particular, to any
-	 * transactions waiting on the speculative token, woken up later.)
-	 */
-	HeapTupleHeaderSetXmin(tp.t_data, InvalidTransactionId);
+		new_xmax = MultiXactIdExpand((MultiXactId) xmax, add_to_xmax,
+									 new_status);
+		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
+	}
+	else if (old_infomask & HEAP_XMAX_COMMITTED)
+	{
+		/*
+		 * It's a committed update, so we need to preserve him as updater of
+		 * the tuple.
+		 */
+		MultiXactStatus status;
+		MultiXactStatus new_status;
 
-	/* Clear the speculative insertion token too */
-	tp.t_data->t_ctid = tp.t_self;
+		if (old_infomask2 & HEAP_KEYS_UPDATED)
+			status = MultiXactStatusUpdate;
+		else
+			status = MultiXactStatusNoKeyUpdate;
 
-	MarkBufferDirty(buffer);
+		new_status = get_mxact_status_for_lock(mode, is_update);
 
-	/*
-	 * XLOG stuff
-	 *
-	 * The WAL records generated here match heap_delete().  The same recovery
-	 * routines are used.
-	 */
-	if (RelationNeedsWAL(relation))
+		/*
+		 * since it's not running, it's obviously impossible for the old
+		 * updater to be identical to the current one, so we need not check
+		 * for that case as we do in the block above.
+		 */
+		new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
+		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
+	}
+	else if (TransactionIdIsInProgress(xmax))
 	{
-		xl_heap_delete xlrec;
-		XLogRecPtr	recptr;
+		/*
+		 * If the XMAX is a valid, in-progress TransactionId, then we need to
+		 * create a new MultiXactId that includes both the old locker or
+		 * updater and our own TransactionId.
+		 */
+		MultiXactStatus new_status;
+		MultiXactStatus old_status;
+		LockTupleMode old_mode;
 
-		xlrec.flags = XLH_DELETE_IS_SUPER;
-		xlrec.infobits_set = compute_infobits(tp.t_data->t_infomask,
-											  tp.t_data->t_infomask2);
-		xlrec.offnum = ItemPointerGetOffsetNumber(&tp.t_self);
-		xlrec.xmax = xid;
+		if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
+		{
+			if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
+				old_status = MultiXactStatusForKeyShare;
+			else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
+				old_status = MultiXactStatusForShare;
+			else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
+			{
+				if (old_infomask2 & HEAP_KEYS_UPDATED)
+					old_status = MultiXactStatusForUpdate;
+				else
+					old_status = MultiXactStatusForNoKeyUpdate;
+			}
+			else
+			{
+				/*
+				 * LOCK_ONLY can be present alone only when a page has been
+				 * upgraded by pg_upgrade.  But in that case,
+				 * TransactionIdIsInProgress() should have returned false.  We
+				 * assume it's no longer locked in this case.
+				 */
+				elog(WARNING, "LOCK_ONLY found for Xid in progress %u", xmax);
+				old_infomask |= HEAP_XMAX_INVALID;
+				old_infomask &= ~HEAP_XMAX_LOCK_ONLY;
+				goto l5;
+			}
+		}
+		else
+		{
+			/* it's an update, but which kind? */
+			if (old_infomask2 & HEAP_KEYS_UPDATED)
+				old_status = MultiXactStatusUpdate;
+			else
+				old_status = MultiXactStatusNoKeyUpdate;
+		}
 
-		XLogBeginInsert();
-		XLogRegisterData((char *) &xlrec, SizeOfHeapDelete);
-		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
+		old_mode = TUPLOCK_from_mxstatus(old_status);
+
+		/*
+		 * If the lock to be acquired is for the same TransactionId as the
+		 * existing lock, there's an optimization possible: consider only the
+		 * strongest of both locks as the only one present, and restart.
+		 */
+		if (xmax == add_to_xmax)
+		{
+			/*
+			 * Note that it's not possible for the original tuple to be
+			 * updated: we wouldn't be here because the tuple would have been
+			 * invisible and we wouldn't try to update it.  As a subtlety,
+			 * this code can also run when traversing an update chain to lock
+			 * future versions of a tuple.  But we wouldn't be here either,
+			 * because the add_to_xmax would be different from the original
+			 * updater.
+			 */
+			Assert(HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
 
-		/* No replica identity & replication origin logged */
+			/* acquire the strongest of both */
+			if (mode < old_mode)
+				mode = old_mode;
+			/* mustn't touch is_update */
 
-		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_DELETE);
+			old_infomask |= HEAP_XMAX_INVALID;
+			goto l5;
+		}
 
-		PageSetLSN(page, recptr);
+		/* otherwise, just fall back to creating a new multixact */
+		new_status = get_mxact_status_for_lock(mode, is_update);
+		new_xmax = MultiXactIdCreate(xmax, old_status,
+									 add_to_xmax, new_status);
+		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
 	}
+	else if (!HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) &&
+			 TransactionIdDidCommit(xmax))
+	{
+		/*
+		 * It's a committed update, so we gotta preserve him as updater of the
+		 * tuple.
+		 */
+		MultiXactStatus status;
+		MultiXactStatus new_status;
 
-	END_CRIT_SECTION();
+		if (old_infomask2 & HEAP_KEYS_UPDATED)
+			status = MultiXactStatusUpdate;
+		else
+			status = MultiXactStatusNoKeyUpdate;
 
-	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+		new_status = get_mxact_status_for_lock(mode, is_update);
 
-	if (HeapTupleHasExternal(&tp))
+		/*
+		 * since it's not running, it's obviously impossible for the old
+		 * updater to be identical to the current one, so we need not check
+		 * for that case as we do in the block above.
+		 */
+		new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
+		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
+	}
+	else
 	{
-		Assert(!IsToastRelation(relation));
-		toast_delete(relation, &tp, true);
+		/*
+		 * Can get here iff the locking/updating transaction was running when
+		 * the infomask was extracted from the tuple, but finished before
+		 * TransactionIdIsInProgress got to run.  Deal with it as if there was
+		 * no locker at all in the first place.
+		 */
+		old_infomask |= HEAP_XMAX_INVALID;
+		goto l5;
 	}
 
-	/*
-	 * Never need to mark tuple for invalidation, since catalogs don't support
-	 * speculative insertion
-	 */
+	*result_infomask = new_infomask;
+	*result_infomask2 = new_infomask2;
+	*result_xmax = new_xmax;
+}
 
-	/* Now we can release the buffer */
-	ReleaseBuffer(buffer);
 
-	/* count deletion, as we counted the insertion too */
-	pgstat_count_heap_delete(relation);
-}
 
 /*
  * heap_inplace_update - update a tuple "in place" (ie, overwrite it)
@@ -6993,7 +4942,7 @@ HeapTupleGetUpdateXid(HeapTupleHeader tuple)
  *
  * The passed infomask pairs up with the given multixact in the tuple header.
  */
-static bool
+bool
 DoesMultiXactIdConflict(MultiXactId multi, uint16 infomask,
 						LockTupleMode lockmode)
 {
@@ -7160,7 +5109,7 @@ Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
  * We return (in *remaining, if not NULL) the number of members that are still
  * running, including any (non-aborted) subtransactions of our own transaction.
  */
-static void
+void
 MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
 				Relation rel, ItemPointer ctid, XLTW_Oper oper,
 				int *remaining)
@@ -7182,7 +5131,7 @@ MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
  * We return (in *remaining, if not NULL) the number of members that are still
  * running, including any (non-aborted) subtransactions of our own transaction.
  */
-static bool
+bool
 ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
 						   uint16 infomask, Relation rel, int *remaining)
 {
@@ -7744,7 +5693,7 @@ log_heap_update(Relation reln, Buffer oldbuf,
  * This is only used in wal_level >= WAL_LEVEL_LOGICAL, and only for catalog
  * tuples.
  */
-static XLogRecPtr
+XLogRecPtr
 log_heap_new_cid(Relation relation, HeapTuple tup)
 {
 	xl_heap_new_cid xlrec;
@@ -9120,46 +7069,6 @@ heap2_redo(XLogReaderState *record)
 	}
 }
 
-/*
- *	heap_sync		- sync a heap, for use when no WAL has been written
- *
- * This forces the heap contents (including TOAST heap if any) down to disk.
- * If we skipped using WAL, and WAL is otherwise needed, we must force the
- * relation down to disk before it's safe to commit the transaction.  This
- * requires writing out any dirty buffers and then doing a forced fsync.
- *
- * Indexes are not touched.  (Currently, index operations associated with
- * the commands that use this are WAL-logged and so do not need fsync.
- * That behavior might change someday, but in any case it's likely that
- * any fsync decisions required would be per-index and hence not appropriate
- * to be done here.)
- */
-void
-heap_sync(Relation rel)
-{
-	/* non-WAL-logged tables never need fsync */
-	if (!RelationNeedsWAL(rel))
-		return;
-
-	/* main heap */
-	FlushRelationBuffers(rel);
-	/* FlushRelationBuffers will have opened rd_smgr */
-	smgrimmedsync(rel->rd_smgr, MAIN_FORKNUM);
-
-	/* FSM is not critical, don't bother syncing it */
-
-	/* toast heap, if any */
-	if (OidIsValid(rel->rd_rel->reltoastrelid))
-	{
-		Relation	toastrel;
-
-		toastrel = heap_open(rel->rd_rel->reltoastrelid, AccessShareLock);
-		FlushRelationBuffers(toastrel);
-		smgrimmedsync(toastrel->rd_smgr, MAIN_FORKNUM);
-		heap_close(toastrel, AccessShareLock);
-	}
-}
-
 /*
  * Mask a heap page before performing consistency checks on it.
  */
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 7d7ac759e3..a0e3272f67 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -50,7 +50,13 @@
 
 /* local functions */
 static bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
-
+static bool heap_fetch(Relation relation,
+					   ItemPointer tid,
+					   Snapshot snapshot,
+					   HeapTuple tuple,
+					   Buffer *userbuf,
+					   bool keep_buf,
+					   Relation stats_relation);
 /*-------------------------------------------------------------------------
  *
  * POSTGRES "time qualification" code, ie, tuple visibility rules.
@@ -1660,10 +1666,2149 @@ HeapTupleSatisfiesHistoricMVCC(StorageTuple stup, Snapshot snapshot,
 		return true;
 }
 
-Datum
-heapam_storage_handler(PG_FUNCTION_ARGS)
+
+/*
+ *	heap_fetch		- retrieve tuple with given tid
+ *
+ * On entry, tuple->t_self is the TID to fetch.  We pin the buffer holding
+ * the tuple, fill in the remaining fields of *tuple, and check the tuple
+ * against the specified snapshot.
+ *
+ * If successful (tuple found and passes snapshot time qual), then *userbuf
+ * is set to the buffer holding the tuple and TRUE is returned.  The caller
+ * must unpin the buffer when done with the tuple.
+ *
+ * If the tuple is not found (ie, item number references a deleted slot),
+ * then tuple->t_data is set to NULL and FALSE is returned.
+ *
+ * If the tuple is found but fails the time qual check, then FALSE is returned
+ * but tuple->t_data is left pointing to the tuple.
+ *
+ * keep_buf determines what is done with the buffer in the FALSE-result cases.
+ * When the caller specifies keep_buf = true, we retain the pin on the buffer
+ * and return it in *userbuf (so the caller must eventually unpin it); when
+ * keep_buf = false, the pin is released and *userbuf is set to InvalidBuffer.
+ *
+ * stats_relation is the relation to charge the heap_fetch operation against
+ * for statistical purposes.  (This could be the heap rel itself, an
+ * associated index, or NULL to not count the fetch at all.)
+ *
+ * heap_fetch does not follow HOT chains: only the exact TID requested will
+ * be fetched.
+ *
+ * It is somewhat inconsistent that we ereport() on invalid block number but
+ * return false on invalid item number.  There are a couple of reasons though.
+ * One is that the caller can relatively easily check the block number for
+ * validity, but cannot check the item number without reading the page
+ * himself.  Another is that when we are following a t_ctid link, we can be
+ * reasonably confident that the page number is valid (since VACUUM shouldn't
+ * truncate off the destination page without having killed the referencing
+ * tuple first), but the item number might well not be good.
+ */
+static bool
+heap_fetch(Relation relation,
+		   ItemPointer tid,
+		   Snapshot snapshot,
+		   HeapTuple tuple,
+		   Buffer *userbuf,
+		   bool keep_buf,
+		   Relation stats_relation)
 {
-	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
+	ItemId		lp;
+	Buffer		buffer;
+	Page		page;
+	OffsetNumber offnum;
+	bool		valid;
+
+	/*
+	 * Fetch and pin the appropriate page of the relation.
+	 */
+	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
+
+	/*
+	 * Need share lock on buffer to examine tuple commit status.
+	 */
+	LockBuffer(buffer, BUFFER_LOCK_SHARE);
+	page = BufferGetPage(buffer);
+	TestForOldSnapshot(snapshot, relation, page);
+
+	/*
+	 * We'd better check for out-of-range offnum in case of VACUUM since the
+	 * TID was obtained.
+	 */
+	offnum = ItemPointerGetOffsetNumber(tid);
+	if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
+	{
+		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+		if (keep_buf)
+			*userbuf = buffer;
+		else
+		{
+			ReleaseBuffer(buffer);
+			*userbuf = InvalidBuffer;
+		}
+		return false;
+	}
+
+	/*
+	 * get the item line pointer corresponding to the requested tid
+	 */
+	lp = PageGetItemId(page, offnum);
+
+	/*
+	 * Must check for deleted tuple.
+	 */
+	if (!ItemIdIsNormal(lp))
+	{
+		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+		if (keep_buf)
+			*userbuf = buffer;
+		else
+		{
+			ReleaseBuffer(buffer);
+			*userbuf = InvalidBuffer;
+		}
+		return false;
+	}
+
+	/*
+	 * 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);
+
+	/*
+	 * check time qualification of tuple, then release lock
+	 */
+	valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, tuple, snapshot, buffer);
+
+	if (valid)
+		PredicateLockTuple(relation, tuple, snapshot);
+
+	CheckForSerializableConflictOut(valid, relation, tuple, buffer, snapshot);
+
+	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+
+	if (valid)
+	{
+		/*
+		 * All checks passed, so return the tuple as valid. Caller is now
+		 * responsible for releasing the buffer.
+		 */
+		*userbuf = buffer;
+
+		/* Count the successful fetch against appropriate rel, if any */
+		if (stats_relation != NULL)
+			pgstat_count_heap_fetch(stats_relation);
+
+		return true;
+	}
+
+	/* Tuple failed time qual, but maybe caller wants to see it anyway. */
+	if (keep_buf)
+		*userbuf = buffer;
+	else
+	{
+		ReleaseBuffer(buffer);
+		*userbuf = InvalidBuffer;
+	}
+
+	return false;
+}
+
+
+
+
+
+/* ----------------------------------------------------------------
+ *				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)
+{
+	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);
+
+	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,
+				   CommandId cid, Snapshot crosscheck, bool wait,
+				   HeapUpdateFailureData *hufd, LockTupleMode *lockmode)
+{
+	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);
+
+	return result;
+}
+
+static void
+heapam_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);
+
+	offnum = ItemPointerGetOffsetNumber(&(tuple->t_self));
+	if (PageGetMaxOffsetNumber(page) >= offnum)
+		lp = PageGetItemId(page, offnum);
+
+	if (PageGetMaxOffsetNumber(page) < offnum || !ItemIdIsNormal(lp))
+		elog(ERROR, "invalid lp");
+
+	htup = (HeapTupleHeader) PageGetItem(page, lp);
+
+	/* SpecTokenOffsetNumber should be distinguishable from any real offset */
+	StaticAssertStmt(MaxOffsetNumber < SpecTokenOffsetNumber,
+					 "invalid speculative token constant");
+
+	/* NO EREPORT(ERROR) from here till changes are logged */
+	START_CRIT_SECTION();
+
+	Assert(HeapTupleHeaderIsSpeculative(tuple->t_data));
+
+	MarkBufferDirty(buffer);
+
+	/*
+	 * Replace the speculative insertion token with a real t_ctid, pointing to
+	 * itself like it does on regular tuples.
+	 */
+	htup->t_ctid = tuple->t_self;
+
+	/* XLOG stuff */
+	if (RelationNeedsWAL(relation))
+	{
+		xl_heap_confirm xlrec;
+		XLogRecPtr	recptr;
+
+		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
+
+		XLogBeginInsert();
+
+		/* We want the same filtering on this as on a plain insert */
+		XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
+
+		XLogRegisterData((char *) &xlrec, SizeOfHeapConfirm);
+		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
+
+		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_CONFIRM);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(buffer);
+	slot->tts_speculativeToken = 0;
+}
+
+static void
+heapam_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;
+	HeapTupleData tp;
+	Page		page;
+	BlockNumber block;
+	Buffer		buffer;
+
+	/*Assert(slot->tts_speculativeToken != 0); This needs some update in toast */
+	Assert(ItemPointerIsValid(tid));
+
+	block = ItemPointerGetBlockNumber(tid);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
+
+	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+
+	/*
+	 * Page can't be all visible, we just inserted into it, and are still
+	 * running.
+	 */
+	Assert(!PageIsAllVisible(page));
+
+	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
+	Assert(ItemIdIsNormal(lp));
+
+	tp.t_tableOid = RelationGetRelid(relation);
+	tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	tp.t_len = ItemIdGetLength(lp);
+	tp.t_self = *tid;
+
+	/*
+	 * Sanity check that the tuple really is a speculatively inserted tuple,
+	 * inserted by us.
+	 */
+	if (tp.t_data->t_choice.t_heap.t_xmin != xid)
+		elog(ERROR, "attempted to kill a tuple inserted by another transaction");
+	if (!(IsToastRelation(relation) || HeapTupleHeaderIsSpeculative(tp.t_data)))
+		elog(ERROR, "attempted to kill a non-speculative tuple");
+	Assert(!HeapTupleHeaderIsHeapOnly(tp.t_data));
+
+	/*
+	 * No need to check for serializable conflicts here.  There is never a
+	 * need for a combocid, either.  No need to extract replica identity, or
+	 * do anything special with infomask bits.
+	 */
+
+	START_CRIT_SECTION();
+
+	/*
+	 * The tuple will become DEAD immediately.  Flag that this page
+	 * immediately is a candidate for pruning by setting xmin to
+	 * RecentGlobalXmin.  That's not pretty, but it doesn't seem worth
+	 * inventing a nicer API for this.
+	 */
+	Assert(TransactionIdIsValid(RecentGlobalXmin));
+	PageSetPrunable(page, RecentGlobalXmin);
+
+	/* store transaction information of xact deleting the tuple */
+	tp.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+	tp.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+
+	/*
+	 * Set the tuple header xmin to InvalidTransactionId.  This makes the
+	 * tuple immediately invisible everyone.  (In particular, to any
+	 * transactions waiting on the speculative token, woken up later.)
+	 */
+	HeapTupleHeaderSetXmin(tp.t_data, InvalidTransactionId);
+
+	/* Clear the speculative insertion token too */
+	tp.t_data->t_ctid = tp.t_self;
+
+	MarkBufferDirty(buffer);
+
+	/*
+	 * XLOG stuff
+	 *
+	 * The WAL records generated here match heap_delete().  The same recovery
+	 * routines are used.
+	 */
+	if (RelationNeedsWAL(relation))
+	{
+		xl_heap_delete xlrec;
+		XLogRecPtr	recptr;
+
+		xlrec.flags = XLH_DELETE_IS_SUPER;
+		xlrec.infobits_set = compute_infobits(tp.t_data->t_infomask,
+											  tp.t_data->t_infomask2);
+		xlrec.offnum = ItemPointerGetOffsetNumber(&tp.t_self);
+		xlrec.xmax = xid;
+
+		XLogBeginInsert();
+		XLogRegisterData((char *) &xlrec, SizeOfHeapDelete);
+		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
+
+		/* No replica identity & replication origin logged */
+
+		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_DELETE);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+
+	if (HeapTupleHasExternal(&tp))
+	{
+		Assert(!IsToastRelation(relation));
+		toast_delete(relation, &tp, true);
+	}
+
+	/*
+	 * Never need to mark tuple for invalidation, since catalogs don't support
+	 * speculative insertion
+	 */
+
+	/* Now we can release the buffer */
+	ReleaseBuffer(buffer);
+
+	/* count deletion, as we counted the insertion too */
+	pgstat_count_heap_delete(relation);
+	slot->tts_speculativeToken = 0;
+}
+
+/*
+ *	heapam_multi_insert	- insert multiple tuple into a heap
+ *
+ * This is like heap_insert(), but inserts multiple tuples in one operation.
+ * That's faster than calling heap_insert() in a loop, because when multiple
+ * tuples can be inserted on a single page, we can write just a single WAL
+ * record covering all of them, and only need to lock/unlock the page once.
+ *
+ * Note: this leaks memory into the current memory context. You can create a
+ * temporary context before calling this, if that's a problem.
+ */
+static void
+heapam_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
+				  CommandId cid, int options, BulkInsertState bistate)
+{
+	TransactionId xid = GetCurrentTransactionId();
+	HeapTuple  *heaptuples;
+	int			i;
+	int			ndone;
+	char	   *scratch = NULL;
+	Page		page;
+	bool		needwal;
+	Size		saveFreeSpace;
+	bool		need_tuple_data = RelationIsLogicallyLogged(relation);
+	bool		need_cids = RelationIsAccessibleInLogicalDecoding(relation);
+
+	needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation);
+	saveFreeSpace = RelationGetTargetPageFreeSpace(relation,
+												   HEAP_DEFAULT_FILLFACTOR);
+
+	/* Toast and set header data in all the tuples */
+	heaptuples = palloc(ntuples * sizeof(HeapTuple));
+	for (i = 0; i < ntuples; i++)
+		heaptuples[i] = heap_prepare_insert(relation, tuples[i],
+											xid, cid, options);
+
+	/*
+	 * Allocate some memory to use for constructing the WAL record. Using
+	 * palloc() within a critical section is not safe, so we allocate this
+	 * beforehand.
+	 */
+	if (needwal)
+		scratch = palloc(BLCKSZ);
+
+	/*
+	 * We're about to do the actual inserts -- but check for conflict first,
+	 * to minimize the possibility of having to roll back work we've just
+	 * done.
+	 *
+	 * A check here does not definitively prevent a serialization anomaly;
+	 * that check MUST be done at least past the point of acquiring an
+	 * exclusive buffer content lock on every buffer that will be affected,
+	 * and MAY be done after all inserts are reflected in the buffers and
+	 * those locks are released; otherwise there race condition.  Since
+	 * multiple buffers can be locked and unlocked in the loop below, and it
+	 * would not be feasible to identify and lock all of those buffers before
+	 * the loop, we must do a final check at the end.
+	 *
+	 * The check here could be omitted with no loss of correctness; it is
+	 * present strictly as an optimization.
+	 *
+	 * For heap inserts, we only need to check for table-level SSI locks. Our
+	 * new tuples can't possibly conflict with existing tuple locks, and heap
+	 * page locks are only consolidated versions of tuple locks; they do not
+	 * lock "gaps" as index page locks do.  So we don't need to specify a
+	 * buffer when making the call, which makes for a faster check.
+	 */
+	CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
+
+	ndone = 0;
+	while (ndone < ntuples)
+	{
+		Buffer		buffer;
+		Buffer		vmbuffer = InvalidBuffer;
+		bool		all_visible_cleared = false;
+		int			nthispage;
+
+		CHECK_FOR_INTERRUPTS();
+
+		/*
+		 * Find buffer where at least the next tuple will fit.  If the page is
+		 * all-visible, this will also pin the requisite visibility map page.
+		 */
+		buffer = RelationGetBufferForTuple(relation, heaptuples[ndone]->t_len,
+										   InvalidBuffer, options, bistate,
+										   &vmbuffer, NULL);
+		page = BufferGetPage(buffer);
+
+		/* NO EREPORT(ERROR) from here till changes are logged */
+		START_CRIT_SECTION();
+
+		/*
+		 * RelationGetBufferForTuple has ensured that the first tuple fits.
+		 * Put that on the page, and then as many other tuples as fit.
+		 */
+		RelationPutHeapTuple(relation, buffer, heaptuples[ndone], false);
+		for (nthispage = 1; ndone + nthispage < ntuples; nthispage++)
+		{
+			HeapTuple	heaptup = heaptuples[ndone + nthispage];
+
+			if (PageGetHeapFreeSpace(page) < MAXALIGN(heaptup->t_len) + saveFreeSpace)
+				break;
+
+			RelationPutHeapTuple(relation, buffer, heaptup, false);
+
+			/*
+			 * We don't use heap_multi_insert for catalog tuples yet, but
+			 * better be prepared...
+			 */
+			if (needwal && need_cids)
+				log_heap_new_cid(relation, heaptup);
+		}
+
+		if (PageIsAllVisible(page))
+		{
+			all_visible_cleared = true;
+			PageClearAllVisible(page);
+			visibilitymap_clear(relation,
+								BufferGetBlockNumber(buffer),
+								vmbuffer, VISIBILITYMAP_VALID_BITS);
+		}
+
+		/*
+		 * XXX Should we set PageSetPrunable on this page ? See heap_insert()
+		 */
+
+		MarkBufferDirty(buffer);
+
+		/* XLOG stuff */
+		if (needwal)
+		{
+			XLogRecPtr	recptr;
+			xl_heap_multi_insert *xlrec;
+			uint8		info = XLOG_HEAP2_MULTI_INSERT;
+			char	   *tupledata;
+			int			totaldatalen;
+			char	   *scratchptr = scratch;
+			bool		init;
+			int			bufflags = 0;
+
+			/*
+			 * If the page was previously empty, we can reinit the page
+			 * instead of restoring the whole thing.
+			 */
+			init = (ItemPointerGetOffsetNumber(&(heaptuples[ndone]->t_self)) == FirstOffsetNumber &&
+					PageGetMaxOffsetNumber(page) == FirstOffsetNumber + nthispage - 1);
+
+			/* allocate xl_heap_multi_insert struct from the scratch area */
+			xlrec = (xl_heap_multi_insert *) scratchptr;
+			scratchptr += SizeOfHeapMultiInsert;
+
+			/*
+			 * Allocate offsets array. Unless we're reinitializing the page,
+			 * in that case the tuples are stored in order starting at
+			 * FirstOffsetNumber and we don't need to store the offsets
+			 * explicitly.
+			 */
+			if (!init)
+				scratchptr += nthispage * sizeof(OffsetNumber);
+
+			/* the rest of the scratch space is used for tuple data */
+			tupledata = scratchptr;
+
+			xlrec->flags = all_visible_cleared ? XLH_INSERT_ALL_VISIBLE_CLEARED : 0;
+			xlrec->ntuples = nthispage;
+
+			/*
+			 * Write out an xl_multi_insert_tuple and the tuple data itself
+			 * for each tuple.
+			 */
+			for (i = 0; i < nthispage; i++)
+			{
+				HeapTuple	heaptup = heaptuples[ndone + i];
+				xl_multi_insert_tuple *tuphdr;
+				int			datalen;
+
+				if (!init)
+					xlrec->offsets[i] = ItemPointerGetOffsetNumber(&heaptup->t_self);
+				/* xl_multi_insert_tuple needs two-byte alignment. */
+				tuphdr = (xl_multi_insert_tuple *) SHORTALIGN(scratchptr);
+				scratchptr = ((char *) tuphdr) + SizeOfMultiInsertTuple;
+
+				tuphdr->t_infomask2 = heaptup->t_data->t_infomask2;
+				tuphdr->t_infomask = heaptup->t_data->t_infomask;
+				tuphdr->t_hoff = heaptup->t_data->t_hoff;
+
+				/* write bitmap [+ padding] [+ oid] + data */
+				datalen = heaptup->t_len - SizeofHeapTupleHeader;
+				memcpy(scratchptr,
+					   (char *) heaptup->t_data + SizeofHeapTupleHeader,
+					   datalen);
+				tuphdr->datalen = datalen;
+				scratchptr += datalen;
+			}
+			totaldatalen = scratchptr - tupledata;
+			Assert((scratchptr - scratch) < BLCKSZ);
+
+			if (need_tuple_data)
+				xlrec->flags |= XLH_INSERT_CONTAINS_NEW_TUPLE;
+
+			/*
+			 * Signal that this is the last xl_heap_multi_insert record
+			 * emitted by this call to heap_multi_insert(). Needed for logical
+			 * decoding so it knows when to cleanup temporary data.
+			 */
+			if (ndone + nthispage == ntuples)
+				xlrec->flags |= XLH_INSERT_LAST_IN_MULTI;
+
+			if (init)
+			{
+				info |= XLOG_HEAP_INIT_PAGE;
+				bufflags |= REGBUF_WILL_INIT;
+			}
+
+			/*
+			 * If we're doing logical decoding, include the new tuple data
+			 * even if we take a full-page image of the page.
+			 */
+			if (need_tuple_data)
+				bufflags |= REGBUF_KEEP_DATA;
+
+			XLogBeginInsert();
+			XLogRegisterData((char *) xlrec, tupledata - scratch);
+			XLogRegisterBuffer(0, buffer, REGBUF_STANDARD | bufflags);
+
+			XLogRegisterBufData(0, tupledata, totaldatalen);
+
+			/* filtering by origin on a row level is much more efficient */
+			XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
+
+			recptr = XLogInsert(RM_HEAP2_ID, info);
+
+			PageSetLSN(page, recptr);
+		}
+
+		END_CRIT_SECTION();
+
+		UnlockReleaseBuffer(buffer);
+		if (vmbuffer != InvalidBuffer)
+			ReleaseBuffer(vmbuffer);
+
+		ndone += nthispage;
+	}
+
+	/*
+	 * We're done with the actual inserts.  Check for conflicts again, to
+	 * ensure that all rw-conflicts in to these inserts are detected.  Without
+	 * this final check, a sequential scan of the heap may have locked the
+	 * table after the "before" check, missing one opportunity to detect the
+	 * conflict, and then scanned the table before the new tuples were there,
+	 * missing the other chance to detect the conflict.
+	 *
+	 * For heap inserts, we only need to check for table-level SSI locks. Our
+	 * new tuples can't possibly conflict with existing tuple locks, and heap
+	 * page locks are only consolidated versions of tuple locks; they do not
+	 * lock "gaps" as index page locks do.  So we don't need to specify a
+	 * buffer when making the call.
+	 */
+	CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
+
+	/*
+	 * If tuples are cachable, mark them for invalidation from the caches in
+	 * case we abort.  Note it is OK to do this after releasing the buffer,
+	 * because the heaptuples data structure is all in local memory, not in
+	 * the shared buffer.
+	 */
+	if (IsCatalogRelation(relation))
+	{
+		for (i = 0; i < ntuples; i++)
+			CacheInvalidateHeapTuple(relation, heaptuples[i], NULL);
+	}
+
+	/*
+	 * Copy t_self fields back to the caller's original tuples. This does
+	 * nothing for untoasted tuples (tuples[i] == heaptuples[i)], but it's
+	 * probably faster to always copy than check.
+	 */
+	for (i = 0; i < ntuples; i++)
+		tuples[i]->t_self = heaptuples[i]->t_self;
+
+	pgstat_count_heap_insert(relation, ntuples);
+}
+
+/*
+ * Subroutine for heap_lock_updated_tuple_rec.
+ *
+ * Given a hypothetical multixact status held by the transaction identified
+ * with the given xid, does the current transaction need to wait, fail, or can
+ * it continue if it wanted to acquire a lock of the given mode?  "needwait"
+ * is set to true if waiting is necessary; if it can continue, then
+ * HeapTupleMayBeUpdated is returned.  If the lock is already held by the
+ * current transaction, return HeapTupleSelfUpdated.  In case of a conflict
+ * with another transaction, a different HeapTupleSatisfiesUpdate return code
+ * is returned.
+ *
+ * The held status is said to be hypothetical because it might correspond to a
+ * lock held by a single Xid, i.e. not a real MultiXactId; we express it this
+ * way for simplicity of API.
+ */
+static HTSU_Result
+test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid,
+						   LockTupleMode mode, bool *needwait)
+{
+	MultiXactStatus wantedstatus;
+
+	*needwait = false;
+	wantedstatus = get_mxact_status_for_lock(mode, false);
+
+	/*
+	 * Note: we *must* check TransactionIdIsInProgress before
+	 * TransactionIdDidAbort/Commit; see comment at top of tqual.c for an
+	 * explanation.
+	 */
+	if (TransactionIdIsCurrentTransactionId(xid))
+	{
+		/*
+		 * The tuple has already been locked by our own transaction.  This is
+		 * very rare but can happen if multiple transactions are trying to
+		 * lock an ancient version of the same tuple.
+		 */
+		return HeapTupleSelfUpdated;
+	}
+	else if (TransactionIdIsInProgress(xid))
+	{
+		/*
+		 * If the locking transaction is running, what we do depends on
+		 * whether the lock modes conflict: if they do, then we must wait for
+		 * it to finish; otherwise we can fall through to lock this tuple
+		 * version without waiting.
+		 */
+		if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
+								LOCKMODE_from_mxstatus(wantedstatus)))
+		{
+			*needwait = true;
+		}
+
+		/*
+		 * If we set needwait above, then this value doesn't matter;
+		 * otherwise, this value signals to caller that it's okay to proceed.
+		 */
+		return HeapTupleMayBeUpdated;
+	}
+	else if (TransactionIdDidAbort(xid))
+		return HeapTupleMayBeUpdated;
+	else if (TransactionIdDidCommit(xid))
+	{
+		/*
+		 * The other transaction committed.  If it was only a locker, then the
+		 * lock is completely gone now and we can return success; but if it
+		 * was an update, then what we do depends on whether the two lock
+		 * modes conflict.  If they conflict, then we must report error to
+		 * caller. But if they don't, we can fall through to allow the current
+		 * transaction to lock the tuple.
+		 *
+		 * Note: the reason we worry about ISUPDATE here is because as soon as
+		 * a transaction ends, all its locks are gone and meaningless, and
+		 * thus we can ignore them; whereas its updates persist.  In the
+		 * TransactionIdIsInProgress case, above, we don't need to check
+		 * because we know the lock is still "alive" and thus a conflict needs
+		 * always be checked.
+		 */
+		if (!ISUPDATE_from_mxstatus(status))
+			return HeapTupleMayBeUpdated;
+
+		if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
+								LOCKMODE_from_mxstatus(wantedstatus)))
+			/* bummer */
+			return HeapTupleUpdated;
+
+		return HeapTupleMayBeUpdated;
+	}
+
+	/* Not in progress, not aborted, not committed -- must have crashed */
+	return HeapTupleMayBeUpdated;
+}
+
+
+/*
+ * Recursive part of heap_lock_updated_tuple
+ *
+ * Fetch the tuple pointed to by tid in rel, and mark it as locked by the given
+ * xid with the given mode; if this tuple is updated, recurse to lock the new
+ * version as well.
+ */
+static HTSU_Result
+heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid,
+							LockTupleMode mode)
+{
+	HTSU_Result result;
+	ItemPointerData tupid;
+	HeapTupleData 	mytup;
+	Buffer		buf;
+	uint16		new_infomask,
+				new_infomask2,
+				old_infomask,
+				old_infomask2;
+	TransactionId xmax,
+				new_xmax;
+	TransactionId priorXmax = InvalidTransactionId;
+	bool		cleared_all_frozen = false;
+	Buffer		vmbuffer = InvalidBuffer;
+	BlockNumber block;
+
+	ItemPointerCopy(tid, &tupid);
+
+	for (;;)
+	{
+		new_infomask = 0;
+		new_xmax = InvalidTransactionId;
+		block = ItemPointerGetBlockNumber(&tupid);
+
+		if (!heap_fetch(rel, &tupid, SnapshotAny, &mytup, &buf, false, NULL))
+		{
+			/*
+			 * if we fail to find the updated version of the tuple, it's
+			 * because it was vacuumed/pruned away after its creator
+			 * transaction aborted.  So behave as if we got to the end of the
+			 * chain, and there's no further tuple to lock: return success to
+			 * caller.
+			 */
+			return HeapTupleMayBeUpdated;
+		}
+
+l4:
+		CHECK_FOR_INTERRUPTS();
+
+		/*
+		 * Before locking the buffer, pin the visibility map page if it
+		 * appears to be necessary.  Since we haven't got the lock yet,
+		 * someone else might be in the middle of changing this, so we'll need
+		 * to recheck after we have the lock.
+		 */
+		if (PageIsAllVisible(BufferGetPage(buf)))
+			visibilitymap_pin(rel, block, &vmbuffer);
+		else
+			vmbuffer = InvalidBuffer;
+
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+		/*
+		 * If we didn't pin the visibility map page and the page has become
+		 * all visible while we were busy locking the buffer, we'll have to
+		 * unlock and re-lock, to avoid holding the buffer lock across I/O.
+		 * That's a bit unfortunate, but hopefully shouldn't happen often.
+		 */
+		if (vmbuffer == InvalidBuffer && PageIsAllVisible(BufferGetPage(buf)))
+		{
+			LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+			visibilitymap_pin(rel, block, &vmbuffer);
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+		}
+
+		/*
+		 * Check the tuple XMIN against prior XMAX, if any.  If we reached the
+		 * end of the chain, we're done, so return success.
+		 */
+		if (TransactionIdIsValid(priorXmax) &&
+			!TransactionIdEquals(HeapTupleHeaderGetXmin(mytup.t_data),
+								 priorXmax))
+		{
+			result = HeapTupleMayBeUpdated;
+			goto out_locked;
+		}
+
+		/*
+		 * Also check Xmin: if this tuple was created by an aborted
+		 * (sub)transaction, then we already locked the last live one in the
+		 * chain, thus we're done, so return success.
+		 */
+		if (TransactionIdDidAbort(HeapTupleHeaderGetXmin(mytup.t_data)))
+		{
+			UnlockReleaseBuffer(buf);
+			return HeapTupleMayBeUpdated;
+		}
+
+		old_infomask = mytup.t_data->t_infomask;
+		old_infomask2 = mytup.t_data->t_infomask2;
+		xmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
+
+		/*
+		 * If this tuple version has been updated or locked by some concurrent
+		 * transaction(s), what we do depends on whether our lock mode
+		 * conflicts with what those other transactions hold, and also on the
+		 * status of them.
+		 */
+		if (!(old_infomask & HEAP_XMAX_INVALID))
+		{
+			TransactionId rawxmax;
+			bool		needwait;
+
+			rawxmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
+			if (old_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				int			nmembers;
+				int			i;
+				MultiXactMember *members;
+
+				/*
+				 * We don't need a test for pg_upgrade'd tuples: this is only
+				 * applied to tuples after the first in an update chain.  Said
+				 * first tuple in the chain may well be locked-in-9.2-and-
+				 * pg_upgraded, but that one was already locked by our caller,
+				 * not us; and any subsequent ones cannot be because our
+				 * caller must necessarily have obtained a snapshot later than
+				 * the pg_upgrade itself.
+				 */
+				Assert(!HEAP_LOCKED_UPGRADED(mytup.t_data->t_infomask));
+
+				nmembers = GetMultiXactIdMembers(rawxmax, &members, false,
+												 HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
+				for (i = 0; i < nmembers; i++)
+				{
+					result = test_lockmode_for_conflict(members[i].status,
+														members[i].xid,
+														mode, &needwait);
+
+					/*
+					 * If the tuple was already locked by ourselves in a
+					 * previous iteration of this (say heap_lock_tuple was
+					 * forced to restart the locking loop because of a change
+					 * in xmax), then we hold the lock already on this tuple
+					 * version and we don't need to do anything; and this is
+					 * not an error condition either.  We just need to skip
+					 * this tuple and continue locking the next version in the
+					 * update chain.
+					 */
+					if (result == HeapTupleSelfUpdated)
+					{
+						pfree(members);
+						goto next;
+					}
+
+					if (needwait)
+					{
+						LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+						XactLockTableWait(members[i].xid, rel,
+										  &mytup.t_self,
+										  XLTW_LockUpdated);
+						pfree(members);
+						goto l4;
+					}
+					if (result != HeapTupleMayBeUpdated)
+					{
+						pfree(members);
+						goto out_locked;
+					}
+				}
+				if (members)
+					pfree(members);
+			}
+			else
+			{
+				MultiXactStatus status;
+
+				/*
+				 * For a non-multi Xmax, we first need to compute the
+				 * corresponding MultiXactStatus by using the infomask bits.
+				 */
+				if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
+				{
+					if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
+						status = MultiXactStatusForKeyShare;
+					else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
+						status = MultiXactStatusForShare;
+					else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
+					{
+						if (old_infomask2 & HEAP_KEYS_UPDATED)
+							status = MultiXactStatusForUpdate;
+						else
+							status = MultiXactStatusForNoKeyUpdate;
+					}
+					else
+					{
+						/*
+						 * LOCK_ONLY present alone (a pg_upgraded tuple marked
+						 * as share-locked in the old cluster) shouldn't be
+						 * seen in the middle of an update chain.
+						 */
+						elog(ERROR, "invalid lock status in tuple");
+					}
+				}
+				else
+				{
+					/* it's an update, but which kind? */
+					if (old_infomask2 & HEAP_KEYS_UPDATED)
+						status = MultiXactStatusUpdate;
+					else
+						status = MultiXactStatusNoKeyUpdate;
+				}
+
+				result = test_lockmode_for_conflict(status, rawxmax, mode,
+													&needwait);
+
+				/*
+				 * If the tuple was already locked by ourselves in a previous
+				 * iteration of this (say heap_lock_tuple was forced to
+				 * restart the locking loop because of a change in xmax), then
+				 * we hold the lock already on this tuple version and we don't
+				 * need to do anything; and this is not an error condition
+				 * either.  We just need to skip this tuple and continue
+				 * locking the next version in the update chain.
+				 */
+				if (result == HeapTupleSelfUpdated)
+					goto next;
+
+				if (needwait)
+				{
+					LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+					XactLockTableWait(rawxmax, rel, &mytup.t_self,
+									  XLTW_LockUpdated);
+					goto l4;
+				}
+				if (result != HeapTupleMayBeUpdated)
+				{
+					goto out_locked;
+				}
+			}
+		}
+
+		/* compute the new Xmax and infomask values for the tuple ... */
+		compute_new_xmax_infomask(xmax, old_infomask, mytup.t_data->t_infomask2,
+								  xid, mode, false,
+								  &new_xmax, &new_infomask, &new_infomask2);
+
+		if (PageIsAllVisible(BufferGetPage(buf)) &&
+			visibilitymap_clear(rel, block, vmbuffer,
+								VISIBILITYMAP_ALL_FROZEN))
+			cleared_all_frozen = true;
+
+		START_CRIT_SECTION();
+
+		/* ... and set them */
+		HeapTupleHeaderSetXmax(mytup.t_data, new_xmax);
+		mytup.t_data->t_infomask &= ~HEAP_XMAX_BITS;
+		mytup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+		mytup.t_data->t_infomask |= new_infomask;
+		mytup.t_data->t_infomask2 |= new_infomask2;
+
+		MarkBufferDirty(buf);
+
+		/* XLOG stuff */
+		if (RelationNeedsWAL(rel))
+		{
+			xl_heap_lock_updated xlrec;
+			XLogRecPtr	recptr;
+			Page		page = BufferGetPage(buf);
+
+			XLogBeginInsert();
+			XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
+
+			xlrec.offnum = ItemPointerGetOffsetNumber(&mytup.t_self);
+			xlrec.xmax = new_xmax;
+			xlrec.infobits_set = compute_infobits(new_infomask, new_infomask2);
+			xlrec.flags =
+				cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
+
+			XLogRegisterData((char *) &xlrec, SizeOfHeapLockUpdated);
+
+			recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_LOCK_UPDATED);
+
+			PageSetLSN(page, recptr);
+		}
+
+		END_CRIT_SECTION();
+
+next:
+		/* if we find the end of update chain, we're done. */
+		if (mytup.t_data->t_infomask & HEAP_XMAX_INVALID ||
+			ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid) ||
+			HeapTupleHeaderIsOnlyLocked(mytup.t_data))
+		{
+			result = HeapTupleMayBeUpdated;
+			goto out_locked;
+		}
+
+		/* tail recursion */
+		priorXmax = HeapTupleHeaderGetUpdateXid(mytup.t_data);
+		ItemPointerCopy(&(mytup.t_data->t_ctid), &tupid);
+		UnlockReleaseBuffer(buf);
+		if (vmbuffer != InvalidBuffer)
+			ReleaseBuffer(vmbuffer);
+	}
+
+	result = HeapTupleMayBeUpdated;
+
+out_locked:
+	UnlockReleaseBuffer(buf);
+
+	if (vmbuffer != InvalidBuffer)
+		ReleaseBuffer(vmbuffer);
+
+	return result;
+
+}
+
+/*
+ * heap_lock_updated_tuple
+ *		Follow update chain when locking an updated tuple, acquiring locks (row
+ *		marks) on the updated versions.
+ *
+ * The initial tuple is assumed to be already locked.
+ *
+ * This function doesn't check visibility, it just unconditionally marks the
+ * tuple(s) as locked.  If any tuple in the updated chain is being deleted
+ * concurrently (or updated with the key being modified), sleep until the
+ * transaction doing it is finished.
+ *
+ * Note that we don't acquire heavyweight tuple locks on the tuples we walk
+ * when we have to wait for other transactions to release them, as opposed to
+ * what heap_lock_tuple does.  The reason is that having more than one
+ * transaction walking the chain is probably uncommon enough that risk of
+ * starvation is not likely: one of the preconditions for being here is that
+ * the snapshot in use predates the update that created this tuple (because we
+ * started at an earlier version of the tuple), but at the same time such a
+ * transaction cannot be using repeatable read or serializable isolation
+ * levels, because that would lead to a serializability failure.
+ */
+static HTSU_Result
+heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid,
+						TransactionId xid, LockTupleMode mode)
+{
+	if (!ItemPointerEquals(&tuple->t_self, ctid))
+	{
+		/*
+		 * If this is the first possibly-multixact-able operation in the
+		 * current transaction, set my per-backend OldestMemberMXactId
+		 * setting. We can be certain that the transaction will never become a
+		 * member of any older MultiXactIds than that.  (We have to do this
+		 * even if we end up just using our own TransactionId below, since
+		 * some other backend could incorporate our XID into a MultiXact
+		 * immediately afterwards.)
+		 */
+		MultiXactIdSetOldestMember();
+
+		return heap_lock_updated_tuple_rec(rel, ctid, xid, mode);
+	}
+
+	/* nothing to lock */
+	return HeapTupleMayBeUpdated;
+}
+
+
+/*
+ *	heapam_lock_tuple - lock a tuple in shared or exclusive mode
+ *
+ * Note that this acquires a buffer pin, which the caller must release.
+ *
+ * Input parameters:
+ *	relation: relation containing tuple (caller must hold suitable lock)
+ *	tuple->t_self: TID of tuple to lock (rest of struct need not be valid)
+ *	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
+ *	follow_updates: if true, follow the update chain to also lock descendant
+ *		tuples.
+ *
+ * Output parameters:
+ *	*tuple: all fields filled in
+ *	*buffer: set to buffer holding tuple (pinned but not locked at exit)
+ *	*hufd: filled in failure cases (see below)
+ *
+ * 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.
+ *
+ * See README.tuplock for a thorough explanation of this mechanism.
+ */
+static HTSU_Result
+heapam_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;
+	ItemId		lp;
+	Page		page;
+	Buffer		vmbuffer = InvalidBuffer;
+	BlockNumber block;
+	TransactionId xid,
+				xmax;
+	uint16		old_infomask,
+				new_infomask,
+				new_infomask2;
+	bool		first_time = true;
+	bool		have_tuple_lock = false;
+	bool		cleared_all_frozen = false;
+	HeapTupleData	tuple;
+	Buffer 		buf;
+
+	Assert(stuple != NULL);
+
+	buf = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
+	block = ItemPointerGetBlockNumber(tid);
+	*buffer = buf;
+
+	/*
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
+	 */
+	if (PageIsAllVisible(BufferGetPage(buf)))
+		visibilitymap_pin(relation, block, &vmbuffer);
+
+	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+	page = BufferGetPage(buf);
+	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);
+
+l3:
+	result = HeapTupleSatisfiesUpdate(&tuple, cid, buf);
+
+	if (result == HeapTupleInvisible)
+	{
+		/*
+		 * This is possible, but only when locking a tuple for ON CONFLICT
+		 * UPDATE.  We return this value here rather than throwing an error in
+		 * order to give that case the opportunity to throw a more specific
+		 * error.
+		 */
+		result = HeapTupleInvisible;
+		goto out_locked;
+	}
+	else if (result == HeapTupleBeingUpdated || result == HeapTupleUpdated)
+	{
+		TransactionId xwait;
+		uint16		infomask;
+		uint16		infomask2;
+		bool		require_sleep;
+		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);
+
+		LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+
+		/*
+		 * If any subtransaction of the current top transaction already holds
+		 * a lock as strong as or stronger than what we're requesting, we
+		 * effectively hold the desired lock already.  We *must* succeed
+		 * without trying to take the tuple lock, else we will deadlock
+		 * against anyone wanting to acquire a stronger lock.
+		 *
+		 * Note we only do this the first time we loop on the HTSU result;
+		 * there is no point in testing in subsequent passes, because
+		 * evidently our own transaction cannot have acquired a new lock after
+		 * the first time we checked.
+		 */
+		if (first_time)
+		{
+			first_time = false;
+
+			if (infomask & HEAP_XMAX_IS_MULTI)
+			{
+				int			i;
+				int			nmembers;
+				MultiXactMember *members;
+
+				/*
+				 * We don't need to allow old multixacts here; if that had
+				 * been the case, HeapTupleSatisfiesUpdate would have returned
+				 * MayBeUpdated and we wouldn't be here.
+				 */
+				nmembers =
+					GetMultiXactIdMembers(xwait, &members, false,
+										  HEAP_XMAX_IS_LOCKED_ONLY(infomask));
+
+				for (i = 0; i < nmembers; i++)
+				{
+					/* only consider members of our own transaction */
+					if (!TransactionIdIsCurrentTransactionId(members[i].xid))
+						continue;
+
+					if (TUPLOCK_from_mxstatus(members[i].status) >= mode)
+					{
+						pfree(members);
+						result = HeapTupleMayBeUpdated;
+						goto out_unlocked;
+					}
+				}
+
+				if (members)
+					pfree(members);
+			}
+			else if (TransactionIdIsCurrentTransactionId(xwait))
+			{
+				switch (mode)
+				{
+					case LockTupleKeyShare:
+						Assert(HEAP_XMAX_IS_KEYSHR_LOCKED(infomask) ||
+							   HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
+							   HEAP_XMAX_IS_EXCL_LOCKED(infomask));
+						result = HeapTupleMayBeUpdated;
+						goto out_unlocked;
+					case LockTupleShare:
+						if (HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
+							HEAP_XMAX_IS_EXCL_LOCKED(infomask))
+						{
+							result = HeapTupleMayBeUpdated;
+							goto out_unlocked;
+						}
+						break;
+					case LockTupleNoKeyExclusive:
+						if (HEAP_XMAX_IS_EXCL_LOCKED(infomask))
+						{
+							result = HeapTupleMayBeUpdated;
+							goto out_unlocked;
+						}
+						break;
+					case LockTupleExclusive:
+						if (HEAP_XMAX_IS_EXCL_LOCKED(infomask) &&
+							infomask2 & HEAP_KEYS_UPDATED)
+						{
+							result = HeapTupleMayBeUpdated;
+							goto out_unlocked;
+						}
+						break;
+				}
+			}
+		}
+
+		/*
+		 * Initially assume that we will have to wait for the locking
+		 * transaction(s) to finish.  We check various cases below in which
+		 * this can be turned off.
+		 */
+		require_sleep = true;
+		if (mode == LockTupleKeyShare)
+		{
+			/*
+			 * If we're requesting KeyShare, and there's no update present, we
+			 * don't need to wait.  Even if there is an update, we can still
+			 * continue if the key hasn't been modified.
+			 *
+			 * However, if there are updates, we need to walk the update chain
+			 * to mark future versions of the row as locked, too.  That way,
+			 * if somebody deletes that future version, we're protected
+			 * against the key going away.  This locking of future versions
+			 * could block momentarily, if a concurrent transaction is
+			 * deleting a key; or it could return a value to the effect that
+			 * the transaction deleting the key has already committed.  So we
+			 * do this before re-locking the buffer; otherwise this would be
+			 * prone to deadlocks.
+			 *
+			 * Note that the TID we're locking was grabbed before we unlocked
+			 * the buffer.  For it to change while we're not looking, the
+			 * other properties we're testing for below after re-locking the
+			 * buffer would also change, in which case we would restart this
+			 * loop above.
+			 */
+			if (!(infomask2 & HEAP_KEYS_UPDATED))
+			{
+				bool		updated;
+
+				updated = !HEAP_XMAX_IS_LOCKED_ONLY(infomask);
+
+				/*
+				 * If there are updates, follow the update chain; bail out if
+				 * that cannot be done.
+				 */
+				if (follow_updates && updated)
+				{
+					HTSU_Result res;
+
+					res = heap_lock_updated_tuple(relation, &tuple, &t_ctid,
+												  GetCurrentTransactionId(),
+												  mode);
+					if (res != HeapTupleMayBeUpdated)
+					{
+						result = res;
+						/* recovery code expects to have buffer lock held */
+						LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+						goto failed;
+					}
+				}
+
+				LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+				/*
+				 * Make sure it's still an appropriate lock, else start over.
+				 * Also, if it wasn't updated before we released the lock, but
+				 * is updated now, we start over too; the reason is that we
+				 * 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) ||
+					 !updated))
+					goto l3;
+
+				/* Things look okay, so we can skip sleeping */
+				require_sleep = false;
+
+				/*
+				 * Note we allow Xmax to change here; other updaters/lockers
+				 * could have modified it before we grabbed the buffer lock.
+				 * However, this is not a problem, because with the recheck we
+				 * just did we ensure that they still don't conflict with the
+				 * lock we want.
+				 */
+			}
+		}
+		else if (mode == LockTupleShare)
+		{
+			/*
+			 * If we're requesting Share, we can similarly avoid sleeping if
+			 * there's no update and no exclusive lock present.
+			 */
+			if (HEAP_XMAX_IS_LOCKED_ONLY(infomask) &&
+				!HEAP_XMAX_IS_EXCL_LOCKED(infomask))
+			{
+				LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+				/*
+				 * 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))
+					goto l3;
+				require_sleep = false;
+			}
+		}
+		else if (mode == LockTupleNoKeyExclusive)
+		{
+			/*
+			 * If we're requesting NoKeyExclusive, we might also be able to
+			 * avoid sleeping; just ensure that there no conflicting lock
+			 * already acquired.
+			 */
+			if (infomask & HEAP_XMAX_IS_MULTI)
+			{
+				if (!DoesMultiXactIdConflict((MultiXactId) xwait, infomask,
+											 mode))
+				{
+					/*
+					 * No conflict, but if the xmax changed under us in the
+					 * meantime, start over.
+					 */
+					LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+					if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
+						!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple.t_data),
+											 xwait))
+						goto l3;
+
+					/* otherwise, we're good */
+					require_sleep = false;
+				}
+			}
+			else if (HEAP_XMAX_IS_KEYSHR_LOCKED(infomask))
+			{
+				LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+				/* if the xmax changed in the meantime, start over */
+				if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
+					!TransactionIdEquals(
+										 HeapTupleHeaderGetRawXmax(tuple.t_data),
+										 xwait))
+					goto l3;
+				/* otherwise, we're good */
+				require_sleep = false;
+			}
+		}
+
+		/*
+		 * As a check independent from those above, we can also avoid sleeping
+		 * if the current transaction is the sole locker of the tuple.  Note
+		 * that the strength of the lock already held is irrelevant; this is
+		 * not about recording the lock in Xmax (which will be done regardless
+		 * of this optimization, below).  Also, note that the cases where we
+		 * hold a lock stronger than we are requesting are already handled
+		 * above by not doing anything.
+		 *
+		 * Note we only deal with the non-multixact case here; MultiXactIdWait
+		 * is well equipped to deal with this situation on its own.
+		 */
+		if (require_sleep && !(infomask & HEAP_XMAX_IS_MULTI) &&
+			TransactionIdIsCurrentTransactionId(xwait))
+		{
+			/* ... but if the xmax changed in the meantime, start over */
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+			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));
+			require_sleep = false;
+		}
+
+		/*
+		 * Time to sleep on the other transaction/multixact, if necessary.
+		 *
+		 * If the other transaction is an update that's already committed,
+		 * then sleeping cannot possibly do any good: if we're required to
+		 * sleep, get out to raise an error instead.
+		 *
+		 * By here, we either have already acquired the buffer exclusive lock,
+		 * 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)
+		{
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+			goto failed;
+		}
+		else if (require_sleep)
+		{
+			/*
+			 * Acquire tuple lock to establish our priority for the tuple, or
+			 * die trying.  LockTuple will release us when we are next-in-line
+			 * for the tuple.  We must do this even if we are share-locking.
+			 *
+			 * If we are forced to "start over" below, we keep the tuple lock;
+			 * this arranges that we stay at the head of the line while
+			 * rechecking tuple state.
+			 */
+			if (!heap_acquire_tuplock(relation, tid, mode, wait_policy,
+									  &have_tuple_lock))
+			{
+				/*
+				 * This can only happen if wait_policy is Skip and the lock
+				 * couldn't be obtained.
+				 */
+				result = HeapTupleWouldBlock;
+				/* recovery code expects to have buffer lock held */
+				LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+				goto failed;
+			}
+
+			if (infomask & HEAP_XMAX_IS_MULTI)
+			{
+				MultiXactStatus status = get_mxact_status_for_lock(mode, false);
+
+				/* We only ever lock tuples, never update them */
+				if (status >= MultiXactStatusNoKeyUpdate)
+					elog(ERROR, "invalid lock mode in heap_lock_tuple");
+
+				/* wait for multixact to end, or die trying  */
+				switch (wait_policy)
+				{
+					case LockWaitBlock:
+						MultiXactIdWait((MultiXactId) xwait, status, infomask,
+										relation, &tuple.t_self, XLTW_Lock, NULL);
+						break;
+					case LockWaitSkip:
+						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
+														status, infomask, relation,
+														NULL))
+						{
+							result = HeapTupleWouldBlock;
+							/* recovery code expects to have buffer lock held */
+							LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+							goto failed;
+						}
+						break;
+					case LockWaitError:
+						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
+														status, infomask, relation,
+														NULL))
+							ereport(ERROR,
+									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+									 errmsg("could not obtain lock on row in relation \"%s\"",
+											RelationGetRelationName(relation))));
+
+						break;
+				}
+
+				/*
+				 * Of course, the multixact might not be done here: if we're
+				 * requesting a light lock mode, other transactions with light
+				 * locks could still be alive, as well as locks owned by our
+				 * own xact or other subxacts of this backend.  We need to
+				 * preserve the surviving MultiXact members.  Note that it
+				 * isn't absolutely necessary in the latter case, but doing so
+				 * is simpler.
+				 */
+			}
+			else
+			{
+				/* wait for regular transaction to end, or die trying */
+				switch (wait_policy)
+				{
+					case LockWaitBlock:
+						XactLockTableWait(xwait, relation, &tuple.t_self,
+										  XLTW_Lock);
+						break;
+					case LockWaitSkip:
+						if (!ConditionalXactLockTableWait(xwait))
+						{
+							result = HeapTupleWouldBlock;
+							/* recovery code expects to have buffer lock held */
+							LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+							goto failed;
+						}
+						break;
+					case LockWaitError:
+						if (!ConditionalXactLockTableWait(xwait))
+							ereport(ERROR,
+									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+									 errmsg("could not obtain lock on row in relation \"%s\"",
+											RelationGetRelationName(relation))));
+						break;
+				}
+			}
+
+			/* if there are updates, follow the update chain */
+			if (follow_updates && !HEAP_XMAX_IS_LOCKED_ONLY(infomask))
+			{
+				HTSU_Result res;
+
+				res = heap_lock_updated_tuple(relation, &tuple, &t_ctid,
+											  GetCurrentTransactionId(),
+											  mode);
+				if (res != HeapTupleMayBeUpdated)
+				{
+					result = res;
+					/* recovery code expects to have buffer lock held */
+					LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+					goto failed;
+				}
+			}
+
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+			/*
+			 * xwait is done, but if xwait had just locked the tuple then some
+			 * 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),
+									 xwait))
+				goto l3;
+
+			if (!(infomask & HEAP_XMAX_IS_MULTI))
+			{
+				/*
+				 * Otherwise check if it committed or aborted.  Note we cannot
+				 * be here if the tuple was only locked by somebody who didn't
+				 * conflict with us; that would have been handled above.  So
+				 * that transaction must necessarily be gone by now.  But
+				 * don't check for this in the multixact case, because some
+				 * locker transactions might still be running.
+				 */
+				UpdateXmaxHintBits(tuple.t_data, buf, xwait);
+			}
+		}
+
+		/* By here, we're certain that we hold buffer exclusive lock again */
+
+		/*
+		 * We may lock if previous xmax aborted, or if it committed but only
+		 * locked the tuple without updating it; or if we didn't have to wait
+		 * 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))
+			result = HeapTupleMayBeUpdated;
+		else
+			result = HeapTupleUpdated;
+	}
+
+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);
+		if (result == HeapTupleSelfUpdated)
+			hufd->cmax = HeapTupleHeaderGetCmax(tuple.t_data);
+		else
+			hufd->cmax = InvalidCommandId;
+		goto out_locked;
+	}
+
+	/*
+	 * If we didn't pin the visibility map page and the page has become all
+	 * visible while we were busy locking the buffer, or during some
+	 * subsequent window during which we had it unlocked, we'll have to unlock
+	 * and re-lock, to avoid holding the buffer lock across I/O.  That's a bit
+	 * unfortunate, especially since we'll now have to recheck whether the
+	 * tuple has been locked or updated under us, but hopefully it won't
+	 * happen very often.
+	 */
+	if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
+	{
+		LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+		visibilitymap_pin(relation, block, &vmbuffer);
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+		goto l3;
+	}
+
+	xmax = HeapTupleHeaderGetRawXmax(tuple.t_data);
+	old_infomask = tuple.t_data->t_infomask;
+
+	/*
+	 * If this is the first possibly-multixact-able operation in the current
+	 * transaction, set my per-backend OldestMemberMXactId setting. We can be
+	 * certain that the transaction will never become a member of any older
+	 * MultiXactIds than that.  (We have to do this even if we end up just
+	 * using our own TransactionId below, since some other backend could
+	 * incorporate our XID into a MultiXact immediately afterwards.)
+	 */
+	MultiXactIdSetOldestMember();
+
+	/*
+	 * Compute the new xmax and infomask to store into the tuple.  Note we do
+	 * 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,
+							  GetCurrentTransactionId(), mode, false,
+							  &xid, &new_infomask, &new_infomask2);
+
+	START_CRIT_SECTION();
+
+	/*
+	 * Store transaction information of xact locking the tuple.
+	 *
+	 * Note: Cmax is meaningless in this context, so don't set it; this avoids
+	 * possibly generating a useless combo CID.  Moreover, if we're locking a
+	 * previously updated tuple, it's important to preserve the Cmax.
+	 *
+	 * 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;
+	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
+		HeapTupleHeaderClearHotUpdated(tuple.t_data);
+	HeapTupleHeaderSetXmax(tuple.t_data, xid);
+
+	/*
+	 * Make sure there is no forward chain link in t_ctid.  Note that in the
+	 * cases where the tuple has been updated, we must not overwrite t_ctid,
+	 * because it was set by the updater.  Moreover, if the tuple has been
+	 * updated, we need to follow the update chain to lock the new versions of
+	 * the tuple as well.
+	 */
+	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
+		tuple.t_data->t_ctid = *tid;
+
+	/* Clear only the all-frozen bit on visibility map if needed */
+	if (PageIsAllVisible(page) &&
+		visibilitymap_clear(relation, block, vmbuffer,
+							VISIBILITYMAP_ALL_FROZEN))
+		cleared_all_frozen = true;
+
+
+	MarkBufferDirty(buf);
+
+	/*
+	 * XLOG stuff.  You might think that we don't need an XLOG record because
+	 * there is no state change worth restoring after a crash.  You would be
+	 * wrong however: we have just written either a TransactionId or a
+	 * MultiXactId that may never have been seen on disk before, and we need
+	 * to make sure that there are XLOG entries covering those ID numbers.
+	 * Else the same IDs might be re-used after a crash, which would be
+	 * disastrous if this page made it to disk before the crash.  Essentially
+	 * we have to enforce the WAL log-before-data rule even in this case.
+	 * (Also, in a PITR log-shipping or 2PC environment, we have to have XLOG
+	 * entries for everything anyway.)
+	 */
+	if (RelationNeedsWAL(relation))
+	{
+		xl_heap_lock xlrec;
+		XLogRecPtr	recptr;
+
+		XLogBeginInsert();
+		XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
+
+		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple.t_self);
+		xlrec.locking_xid = xid;
+		xlrec.infobits_set = compute_infobits(new_infomask,
+											  tuple.t_data->t_infomask2);
+		xlrec.flags = cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
+		XLogRegisterData((char *) &xlrec, SizeOfHeapLock);
+
+		/* we don't decode row locks atm, so no need to log the origin */
+
+		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_LOCK);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	result = HeapTupleMayBeUpdated;
+
+out_locked:
+	LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+
+out_unlocked:
+	if (BufferIsValid(vmbuffer))
+		ReleaseBuffer(vmbuffer);
+
+	/*
+	 * Don't update the visibility map here. Locking a tuple doesn't change
+	 * visibility info.
+	 */
+
+	/*
+	 * Now that we have successfully marked the tuple as locked, we can
+	 * release the lmgr tuple lock, if we had it.
+	 */
+	if (have_tuple_lock)
+		UnlockTupleTuplock(relation, tid, mode);
+
+	*stuple = heap_copytuple(&tuple);
+	return result;
+}
+
+/*
+ *	heapam_get_latest_tid -  get the latest tid of a specified tuple
+ *
+ * Actually, this gets the latest version that is visible according to
+ * the passed snapshot.  You can pass SnapshotDirty to get the very latest,
+ * possibly uncommitted version.
+ *
+ * *tid is both an input and an output parameter: it is updated to
+ * show the latest version of the row.  Note that it will not be changed
+ * if no version of the row passes the snapshot test.
+ */
+static void
+heapam_get_latest_tid(Relation relation,
+					Snapshot snapshot,
+					ItemPointer tid)
+{
+	BlockNumber blk;
+	ItemPointerData ctid;
+	TransactionId priorXmax;
+
+	/* this is to avoid Assert failures on bad input */
+	if (!ItemPointerIsValid(tid))
+		return;
+
+	/*
+	 * Since this can be called with user-supplied TID, don't trust the input
+	 * too much.  (RelationGetNumberOfBlocks is an expensive check, so we
+	 * don't check t_ctid links again this way.  Note that it would not do to
+	 * call it just once and save the result, either.)
+	 */
+	blk = ItemPointerGetBlockNumber(tid);
+	if (blk >= RelationGetNumberOfBlocks(relation))
+		elog(ERROR, "block number %u is out of range for relation \"%s\"",
+			 blk, RelationGetRelationName(relation));
+
+	/*
+	 * Loop to chase down t_ctid links.  At top of loop, ctid is the tuple we
+	 * need to examine, and *tid is the TID we will return if ctid turns out
+	 * to be bogus.
+	 *
+	 * Note that we will loop until we reach the end of the t_ctid chain.
+	 * Depending on the snapshot passed, there might be at most one visible
+	 * version of the row, but we don't try to optimize for that.
+	 */
+	ctid = *tid;
+	priorXmax = InvalidTransactionId;	/* cannot check first XMIN */
+	for (;;)
+	{
+		Buffer		buffer;
+		Page		page;
+		OffsetNumber offnum;
+		ItemId		lp;
+		HeapTupleData tp;
+		bool		valid;
+
+		/*
+		 * Read, pin, and lock the page.
+		 */
+		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&ctid));
+		LockBuffer(buffer, BUFFER_LOCK_SHARE);
+		page = BufferGetPage(buffer);
+		TestForOldSnapshot(snapshot, relation, page);
+
+		/*
+		 * Check for bogus item number.  This is not treated as an error
+		 * condition because it can happen while following a t_ctid link. We
+		 * just assume that the prior tid is OK and return it unchanged.
+		 */
+		offnum = ItemPointerGetOffsetNumber(&ctid);
+		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+		lp = PageGetItemId(page, offnum);
+		if (!ItemIdIsNormal(lp))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+
+		/* OK to access the tuple */
+		tp.t_self = ctid;
+		tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+		tp.t_len = ItemIdGetLength(lp);
+		tp.t_tableOid = RelationGetRelid(relation);
+
+		/*
+		 * After following a t_ctid link, we might arrive at an unrelated
+		 * tuple.  Check for XMIN match.
+		 */
+		if (TransactionIdIsValid(priorXmax) &&
+			!TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(tp.t_data)))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+
+		/*
+		 * Check time qualification of tuple; if visible, set it as the new
+		 * result candidate.
+		 */
+		valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, &tp, snapshot, buffer);
+		CheckForSerializableConflictOut(valid, relation, &tp, buffer, snapshot);
+		if (valid)
+			*tid = ctid;
+
+		/*
+		 * If there's a valid t_ctid link, follow it, else we're done.
+		 */
+		if ((tp.t_data->t_infomask & HEAP_XMAX_INVALID) ||
+			HeapTupleHeaderIsOnlyLocked(tp.t_data) ||
+			ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+
+		ctid = tp.t_data->t_ctid;
+		priorXmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
+		UnlockReleaseBuffer(buffer);
+	}							/* end of loop */
+}
+
+
+/*
+ *	heapam_sync		- sync a heap, for use when no WAL has been written
+ *
+ * This forces the heap contents (including TOAST heap if any) down to disk.
+ * If we skipped using WAL, and WAL is otherwise needed, we must force the
+ * relation down to disk before it's safe to commit the transaction.  This
+ * requires writing out any dirty buffers and then doing a forced fsync.
+ *
+ * Indexes are not touched.  (Currently, index operations associated with
+ * the commands that use this are WAL-logged and so do not need fsync.
+ * That behavior might change someday, but in any case it's likely that
+ * any fsync decisions required would be per-index and hence not appropriate
+ * to be done here.)
+ */
+static void
+heapam_sync(Relation rel)
+{
+	/* non-WAL-logged tables never need fsync */
+	if (!RelationNeedsWAL(rel))
+		return;
+
+	/* main heap */
+	FlushRelationBuffers(rel);
+	/* FlushRelationBuffers will have opened rd_smgr */
+	smgrimmedsync(rel->rd_smgr, MAIN_FORKNUM);
+
+	/* FSM is not critical, don't bother syncing it */
+
+	/* toast heap, if any */
+	if (OidIsValid(rel->rd_rel->reltoastrelid))
+	{
+		Relation	toastrel;
+
+		toastrel = heap_open(rel->rd_rel->reltoastrelid, AccessShareLock);
+		FlushRelationBuffers(toastrel);
+		smgrimmedsync(toastrel->rd_smgr, MAIN_FORKNUM);
+		heap_close(toastrel, AccessShareLock);
+	}
+}
+
+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 bool
+heapam_tuple_is_heaopnly(StorageTuple tuple)
+{
+	return HeapTupleIsHeapOnly((HeapTuple)tuple);
+}
+
+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)
+{
+	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
+
+    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 = heapam_multi_insert;
+
+    amroutine->speculative_finish = heapam_finish_speculative;
+    amroutine->speculative_abort = heapam_abort_speculative;
+
+    amroutine->get_tuple_data = heapam_get_tuple_data;
+    amroutine->tuple_is_heaponly = heapam_tuple_is_heaopnly;
+    amroutine->tuple_from_datum = heapam_form_tuple_by_datum;
+    amroutine->tuple_get_latest_tid = heapam_get_latest_tid;
+
+    amroutine->relation_sync = heapam_sync;
 
 	amroutine->slot_storageam = heapam_storage_slot_handler;
 
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 191f088703..8fba61c4f1 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -111,6 +111,7 @@
 #include "access/heapam_common.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"
@@ -127,13 +128,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
@@ -358,7 +359,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/storageam.c b/src/backend/access/heap/storageam.c
new file mode 100644
index 0000000000..d1d7364e7f
--- /dev/null
+++ b/src/backend/access/heap/storageam.c
@@ -0,0 +1,306 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/heap/storageam.c
+ *
+ *
+ * NOTES
+ *	  This file contains the storage_ routines which implement
+ *	  the POSTGRES storage access method used for all POSTGRES
+ *	  relations.
+ *
+ *-------------------------------------------------------------------------
+ */
+#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 "access/relscan.h"
+#include "access/storageam.h"
+#include "access/storageamapi.h"
+#include "access/tuptoaster.h"
+#include "access/valid.h"
+#include "access/visibilitymap.h"
+#include "access/xloginsert.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/bufmgr.h"
+#include "storage/lmgr.h"
+#include "storage/predicate.h"
+#include "storage/procarray.h"
+#include "storage/smgr.h"
+#include "storage/spin.h"
+#include "utils/builtins.h"
+#include "utils/inval.h"
+#include "utils/rel.h"
+#include "utils/tqual.h"
+
+
+/*
+ *	storage_fetch		- retrieve tuple with given tid
+ *
+ * On entry, tuple->t_self is the TID to fetch.  We pin the buffer holding
+ * the tuple, fill in the remaining fields of *tuple, and check the tuple
+ * against the specified snapshot.
+ *
+ * If successful (tuple found and passes snapshot time qual), then *userbuf
+ * is set to the buffer holding the tuple and TRUE is returned.  The caller
+ * must unpin the buffer when done with the tuple.
+ *
+ * If the tuple is not found (ie, item number references a deleted slot),
+ * then tuple->t_data is set to NULL and FALSE is returned.
+ *
+ * If the tuple is found but fails the time qual check, then FALSE is returned
+ * but tuple->t_data is left pointing to the tuple.
+ *
+ * keep_buf determines what is done with the buffer in the FALSE-result cases.
+ * When the caller specifies keep_buf = true, we retain the pin on the buffer
+ * and return it in *userbuf (so the caller must eventually unpin it); when
+ * keep_buf = false, the pin is released and *userbuf is set to InvalidBuffer.
+ *
+ * stats_relation is the relation to charge the heap_fetch operation against
+ * for statistical purposes.  (This could be the heap rel itself, an
+ * associated index, or NULL to not count the fetch at all.)
+ *
+ * heap_fetch does not follow HOT chains: only the exact TID requested will
+ * be fetched.
+ *
+ * It is somewhat inconsistent that we ereport() on invalid block number but
+ * return false on invalid item number.  There are a couple of reasons though.
+ * One is that the caller can relatively easily check the block number for
+ * validity, but cannot check the item number without reading the page
+ * himself.  Another is that when we are following a t_ctid link, we can be
+ * reasonably confident that the page number is valid (since VACUUM shouldn't
+ * truncate off the destination page without having killed the referencing
+ * tuple first), but the item number might well not be good.
+ */
+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
+ *
+ * Note that this acquires a buffer pin, which the caller must release.
+ *
+ * Input parameters:
+ *	relation: relation containing tuple (caller must hold suitable lock)
+ *	tuple->t_self: TID of tuple to lock (rest of struct need not be valid)
+ *	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
+ *	follow_updates: if true, follow the update chain to also lock descendant
+ *		tuples.
+ *
+ * Output parameters:
+ *	*tuple: all fields filled in
+ *	*buffer: set to buffer holding tuple (pinned but not locked at exit)
+ *	*hufd: filled in failure cases (see below)
+ *
+ * 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.
+ *
+ * See README.tuplock for a thorough explanation of this mechanism.
+ */
+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)
+{
+	return relation->rd_stamroutine->tuple_insert(relation, slot, cid,
+							options, bistate);
+}
+
+/*
+ * 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,
+				   CommandId cid, Snapshot crosscheck, bool wait,
+				   HeapUpdateFailureData *hufd, LockTupleMode *lockmode)
+{
+	return relation->rd_stamroutine->tuple_update(relation, otid, slot, cid,
+							crosscheck, wait, hufd, lockmode);
+}
+
+
+/*
+ *	storage_multi_insert	- insert multiple tuple into a storage
+ *
+ * This is like heap_insert(), but inserts multiple tuples in one operation.
+ * That's faster than calling heap_insert() in a loop, because when multiple
+ * tuples can be inserted on a single page, we can write just a single WAL
+ * record covering all of them, and only need to lock/unlock the page once.
+ *
+ * Note: this leaks memory into the current memory context. You can create a
+ * temporary context before calling this, if that's a problem.
+ */
+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_finish_speculative - mark speculative insertion as successful
+ *
+ * To successfully finish a speculative insertion we have to clear speculative
+ * token from tuple.  To do so the t_ctid field, which will contain a
+ * speculative token value, is modified in place to point to the tuple itself,
+ * which is characteristic of a newly inserted ordinary tuple.
+ *
+ * NB: It is not ok to commit without either finishing or aborting a
+ * speculative insertion.  We could treat speculative tuples of committed
+ * transactions implicitly as completed, but then we would have to be prepared
+ * to deal with speculative tokens on committed tuples.  That wouldn't be
+ * difficult - no-one looks at the ctid field of a tuple with invalid xmax -
+ * but clearing the token at completion isn't very expensive either.
+ * An explicit confirmation WAL record also makes logical decoding simpler.
+ */
+void
+storage_finish_speculative(Relation relation, TupleTableSlot *slot)
+{
+	relation->rd_stamroutine->speculative_finish(relation, slot);
+}
+
+/*
+ *	storage_abort_speculative - kill a speculatively inserted tuple
+ *
+ * Marks a tuple that was speculatively inserted in the same command as dead,
+ * by setting its xmin as invalid.  That makes it immediately appear as dead
+ * to all transactions, including our own.  In particular, it makes
+ * HeapTupleSatisfiesDirty() regard the tuple as dead, so that another backend
+ * inserting a duplicate key value won't unnecessarily wait for our whole
+ * transaction to finish (it'll just wait for our speculative insertion to
+ * finish).
+ *
+ * Killing the tuple prevents "unprincipled deadlocks", which are deadlocks
+ * that arise due to a mutual dependency that is not user visible.  By
+ * definition, unprincipled deadlocks cannot be prevented by the user
+ * reordering lock acquisition in client code, because the implementation level
+ * lock acquisitions are not under the user's direct control.  If speculative
+ * inserters did not take this precaution, then under high concurrency they
+ * could deadlock with each other, which would not be acceptable.
+ *
+ * This is somewhat redundant with heap_delete, but we prefer to have a
+ * dedicated routine with stripped down requirements.  Note that this is also
+ * used to delete the TOAST tuples created during speculative insertion.
+ *
+ * This routine does not affect logical decoding as it only looks at
+ * confirmation records.
+ */
+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);
+}
+
+bool
+storage_tuple_is_heaponly(Relation relation, StorageTuple tuple)
+{
+	return relation->rd_stamroutine->tuple_is_heaponly(tuple);
+}
+
+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
+ *
+ * This forces the heap contents (including TOAST heap if any) down to disk.
+ * If we skipped using WAL, and WAL is otherwise needed, we must force the
+ * relation down to disk before it's safe to commit the transaction.  This
+ * requires writing out any dirty buffers and then doing a forced fsync.
+ *
+ * Indexes are not touched.  (Currently, index operations associated with
+ * the commands that use this are WAL-logged and so do not need fsync.
+ * That behavior might change someday, but in any case it's likely that
+ * any fsync decisions required would be per-index and hence not appropriate
+ * to be done here.)
+ */
+void
+storage_sync(Relation rel)
+{
+	rel->rd_stamroutine->relation_sync(rel);
+}
diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index 5a8f1dab83..d766a6eb6a 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,12 @@ 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/commands/copy.c b/src/backend/commands/copy.c
index f1f546a321..beb7f050fe 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -22,6 +22,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"
@@ -2696,8 +2697,6 @@ CopyFrom(CopyState cstate)
 
 			if (slot == NULL)	/* "do nothing" */
 				skip_tuple = true;
-			else				/* trigger might have changed tuple */
-				tuple = ExecHeapifySlot(slot);
 		}
 
 		if (!skip_tuple)
@@ -2760,19 +2759,18 @@ 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);
+					storage_insert(resultRelInfo->ri_RelationDesc, slot, mycid, hi_options, bistate);
 
 					if (resultRelInfo->ri_NumIndices > 0)
 						recheckIndexes = ExecInsertIndexTuples(slot,
-															   &(tuple->t_self),
+															   &(slot->tts_tid),
 															   estate,
 															   false,
 															   NULL,
 															   NIL);
 
 					/* AFTER ROW INSERT Triggers */
-					ExecARInsertTriggers(estate, resultRelInfo, tuple,
+					ExecARInsertTriggers(estate, resultRelInfo, slot,
 										 recheckIndexes, cstate->transition_capture);
 
 					list_free(recheckIndexes);
@@ -2868,7 +2866,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;
 }
@@ -2901,12 +2899,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);
 
 	/*
@@ -2925,7 +2923,7 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
 				ExecInsertIndexTuples(myslot, &(bufferedTuples[i]->t_self),
 									  estate, false, NULL, NIL);
 			ExecARInsertTriggers(estate, resultRelInfo,
-								 bufferedTuples[i],
+								 myslot,
 								 recheckIndexes, cstate->transition_capture);
 			list_free(recheckIndexes);
 		}
@@ -2942,8 +2940,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 a0ec444d33..d119149039 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,24 @@ 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);
+		slot->tts_tupleOid = InvalidOid;
 
-	heap_insert(myState->rel,
-				tuple,
-				myState->output_cid,
-				myState->hi_options,
-				myState->bistate);
+	storage_insert(myState->rel,
+					 slot,
+					myState->output_cid,
+					myState->hi_options,
+					myState->bistate);
 
 	/* We know this is a newly created relation, so there are no indexes */
 
@@ -619,7 +619,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..6102481fde 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,16 +492,15 @@ 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);
+	ExecMaterializeSlot(slot);
 
-	heap_insert(myState->transientrel,
-				tuple,
+	storage_insert(myState->transientrel,
+						slot,
 				myState->output_cid,
 				myState->hi_options,
 				myState->bistate);
@@ -522,7 +522,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 563bcda30c..c9e5ae0832 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"
@@ -4652,7 +4653,7 @@ 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);
 
 			ResetExprContext(econtext);
 
@@ -4676,7 +4677,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 2f530169b8..8e2f351949 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2352,17 +2352,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 +3016,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 +3035,11 @@ GetTupleForTrigger(EState *estate,
 		 * lock tuple for update
 		 */
 ltrmark:;
-		tuple.t_self = *tid;
-		test = heap_lock_tuple(relation, &tuple,
+		test = storage_lock_tuple(relation, tid, &tuple,
 							   estate->es_output_cid,
 							   lockmode, LockWaitBlock,
 							   false, &buffer, &hufd);
+		result = tuple;
 		switch (test)
 		{
 			case HeapTupleSelfUpdated:
@@ -3066,7 +3071,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 +3118,7 @@ ltrmark:;
 	{
 		Page		page;
 		ItemId		lp;
+		HeapTupleData tupledata;
 
 		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 
@@ -3130,17 +3137,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;
 }
 
@@ -3953,8 +3960,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;
@@ -4027,10 +4034,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
@@ -4044,10 +4050,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/execMain.c b/src/backend/executor/execMain.c
index 62fb05efac..8657a139ed 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1895,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;
 
@@ -1975,7 +1975,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				 */
 				if (resultRelInfo->ri_PartitionRoot)
 				{
-					HeapTuple	tuple = ExecFetchSlotTuple(slot);
+					StorageTuple	tuple = ExecFetchSlotTuple(slot);
 					TupleConversionMap *map;
 
 					rel = resultRelInfo->ri_PartitionRoot;
@@ -2022,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;
 
@@ -2481,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);
 
@@ -2498,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.
@@ -2529,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
@@ -2562,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
@@ -2577,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;
@@ -2596,8 +2599,8 @@ 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),
-									 priorXmax))
+			t_data = storage_tuple_get_data(relation, tuple, XMIN);
+			if (!TransactionIdEquals(t_data.xid, priorXmax))
 			{
 				ReleaseBuffer(buffer);
 				return NULL;
@@ -2618,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:
@@ -2647,17 +2651,20 @@ 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,
+			test = storage_lock_tuple(relation, tid, tuple,
 								   estate->es_output_cid,
 								   lockmode, wait_policy,
 								   false, &buffer, &hufd);
@@ -2696,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;
@@ -2723,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;
 		}
@@ -2735,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;
@@ -2744,8 +2750,8 @@ 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),
-								 priorXmax))
+		t_data = storage_tuple_get_data(relation, tuple, XMIN);
+		if (!TransactionIdEquals(t_data.xid, priorXmax))
 		{
 			ReleaseBuffer(buffer);
 			return NULL;
@@ -2763,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);
@@ -2771,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;
 }
 
 /*
@@ -2827,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;
 
@@ -2846,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;
@@ -2874,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");
@@ -2905,8 +2915,6 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 
 		if (erm->markType == ROW_MARK_REFERENCE)
 		{
-			HeapTuple	copyTuple;
-
 			Assert(erm->relation != NULL);
 
 			/* fetch the tuple's ctid */
@@ -2930,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,
+				tuple = fdwroutine->RefetchForeignRow(epqstate->estate,
 														  erm,
 														  datum,
 														  &updated);
-				if (copyTuple == NULL)
+				if (tuple == NULL)
 					elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck");
 
 				/*
@@ -2948,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,
+				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 */
@@ -2974,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);
+
 		}
 	}
 }
@@ -3152,8 +3148,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 6700f0ad80..8d625b6cbe 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),
+		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),
+		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,7 @@ void
 ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 {
 	bool		skip_tuple = false;
-	HeapTuple	tuple;
+	StorageTuple	tuple;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 
@@ -349,6 +351,7 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 	if (!skip_tuple)
 	{
 		List	   *recheckIndexes = NIL;
+		tuple_data	t_data;
 
 		/* Check the constraints of the tuple */
 		if (rel->rd_att->constr)
@@ -359,14 +362,15 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 
 		/* OK, store the tuple and create index entries for it */
 		simple_heap_insert(rel, tuple);
+		t_data = storage_tuple_get_data(rel, tuple, TID);
 
 		if (resultRelInfo->ri_NumIndices > 0)
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+			recheckIndexes = ExecInsertIndexTuples(slot, &(t_data.tid),
 												   estate, false, NULL,
 												   NIL);
 
 		/* AFTER ROW INSERT Triggers */
-		ExecARInsertTriggers(estate, resultRelInfo, tuple,
+		ExecARInsertTriggers(estate, resultRelInfo, slot,
 							 recheckIndexes, NULL);
 
 		/*
@@ -390,7 +394,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);
@@ -426,8 +430,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 		/* OK, update the tuple and index entries for it */
 		simple_heap_update(rel, tid, tuple);
 
-		if (resultRelInfo->ri_NumIndices > 0 &&
-			!HeapTupleIsHeapOnly(tuple))
+		if (resultRelInfo->ri_NumIndices > 0 && !storage_tuple_is_heaponly(rel, tuple))
 			recheckIndexes = ExecInsertIndexTuples(slot, tid,
 												   estate, false, NULL,
 												   NIL);
diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c
index 93895600a5..f06f34a1ec 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,
+		test = storage_lock_tuple(erm->relation, &tid, &tuple,
 							   estate->es_output_cid,
 							   lockmode, erm->waitPolicy, true,
 							   &buffer, &hufd);
-		ReleaseBuffer(buffer);
+		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,
+			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 dd8f792404..4681dc8dac 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/executor.h"
 #include "executor/nodeModifyTable.h"
@@ -164,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;
 
@@ -191,7 +192,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
 static void
 ExecCheckHeapTupleVisible(EState *estate,
 						  Relation rel,
-						  HeapTuple tuple,
+						  StorageTuple tuple,
 						  Buffer buffer)
 {
 	if (!IsolationUsesXactSnapshot())
@@ -204,13 +205,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")));
@@ -226,19 +229,20 @@ ExecCheckTIDVisible(EState *estate,
 					ResultRelInfo *relinfo,
 					ItemPointer tid)
 {
+	Buffer buffer;
 	Relation	rel = relinfo->ri_RelationDesc;
-	Buffer		buffer;
-	HeapTupleData tuple;
+	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);
 }
 
 /* ----------------------------------------------------------------
@@ -259,7 +263,7 @@ ExecInsert(ModifyTableState *mtstate,
 		   EState *estate,
 		   bool canSetTag)
 {
-	HeapTuple	tuple;
+	StorageTuple	tuple;
 	ResultRelInfo *resultRelInfo;
 	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
@@ -267,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
 	 */
@@ -284,6 +282,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.
@@ -374,19 +374,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.
@@ -404,9 +416,6 @@ ExecInsert(ModifyTableState *mtstate,
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
-
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -418,9 +427,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)
@@ -436,14 +442,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;
 	}
@@ -463,7 +467,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
@@ -554,24 +559,24 @@ 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,
+			newId = storage_insert(resultRelationDesc, slot,
 								estate->es_output_cid,
 								HEAP_INSERT_SPECULATIVE,
 								NULL);
 
 			/* insert index entries for tuple */
-			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+			recheckIndexes = ExecInsertIndexTuples(slot, &(slot->tts_tid),
 												   estate, true, &specConflict,
 												   arbiterIndexes);
 
 			/* adjust the tuple's state accordingly */
 			if (!specConflict)
-				heap_finish_speculative(resultRelationDesc, tuple);
+				storage_finish_speculative(resultRelationDesc, slot);
 			else
-				heap_abort_speculative(resultRelationDesc, tuple);
+				storage_abort_speculative(resultRelationDesc, slot);
 
 			/*
 			 * Wake up anyone waiting for our decision.  They will re-check
@@ -599,17 +604,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,
+			newId = storage_insert(resultRelationDesc, slot,
 								estate->es_output_cid,
 								0, NULL);
 
 			/* insert index entries for tuple */
 			if (resultRelInfo->ri_NumIndices > 0)
-				recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+				recheckIndexes = ExecInsertIndexTuples(slot, &(slot->tts_tid),
 													   estate, false, NULL,
 													   arbiterIndexes);
 		}
@@ -619,11 +621,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);
@@ -674,7 +676,7 @@ ExecInsert(ModifyTableState *mtstate,
 static TupleTableSlot *
 ExecDelete(ModifyTableState *mtstate,
 		   ItemPointer tupleid,
-		   HeapTuple oldtuple,
+		   StorageTuple oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate,
@@ -719,8 +721,6 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
-		HeapTuple	tuple;
-
 		/*
 		 * delete from foreign table: let the FDW do it
 		 *
@@ -746,8 +746,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
 	{
@@ -761,7 +763,7 @@ ExecDelete(ModifyTableState *mtstate,
 		 * mode transactions.
 		 */
 ldelete:;
-		result = heap_delete(resultRelationDesc, tupleid,
+		result = storage_delete(resultRelationDesc, tupleid,
 							 estate->es_output_cid,
 							 estate->es_crosscheck_snapshot,
 							 true /* wait for commit */ ,
@@ -861,7 +863,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)
@@ -875,20 +877,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);
@@ -897,7 +898,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))
@@ -934,14 +935,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;
@@ -1006,14 +1007,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
 	{
@@ -1023,7 +1024,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
@@ -1059,7 +1060,7 @@ lreplace:;
 		 * needed for referential integrity updates in transaction-snapshot
 		 * mode transactions.
 		 */
-		result = heap_update(resultRelationDesc, tupleid, tuple,
+		result = storage_update(resultRelationDesc, tupleid, slot,
 							 estate->es_output_cid,
 							 estate->es_crosscheck_snapshot,
 							 true /* wait for commit */ ,
@@ -1151,8 +1152,8 @@ lreplace:;
 		 *
 		 * 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),
+		if ((resultRelInfo->ri_NumIndices > 0) && !storage_tuple_is_heaponly(resultRelationDesc, tuple))
+			recheckIndexes = ExecInsertIndexTuples(slot, &(slot->tts_tid),
 												   estate, false, NULL, NIL);
 	}
 
@@ -1211,11 +1212,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);
@@ -1226,10 +1228,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:
@@ -1254,7 +1254,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"),
@@ -1285,7 +1286,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:
@@ -1313,10 +1316,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
@@ -1331,7 +1334,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 */
 	}
@@ -1371,12 +1376,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;
 }
 
@@ -1569,7 +1576,7 @@ ExecModifyTable(PlanState *pstate)
 	ItemPointer tupleid = NULL;
 	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..8a6b2172ea 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 083f7d60a7..52779b7256 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"
@@ -354,7 +355,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);
@@ -389,7 +390,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..cdd45ef313 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -133,40 +133,19 @@ 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,
-		   HeapTuple tuple, Buffer *userbuf, bool keep_buf,
-		   Relation stats_relation);
+extern Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid,
+			int options, BulkInsertState bistate);
 extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
 					   Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
 					   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);
 
 extern BulkInsertState GetBulkInsertState(void);
 extern void FreeBulkInsertState(BulkInsertState);
 extern void ReleaseBulkInsertStatePin(BulkInsertState bistate);
 
-extern Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid,
-			int options, BulkInsertState bistate);
-extern void heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
-				  CommandId cid, int options, BulkInsertState bistate);
-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 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,
-				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);
@@ -179,7 +158,6 @@ extern void simple_heap_delete(Relation relation, ItemPointer tid);
 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);
 
 /* in heap/pruneheap.c */
diff --git a/src/include/access/heapam_common.h b/src/include/access/heapam_common.h
index 1fe15ede56..799b4edada 100644
--- a/src/include/access/heapam_common.h
+++ b/src/include/access/heapam_common.h
@@ -34,6 +34,111 @@
 #include "utils/relcache.h"
 #include "utils/snapshot.h"
 
+/*
+ * Each tuple lock mode has a corresponding heavyweight lock, and one or two
+ * corresponding MultiXactStatuses (one to merely lock tuples, another one to
+ * update them).  This table (and the macros below) helps us determine the
+ * heavyweight lock mode and MultiXactStatus values to use for any particular
+ * tuple lock strength.
+ *
+ * Don't look at lockstatus/updstatus directly!  Use get_mxact_status_for_lock
+ * instead.
+ */
+static const struct
+{
+	LOCKMODE	hwlock;
+	int			lockstatus;
+	int			updstatus;
+}
+
+			tupleLockExtraInfo[MaxLockTupleMode + 1] =
+{
+	{							/* LockTupleKeyShare */
+		AccessShareLock,
+		MultiXactStatusForKeyShare,
+		-1						/* KeyShare does not allow updating tuples */
+	},
+	{							/* LockTupleShare */
+		RowShareLock,
+		MultiXactStatusForShare,
+		-1						/* Share does not allow updating tuples */
+	},
+	{							/* LockTupleNoKeyExclusive */
+		ExclusiveLock,
+		MultiXactStatusForNoKeyUpdate,
+		MultiXactStatusNoKeyUpdate
+	},
+	{							/* LockTupleExclusive */
+		AccessExclusiveLock,
+		MultiXactStatusForUpdate,
+		MultiXactStatusUpdate
+	}
+};
+
+/*
+ * This table maps tuple lock strength values for each particular
+ * MultiXactStatus value.
+ */
+static const int MultiXactStatusLock[MaxMultiXactStatus + 1] =
+{
+	LockTupleKeyShare,			/* ForKeyShare */
+	LockTupleShare,				/* ForShare */
+	LockTupleNoKeyExclusive,	/* ForNoKeyUpdate */
+	LockTupleExclusive,			/* ForUpdate */
+	LockTupleNoKeyExclusive,	/* NoKeyUpdate */
+	LockTupleExclusive			/* Update */
+};
+
+/* Get the LockTupleMode for a given MultiXactStatus */
+#define TUPLOCK_from_mxstatus(status) \
+			(MultiXactStatusLock[(status)])
+
+/*
+ * Acquire heavyweight locks on tuples, using a LockTupleMode strength value.
+ * This is more readable than having every caller translate it to lock.h's
+ * LOCKMODE.
+ */
+#define LockTupleTuplock(rel, tup, mode) \
+	LockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
+#define UnlockTupleTuplock(rel, tup, mode) \
+	UnlockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
+#define ConditionalLockTupleTuplock(rel, tup, mode) \
+	ConditionalLockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
+/* Get the LOCKMODE for a given MultiXactStatus */
+#define LOCKMODE_from_mxstatus(status) \
+			(tupleLockExtraInfo[TUPLOCK_from_mxstatus((status))].hwlock)
+extern HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
+					TransactionId xid, CommandId cid, int options);
+
+extern HTSU_Result heap_delete(Relation relation, ItemPointer tid,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			HeapUpdateFailureData *hufd);
+extern HTSU_Result heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			HeapUpdateFailureData *hufd, LockTupleMode *lockmode);
+
+extern XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
+extern uint8 compute_infobits(uint16 infomask, uint16 infomask2);
+extern void compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
+						  uint16 old_infomask2, TransactionId add_to_xmax,
+						  LockTupleMode mode, bool is_update,
+						  TransactionId *result_xmax, uint16 *result_infomask,
+						  uint16 *result_infomask2);
+extern void UpdateXmaxHintBits(HeapTupleHeader tuple, Buffer buffer, TransactionId xid);
+extern bool DoesMultiXactIdConflict(MultiXactId multi, uint16 infomask,
+						LockTupleMode lockmode);
+extern bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
+						   uint16 infomask, Relation rel, int *remaining);
+
+extern void MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
+				Relation rel, ItemPointer ctid, XLTW_Oper oper,
+				int *remaining);
+extern MultiXactStatus get_mxact_status_for_lock(LockTupleMode mode, bool is_update);
+
+extern void heap_inplace_update(Relation relation, HeapTuple tuple);
+extern bool heap_acquire_tuplock(Relation relation, ItemPointer tid,
+					 LockTupleMode mode, LockWaitPolicy wait_policy,
+					 bool *have_tuple_lock);
 
 /* in heap/heapam_common.c */
 extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
@@ -43,6 +148,28 @@ extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin);
 typedef struct StorageSlotAmRoutine StorageSlotAmRoutine;
 extern StorageSlotAmRoutine* heapam_storage_slot_handler(void);
 
+
+/*
+ * Given two versions of the same t_infomask for a tuple, compare them and
+ * return whether the relevant status for a tuple Xmax has changed.  This is
+ * used after a buffer lock has been released and reacquired: we want to ensure
+ * that the tuple state continues to be the same it was when we previously
+ * examined it.
+ *
+ * Note the Xmax field itself must be compared separately.
+ */
+static inline bool
+xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask)
+{
+	const uint16 interesting =
+	HEAP_XMAX_IS_MULTI | HEAP_XMAX_LOCK_ONLY | HEAP_LOCK_MASK;
+
+	if ((new_infomask & interesting) != (old_infomask & interesting))
+		return true;
+
+	return false;
+}
+
 /*
  * SetHintBits()
  *
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index 9539d67bec..16dfb3e748 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -811,6 +811,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/storageam.h b/src/include/access/storageam.h
new file mode 100644
index 0000000000..9502c92318
--- /dev/null
+++ b/src/include/access/storageam.h
@@ -0,0 +1,81 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "executor/tuptable.h"
+
+/* A physical tuple coming from a storage AM scan */
+typedef void *StorageTuple;
+
+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;
+
+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);
+
+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,
+				   CommandId cid, Snapshot crosscheck, bool wait,
+				   HeapUpdateFailureData *hufd, LockTupleMode *lockmode);
+
+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 void storage_finish_speculative(Relation relation, TupleTableSlot *slot);
+
+extern tuple_data storage_tuple_get_data(Relation relation, StorageTuple tuple, tuple_data_flags flags);
+
+extern bool storage_tuple_is_heaponly(Relation relation, StorageTuple tuple);
+
+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 95fe02888f..c2e6dc2aef 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -13,32 +13,13 @@
 
 #include "access/htup.h"
 #include "access/heapam.h"
+#include "access/storageam.h"
 #include "access/sdir.h"
 #include "access/skey.h"
 #include "executor/tuptable.h"
 #include "utils/relcache.h"
 #include "utils/snapshot.h"
 
-/* A physical tuple coming from a storage AM scan */
-typedef void *StorageTuple;
-
-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;
-
-
 typedef HeapScanDesc (*scan_begin_hook) (Relation relation,
 										Snapshot snapshot,
 										int nkeys, ScanKey key,
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 770881849c..8704b7b54c 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -196,7 +196,7 @@ 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,
+extern StorageTuple EvalPlanQualFetch(EState *estate, Relation relation,
 				  int lockmode, LockWaitPolicy wait_policy, ItemPointer tid,
 				  TransactionId priorXmax);
 extern void EvalPlanQualInit(EPQState *epqstate, EState *estate,
@@ -204,8 +204,8 @@ extern void EvalPlanQualInit(EPQState *epqstate, EState *estate,
 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);
 extern void ExecSetupPartitionTupleRouting(Relation rel,
 							   Index resultRTindex,
 							   EState *estate,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index c6d3021c85..c19698089b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -503,7 +503,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 */
 
@@ -2023,7 +2023,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.14.1.windows.1

0007-Scan-functions-are-added-to-storage-AM.patchapplication/octet-stream; name=0007-Scan-functions-are-added-to-storage-AM.patchDownload
From 477750defc520950c38dd565ba38562bae6f4789 Mon Sep 17 00:00:00 2001
From: Hari Babu Kommi <kommi.haribabu@gmail.com>
Date: Fri, 8 Sep 2017 15:45:04 +1000
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           | 1504 ++--------------------------
 src/backend/access/heap/heapam_storage.c   | 1299 ++++++++++++++++++++++++
 src/backend/access/heap/rewriteheap.c      |    2 +-
 src/backend/access/heap/storageam.c        |  235 +++++
 src/backend/access/index/genam.c           |    7 +-
 src/backend/access/index/indexam.c         |    3 +-
 src/backend/access/nbtree/nbtinsert.c      |    5 +-
 src/backend/bootstrap/bootstrap.c          |   25 +-
 src/backend/catalog/aclchk.c               |   13 +-
 src/backend/catalog/index.c                |   27 +-
 src/backend/catalog/partition.c            |    6 +-
 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/trigger.c             |    3 +-
 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     |   16 +-
 src/backend/executor/execTuples.c          |    8 +-
 src/backend/executor/functions.c           |    4 +-
 src/backend/executor/nodeAgg.c             |    4 +-
 src/backend/executor/nodeBitmapHeapscan.c  |   11 +-
 src/backend/executor/nodeForeignscan.c     |    6 +-
 src/backend/executor/nodeGather.c          |    8 +-
 src/backend/executor/nodeGatherMerge.c     |   12 +-
 src/backend/executor/nodeIndexonlyscan.c   |    4 +-
 src/backend/executor/nodeIndexscan.c       |   16 +-
 src/backend/executor/nodeSamplescan.c      |   21 +-
 src/backend/executor/nodeSeqscan.c         |   39 +-
 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                |   30 +-
 src/include/access/heapam_common.h         |    8 +
 src/include/access/storageam.h             |   42 +-
 src/include/executor/functions.h           |    2 +-
 src/include/executor/spi.h                 |   10 +-
 src/include/executor/tqueue.h              |    2 +-
 src/include/funcapi.h                      |    2 +-
 56 files changed, 1917 insertions(+), 1727 deletions(-)

diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index 5f076efe7c..063e07992b 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 d20f211a08..b64fec8e4a 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -81,19 +81,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 XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 				Buffer newbuf, HeapTuple oldtup,
 				HeapTuple newtup, HeapTuple old_key_tup,
@@ -112,139 +99,6 @@ static bool Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
 static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_modified,
 					   bool *copy);
 
-/* ----------------------------------------------------------------
- *						 heap support routines
- * ----------------------------------------------------------------
- */
-
-/* ----------------
- *		initscan - scan code common to heap_beginscan and heap_rescan
- * ----------------
- */
-static void
-initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
-{
-	bool		allow_strat;
-	bool		allow_sync;
-
-	/*
-	 * Determine the number of blocks we have to scan.
-	 *
-	 * It is sufficient to do this once at scan start, since any tuples added
-	 * while the scan is in progress will be invisible to my snapshot anyway.
-	 * (That is not true when using a non-MVCC snapshot.  However, we couldn't
-	 * guarantee to return tuples added after scan start anyway, since they
-	 * might go into pages we already scanned.  To guarantee consistent
-	 * results for a non-MVCC snapshot, the caller must hold some higher-level
-	 * lock that ensures the interesting tuple(s) won't change.)
-	 */
-	if (scan->rs_parallel != NULL)
-		scan->rs_nblocks = scan->rs_parallel->phs_nblocks;
-	else
-		scan->rs_nblocks = RelationGetNumberOfBlocks(scan->rs_rd);
-
-	/*
-	 * If the table is large relative to NBuffers, use a bulk-read access
-	 * strategy and enable synchronized scanning (see syncscan.c).  Although
-	 * the thresholds for these features could be different, we make them the
-	 * same so that there are only two behaviors to tune rather than four.
-	 * (However, some callers need to be able to disable one or both of these
-	 * behaviors, independently of the size of the table; also there is a GUC
-	 * variable that can disable synchronized scanning.)
-	 *
-	 * 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)
-	{
-		allow_strat = scan->rs_allow_strat;
-		allow_sync = scan->rs_allow_sync;
-	}
-	else
-		allow_strat = allow_sync = false;
-
-	if (allow_strat)
-	{
-		/* During a rescan, keep the previous strategy object. */
-		if (scan->rs_strategy == NULL)
-			scan->rs_strategy = GetAccessStrategy(BAS_BULKREAD);
-	}
-	else
-	{
-		if (scan->rs_strategy != NULL)
-			FreeAccessStrategy(scan->rs_strategy);
-		scan->rs_strategy = NULL;
-	}
-
-	if (scan->rs_parallel != NULL)
-	{
-		/* For parallel scan, believe whatever ParallelHeapScanDesc says. */
-		scan->rs_syncscan = scan->rs_parallel->phs_syncscan;
-	}
-	else if (keep_startblock)
-	{
-		/*
-		 * When rescanning, we want to keep the previous startblock setting,
-		 * so that rewinding a cursor doesn't generate surprising results.
-		 * Reset the active syncscan setting, though.
-		 */
-		scan->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);
-	}
-	else
-	{
-		scan->rs_syncscan = false;
-		scan->rs_startblock = 0;
-	}
-
-	scan->rs_numblocks = InvalidBlockNumber;
-	scan->rs_inited = false;
-	scan->rs_ctup.t_data = NULL;
-	ItemPointerSetInvalid(&scan->rs_ctup.t_self);
-	scan->rs_cbuf = InvalidBuffer;
-	scan->rs_cblock = InvalidBlockNumber;
-
-	/* page-at-a-time fields are always invalid when not rs_inited */
-
-	/*
-	 * copy the scan key, if appropriate
-	 */
-	if (key != NULL)
-		memcpy(scan->rs_key, key, scan->rs_nkeys * sizeof(ScanKeyData));
-
-	/*
-	 * Currently, we don't have a stats counter for bitmap heap scans (but the
-	 * underlying bitmap index scans will be counted) or sample scans (we only
-	 * update stats for tuple fetches there)
-	 */
-	if (!scan->rs_bitmapscan && !scan->rs_samplescan)
-		pgstat_count_heap_scan(scan->rs_rd);
-}
-
-/*
- * 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
-heap_setscanlimits(HeapScanDesc scan, BlockNumber startBlk, BlockNumber numBlks)
-{
-	Assert(!scan->rs_inited);	/* else too late to change */
-	Assert(!scan->rs_syncscan); /* else rs_startblock is significant */
-
-	/* Check startBlk is valid (but allow case of zero blocks...) */
-	Assert(startBlk == 0 || startBlk < scan->rs_nblocks);
-
-	scan->rs_startblock = startBlk;
-	scan->rs_numblocks = numBlks;
-}
-
 /*
  * heapgetpage - subroutine for heapgettup()
  *
@@ -363,603 +217,6 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 	scan->rs_ntuples = ntup;
 }
 
-/* ----------------
- *		heapgettup - fetch next heap tuple
- *
- *		Initialize the scan if not already done; then advance to the next
- *		tuple as indicated by "dir"; return the next tuple in scan->rs_ctup,
- *		or set scan->rs_ctup.t_data = NULL if no more tuples.
- *
- * dir == NoMovementScanDirection means "re-fetch the tuple indicated
- * by scan->rs_ctup".
- *
- * Note: the reason nkeys/key are passed separately, even though they are
- * kept in the scan descriptor, is that the caller may not want us to check
- * the scankeys.
- *
- * Note: when we fall off the end of the scan in either direction, we
- * reset rs_inited.  This means that a further request with the same
- * scan direction will restart the scan, which is a bit odd, but a
- * request with the opposite scan direction will start a fresh scan
- * in the proper direction.  The latter is required behavior for cursors,
- * while the former case is generally undefined behavior in Postgres
- * so we don't care too much.
- * ----------------
- */
-static void
-heapgettup(HeapScanDesc scan,
-		   ScanDirection dir,
-		   int nkeys,
-		   ScanKey key)
-{
-	HeapTuple	tuple = &(scan->rs_ctup);
-	Snapshot	snapshot = scan->rs_snapshot;
-	bool		backward = ScanDirectionIsBackward(dir);
-	BlockNumber page;
-	bool		finished;
-	Page		dp;
-	int			lines;
-	OffsetNumber lineoff;
-	int			linesleft;
-	ItemId		lpp;
-
-	/*
-	 * calculate next starting lineoff, given scan direction
-	 */
-	if (ScanDirectionIsForward(dir))
-	{
-		if (!scan->rs_inited)
-		{
-			/*
-			 * return null immediately if relation is empty
-			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
-			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
-				tuple->t_data = NULL;
-				return;
-			}
-			if (scan->rs_parallel != NULL)
-			{
-				heap_parallelscan_startblock_init(scan);
-
-				page = heap_parallelscan_nextpage(scan);
-
-				/* Other processes might have already finished the scan. */
-				if (page == InvalidBlockNumber)
-				{
-					Assert(!BufferIsValid(scan->rs_cbuf));
-					tuple->t_data = NULL;
-					return;
-				}
-			}
-			else
-				page = scan->rs_startblock; /* first page */
-			heapgetpage(scan, page);
-			lineoff = FirstOffsetNumber;	/* first offnum */
-			scan->rs_inited = true;
-		}
-		else
-		{
-			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-			lineoff =			/* next offnum */
-				OffsetNumberNext(ItemPointerGetOffsetNumber(&(tuple->t_self)));
-		}
-
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
-		lines = PageGetMaxOffsetNumber(dp);
-		/* page and lineoff now reference the physically next tid */
-
-		linesleft = lines - lineoff + 1;
-	}
-	else if (backward)
-	{
-		/* backward parallel scan not supported */
-		Assert(scan->rs_parallel == NULL);
-
-		if (!scan->rs_inited)
-		{
-			/*
-			 * return null immediately if relation is empty
-			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
-			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
-				tuple->t_data = NULL;
-				return;
-			}
-
-			/*
-			 * Disable reporting to syncscan logic in a backwards scan; it's
-			 * not very likely anyone else is doing the same thing at the same
-			 * time, and much more likely that we'll just bollix things for
-			 * forward scanners.
-			 */
-			scan->rs_syncscan = false;
-			/* start from last page of the scan */
-			if (scan->rs_startblock > 0)
-				page = scan->rs_startblock - 1;
-			else
-				page = scan->rs_nblocks - 1;
-			heapgetpage(scan, page);
-		}
-		else
-		{
-			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-		}
-
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
-		lines = PageGetMaxOffsetNumber(dp);
-
-		if (!scan->rs_inited)
-		{
-			lineoff = lines;	/* final offnum */
-			scan->rs_inited = true;
-		}
-		else
-		{
-			lineoff =			/* previous offnum */
-				OffsetNumberPrev(ItemPointerGetOffsetNumber(&(tuple->t_self)));
-		}
-		/* page and lineoff now reference the physically previous tid */
-
-		linesleft = lineoff;
-	}
-	else
-	{
-		/*
-		 * ``no movement'' scan direction: refetch prior tuple
-		 */
-		if (!scan->rs_inited)
-		{
-			Assert(!BufferIsValid(scan->rs_cbuf));
-			tuple->t_data = NULL;
-			return;
-		}
-
-		page = ItemPointerGetBlockNumber(&(tuple->t_self));
-		if (page != scan->rs_cblock)
-			heapgetpage(scan, page);
-
-		/* Since the tuple was previously fetched, needn't lock page here */
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
-		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
-		lpp = PageGetItemId(dp, lineoff);
-		Assert(ItemIdIsNormal(lpp));
-
-		tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
-		tuple->t_len = ItemIdGetLength(lpp);
-
-		return;
-	}
-
-	/*
-	 * advance the scan until we find a qualifying tuple or run out of stuff
-	 * to scan
-	 */
-	lpp = PageGetItemId(dp, lineoff);
-	for (;;)
-	{
-		while (linesleft > 0)
-		{
-			if (ItemIdIsNormal(lpp))
-			{
-				bool		valid;
-
-				tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
-				tuple->t_len = ItemIdGetLength(lpp);
-				ItemPointerSet(&(tuple->t_self), page, lineoff);
-
-				/*
-				 * if current tuple qualifies, return it.
-				 */
-				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine,
-													 tuple,
-													 snapshot,
-													 scan->rs_cbuf);
-
-				CheckForSerializableConflictOut(valid, scan->rs_rd, tuple,
-												scan->rs_cbuf, snapshot);
-
-				if (valid && key != NULL)
-					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
-								nkeys, key, valid);
-
-				if (valid)
-				{
-					LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
-					return;
-				}
-			}
-
-			/*
-			 * otherwise move to the next item on the page
-			 */
-			--linesleft;
-			if (backward)
-			{
-				--lpp;			/* move back in this page's ItemId array */
-				--lineoff;
-			}
-			else
-			{
-				++lpp;			/* move forward in this page's ItemId array */
-				++lineoff;
-			}
-		}
-
-		/*
-		 * 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);
-
-		/*
-		 * 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);
-			if (page == 0)
-				page = scan->rs_nblocks;
-			page--;
-		}
-		else if (scan->rs_parallel != NULL)
-		{
-			page = heap_parallelscan_nextpage(scan);
-			finished = (page == InvalidBlockNumber);
-		}
-		else
-		{
-			page++;
-			if (page >= scan->rs_nblocks)
-				page = 0;
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
-
-			/*
-			 * Report our new scan position for synchronization purposes. We
-			 * don't do that when moving backwards, however. That would just
-			 * mess up any other forward-moving scanners.
-			 *
-			 * Note: we do this before checking for end of scan so that the
-			 * final state of the position hint is back at the start of the
-			 * rel.  That's not strictly necessary, but otherwise when you run
-			 * the same query multiple times the starting position would shift
-			 * 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);
-		}
-
-		/*
-		 * return NULL if we've exhausted all the pages
-		 */
-		if (finished)
-		{
-			if (BufferIsValid(scan->rs_cbuf))
-				ReleaseBuffer(scan->rs_cbuf);
-			scan->rs_cbuf = InvalidBuffer;
-			scan->rs_cblock = InvalidBlockNumber;
-			tuple->t_data = NULL;
-			scan->rs_inited = false;
-			return;
-		}
-
-		heapgetpage(scan, page);
-
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
-		lines = PageGetMaxOffsetNumber((Page) dp);
-		linesleft = lines;
-		if (backward)
-		{
-			lineoff = lines;
-			lpp = PageGetItemId(dp, lines);
-		}
-		else
-		{
-			lineoff = FirstOffsetNumber;
-			lpp = PageGetItemId(dp, FirstOffsetNumber);
-		}
-	}
-}
-
-/* ----------------
- *		heapgettup_pagemode - fetch next heap tuple in page-at-a-time mode
- *
- *		Same API as heapgettup, but used in page-at-a-time mode
- *
- * The internal logic is much the same as heapgettup's too, but there are some
- * differences: we do not take the buffer content lock (that only needs to
- * happen inside heapgetpage), and we iterate through just the tuples listed
- * in rs_vistuples[] rather than all tuples on the page.  Notice that
- * lineindex is 0-based, where the corresponding loop variable lineoff in
- * heapgettup is 1-based.
- * ----------------
- */
-static void
-heapgettup_pagemode(HeapScanDesc scan,
-					ScanDirection dir,
-					int nkeys,
-					ScanKey key)
-{
-	HeapTuple	tuple = &(scan->rs_ctup);
-	bool		backward = ScanDirectionIsBackward(dir);
-	BlockNumber page;
-	bool		finished;
-	Page		dp;
-	int			lines;
-	int			lineindex;
-	OffsetNumber lineoff;
-	int			linesleft;
-	ItemId		lpp;
-
-	/*
-	 * calculate next starting lineindex, given scan direction
-	 */
-	if (ScanDirectionIsForward(dir))
-	{
-		if (!scan->rs_inited)
-		{
-			/*
-			 * return null immediately if relation is empty
-			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
-			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
-				tuple->t_data = NULL;
-				return;
-			}
-			if (scan->rs_parallel != NULL)
-			{
-				heap_parallelscan_startblock_init(scan);
-
-				page = heap_parallelscan_nextpage(scan);
-
-				/* Other processes might have already finished the scan. */
-				if (page == InvalidBlockNumber)
-				{
-					Assert(!BufferIsValid(scan->rs_cbuf));
-					tuple->t_data = NULL;
-					return;
-				}
-			}
-			else
-				page = scan->rs_startblock; /* first page */
-			heapgetpage(scan, page);
-			lineindex = 0;
-			scan->rs_inited = true;
-		}
-		else
-		{
-			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-			lineindex = scan->rs_cindex + 1;
-		}
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
-		/* page and lineindex now reference the next visible tid */
-
-		linesleft = lines - lineindex;
-	}
-	else if (backward)
-	{
-		/* backward parallel scan not supported */
-		Assert(scan->rs_parallel == NULL);
-
-		if (!scan->rs_inited)
-		{
-			/*
-			 * return null immediately if relation is empty
-			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
-			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
-				tuple->t_data = NULL;
-				return;
-			}
-
-			/*
-			 * Disable reporting to syncscan logic in a backwards scan; it's
-			 * not very likely anyone else is doing the same thing at the same
-			 * time, and much more likely that we'll just bollix things for
-			 * forward scanners.
-			 */
-			scan->rs_syncscan = false;
-			/* start from last page of the scan */
-			if (scan->rs_startblock > 0)
-				page = scan->rs_startblock - 1;
-			else
-				page = scan->rs_nblocks - 1;
-			heapgetpage(scan, page);
-		}
-		else
-		{
-			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-		}
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
-
-		if (!scan->rs_inited)
-		{
-			lineindex = lines - 1;
-			scan->rs_inited = true;
-		}
-		else
-		{
-			lineindex = scan->rs_cindex - 1;
-		}
-		/* page and lineindex now reference the previous visible tid */
-
-		linesleft = lineindex + 1;
-	}
-	else
-	{
-		/*
-		 * ``no movement'' scan direction: refetch prior tuple
-		 */
-		if (!scan->rs_inited)
-		{
-			Assert(!BufferIsValid(scan->rs_cbuf));
-			tuple->t_data = NULL;
-			return;
-		}
-
-		page = ItemPointerGetBlockNumber(&(tuple->t_self));
-		if (page != scan->rs_cblock)
-			heapgetpage(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);
-		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
-		lpp = PageGetItemId(dp, lineoff);
-		Assert(ItemIdIsNormal(lpp));
-
-		tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
-		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]);
-
-		return;
-	}
-
-	/*
-	 * advance the scan until we find a qualifying tuple or run out of stuff
-	 * to scan
-	 */
-	for (;;)
-	{
-		while (linesleft > 0)
-		{
-			lineoff = scan->rs_vistuples[lineindex];
-			lpp = PageGetItemId(dp, lineoff);
-			Assert(ItemIdIsNormal(lpp));
-
-			tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
-			tuple->t_len = ItemIdGetLength(lpp);
-			ItemPointerSet(&(tuple->t_self), page, lineoff);
-
-			/*
-			 * if current tuple qualifies, return it.
-			 */
-			if (key != NULL)
-			{
-				bool		valid;
-
-				HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
-							nkeys, key, valid);
-				if (valid)
-				{
-					scan->rs_cindex = lineindex;
-					return;
-				}
-			}
-			else
-			{
-				scan->rs_cindex = lineindex;
-				return;
-			}
-
-			/*
-			 * otherwise move to the next item on the page
-			 */
-			--linesleft;
-			if (backward)
-				--lineindex;
-			else
-				++lineindex;
-		}
-
-		/*
-		 * if we get here, it means we've exhausted the items on this page and
-		 * it's time to move to the next.
-		 */
-		if (backward)
-		{
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
-			if (page == 0)
-				page = scan->rs_nblocks;
-			page--;
-		}
-		else if (scan->rs_parallel != NULL)
-		{
-			page = heap_parallelscan_nextpage(scan);
-			finished = (page == InvalidBlockNumber);
-		}
-		else
-		{
-			page++;
-			if (page >= scan->rs_nblocks)
-				page = 0;
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
-
-			/*
-			 * Report our new scan position for synchronization purposes. We
-			 * don't do that when moving backwards, however. That would just
-			 * mess up any other forward-moving scanners.
-			 *
-			 * Note: we do this before checking for end of scan so that the
-			 * final state of the position hint is back at the start of the
-			 * rel.  That's not strictly necessary, but otherwise when you run
-			 * the same query multiple times the starting position would shift
-			 * 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);
-		}
-
-		/*
-		 * return NULL if we've exhausted all the pages
-		 */
-		if (finished)
-		{
-			if (BufferIsValid(scan->rs_cbuf))
-				ReleaseBuffer(scan->rs_cbuf);
-			scan->rs_cbuf = InvalidBuffer;
-			scan->rs_cblock = InvalidBlockNumber;
-			tuple->t_data = NULL;
-			scan->rs_inited = false;
-			return;
-		}
-
-		heapgetpage(scan, page);
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
-		linesleft = lines;
-		if (backward)
-			lineindex = lines - 1;
-		else
-			lineindex = 0;
-	}
-}
-
 
 #if defined(DISABLE_COMPLEX_MACRO)
 /*
@@ -1186,317 +443,96 @@ relation_close(Relation relation, LOCKMODE lockmode)
 		UnlockRelationId(&relid, lockmode);
 }
 
-
-/* ----------------
- *		heap_open - open a heap relation by relation OID
- *
- *		This is essentially relation_open plus check that the relation
- *		is not an index nor a composite type.  (The caller should also
- *		check that it's not a view or foreign table before assuming it has
- *		storage.)
- * ----------------
- */
-Relation
-heap_open(Oid relationId, LOCKMODE lockmode)
-{
-	Relation	r;
-
-	r = relation_open(relationId, lockmode);
-
-	if (r->rd_rel->relkind == RELKIND_INDEX)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is an index",
-						RelationGetRelationName(r))));
-	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is a composite type",
-						RelationGetRelationName(r))));
-
-	return r;
-}
-
-/* ----------------
- *		heap_openrv - open a heap relation specified
- *		by a RangeVar node
- *
- *		As above, but relation is specified by a RangeVar.
- * ----------------
- */
-Relation
-heap_openrv(const RangeVar *relation, LOCKMODE lockmode)
-{
-	Relation	r;
-
-	r = relation_openrv(relation, lockmode);
-
-	if (r->rd_rel->relkind == RELKIND_INDEX)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is an index",
-						RelationGetRelationName(r))));
-	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is a composite type",
-						RelationGetRelationName(r))));
-
-	return r;
-}
-
-/* ----------------
- *		heap_openrv_extended - open a heap relation specified
- *		by a RangeVar node
- *
- *		As above, but optionally return NULL instead of failing for
- *		relation-not-found.
- * ----------------
- */
-Relation
-heap_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
-					 bool missing_ok)
-{
-	Relation	r;
-
-	r = relation_openrv_extended(relation, lockmode, missing_ok);
-
-	if (r)
-	{
-		if (r->rd_rel->relkind == RELKIND_INDEX)
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("\"%s\" is an index",
-							RelationGetRelationName(r))));
-		else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("\"%s\" is a composite type",
-							RelationGetRelationName(r))));
-	}
-
-	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)
-{
-	HeapScanDesc scan;
-
-	/*
-	 * increment relation ref count while scanning relation
-	 *
-	 * This is just to make really sure the relcache entry won't go away while
-	 * the scan has a pointer to it.  Caller should be holding the rel open
-	 * anyway, so this is redundant in all normal scenarios...
-	 */
-	RelationIncrementReferenceCount(relation);
-
-	/*
-	 * allocate and initialize scan descriptor
-	 */
-	scan = (HeapScanDesc) palloc(sizeof(HeapScanDescData));
-
-	scan->rs_rd = relation;
-	scan->rs_snapshot = snapshot;
-	scan->rs_nkeys = nkeys;
-	scan->rs_bitmapscan = is_bitmapscan;
-	scan->rs_samplescan = is_samplescan;
-	scan->rs_strategy = NULL;	/* set in initscan */
-	scan->rs_allow_strat = allow_strat;
-	scan->rs_allow_sync = allow_sync;
-	scan->rs_temp_snap = temp_snap;
-	scan->rs_parallel = parallel_scan;
-
-	/*
-	 * we can use page-at-a-time mode if it's an MVCC-safe snapshot
-	 */
-	scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(snapshot);
-
-	/*
-	 * For a seqscan in a serializable transaction, acquire a predicate lock
-	 * on the entire relation. This is required not only to lock all the
-	 * matching tuples, but also to conflict with new insertions into the
-	 * table. In an indexscan, we take page locks on the index pages covering
-	 * the range specified in the scan qual, but in a heap scan there is
-	 * nothing more fine-grained to lock. A bitmap scan is a different story,
-	 * there we have already scanned the index and locked the index pages
-	 * covering the predicate. But in that case we still have to lock any
-	 * matching heap tuples.
-	 */
-	if (!is_bitmapscan)
-		PredicateLockRelation(relation, snapshot);
-
-	/* we only need to set this up once */
-	scan->rs_ctup.t_tableOid = RelationGetRelid(relation);
-
-	/*
-	 * we do this here instead of in initscan() because heap_rescan also calls
-	 * initscan() and we don't want to allocate memory again
-	 */
-	if (nkeys > 0)
-		scan->rs_key = (ScanKey) palloc(sizeof(ScanKeyData) * nkeys);
-	else
-		scan->rs_key = NULL;
-
-	initscan(scan, key, false);
-
-	return scan;
-}
-
+
 /* ----------------
- *		heap_rescan		- restart a relation scan
+ *		heap_open - open a heap relation by relation OID
+ *
+ *		This is essentially relation_open plus check that the relation
+ *		is not an index nor a composite type.  (The caller should also
+ *		check that it's not a view or foreign table before assuming it has
+ *		storage.)
  * ----------------
  */
-void
-heap_rescan(HeapScanDesc scan,
-			ScanKey key)
+Relation
+heap_open(Oid relationId, LOCKMODE lockmode)
 {
-	/*
-	 * unpin scan buffers
-	 */
-	if (BufferIsValid(scan->rs_cbuf))
-		ReleaseBuffer(scan->rs_cbuf);
+	Relation	r;
 
-	/*
-	 * reinitialize scan descriptor
-	 */
-	initscan(scan, key, true);
+	r = relation_open(relationId, lockmode);
+
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is an index",
+						RelationGetRelationName(r))));
+	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is a composite type",
+						RelationGetRelationName(r))));
+
+	return r;
 }
 
 /* ----------------
- *		heap_rescan_set_params	- restart a relation scan after changing params
+ *		heap_openrv - open a heap relation specified
+ *		by a RangeVar node
  *
- * 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.
+ *		As above, but relation is specified by a RangeVar.
  * ----------------
  */
-void
-heap_rescan_set_params(HeapScanDesc scan, ScanKey key,
-					   bool allow_strat, bool allow_sync, bool allow_pagemode)
+Relation
+heap_openrv(const RangeVar *relation, LOCKMODE lockmode)
 {
-	/* 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);
+	Relation	r;
+
+	r = relation_openrv(relation, lockmode);
+
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is an index",
+						RelationGetRelationName(r))));
+	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is a composite type",
+						RelationGetRelationName(r))));
+
+	return r;
 }
 
 /* ----------------
- *		heap_endscan	- end relation scan
+ *		heap_openrv_extended - open a heap relation specified
+ *		by a RangeVar node
  *
- *		See how to integrate with index scans.
- *		Check handling if reldesc caching.
+ *		As above, but optionally return NULL instead of failing for
+ *		relation-not-found.
  * ----------------
  */
-void
-heap_endscan(HeapScanDesc scan)
+Relation
+heap_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
+					 bool missing_ok)
 {
-	/* Note: no locking manipulations needed */
-
-	/*
-	 * unpin scan buffers
-	 */
-	if (BufferIsValid(scan->rs_cbuf))
-		ReleaseBuffer(scan->rs_cbuf);
-
-	/*
-	 * decrement relation reference count and free scan descriptor storage
-	 */
-	RelationDecrementReferenceCount(scan->rs_rd);
-
-	if (scan->rs_key)
-		pfree(scan->rs_key);
+	Relation	r;
 
-	if (scan->rs_strategy != NULL)
-		FreeAccessStrategy(scan->rs_strategy);
+	r = relation_openrv_extended(relation, lockmode, missing_ok);
 
-	if (scan->rs_temp_snap)
-		UnregisterSnapshot(scan->rs_snapshot);
+	if (r)
+	{
+		if (r->rd_rel->relkind == RELKIND_INDEX)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("\"%s\" is an index",
+							RelationGetRelationName(r))));
+		else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("\"%s\" is a composite type",
+							RelationGetRelationName(r))));
+	}
 
-	pfree(scan);
+	return r;
 }
 
 /* ----------------
@@ -1550,384 +586,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
- *
- *		Determine where the parallel seq scan should start.  This function may
- *		be called many times, once by each parallel worker.  We must be careful
- *		only to set the startblock once.
- * ----------------
- */
-static void
-heap_parallelscan_startblock_init(HeapScanDesc scan)
-{
-	BlockNumber sync_startpage = InvalidBlockNumber;
-	ParallelHeapScanDesc parallel_scan;
-
-	Assert(scan->rs_parallel);
-	parallel_scan = scan->rs_parallel;
-
-retry:
-	/* Grab the spinlock. */
-	SpinLockAcquire(&parallel_scan->phs_mutex);
-
-	/*
-	 * If the scan's startblock has not yet been initialized, we must do so
-	 * now.  If this is not a synchronized scan, we just start at block 0, but
-	 * if it is a synchronized scan, we must get the starting position from
-	 * the synchronized scan machinery.  We can't hold the spinlock while
-	 * doing that, though, so release the spinlock, get the information we
-	 * need, and retry.  If nobody else has initialized the scan in the
-	 * meantime, we'll fill in the value we fetched on the second time
-	 * through.
-	 */
-	if (parallel_scan->phs_startblock == InvalidBlockNumber)
-	{
-		if (!parallel_scan->phs_syncscan)
-			parallel_scan->phs_startblock = 0;
-		else if (sync_startpage != InvalidBlockNumber)
-			parallel_scan->phs_startblock = sync_startpage;
-		else
-		{
-			SpinLockRelease(&parallel_scan->phs_mutex);
-			sync_startpage = ss_get_location(scan->rs_rd, scan->rs_nblocks);
-			goto retry;
-		}
-	}
-	SpinLockRelease(&parallel_scan->phs_mutex);
-}
-
-/* ----------------
- *		heap_parallelscan_nextpage - get the next page to scan
- *
- *		Get the next page to scan.  Even if there are no pages left to scan,
- *		another backend could have grabbed a page to scan and not yet finished
- *		looking at it, so it doesn't follow that the scan is done when the
- *		first backend gets an InvalidBlockNumber return.
- * ----------------
- */
-static BlockNumber
-heap_parallelscan_nextpage(HeapScanDesc scan)
-{
-	BlockNumber page;
-	ParallelHeapScanDesc parallel_scan;
-	uint64		nallocated;
-
-	Assert(scan->rs_parallel);
-	parallel_scan = scan->rs_parallel;
-
-	/*
-	 * phs_nallocated tracks how many pages have been allocated to workers
-	 * already.  When phs_nallocated >= rs_nblocks, all blocks have been
-	 * allocated.
-	 *
-	 * Because we use an atomic fetch-and-add to fetch the current value, the
-	 * phs_nallocated counter will exceed rs_nblocks, because workers will
-	 * still increment the value, when they try to allocate the next block but
-	 * all blocks have been allocated already. The counter must be 64 bits
-	 * wide because of that, to avoid wrapping around when rs_nblocks is close
-	 * to 2^32.
-	 *
-	 * The actual page to return is calculated by adding the counter to the
-	 * starting block number, modulo nblocks.
-	 */
-	nallocated = pg_atomic_fetch_add_u64(&parallel_scan->phs_nallocated, 1);
-	if (nallocated >= scan->rs_nblocks)
-		page = InvalidBlockNumber;	/* all blocks have been allocated */
-	else
-		page = (nallocated + parallel_scan->phs_startblock) % scan->rs_nblocks;
-
-	/*
-	 * Report scan location.  Normally, we report the current page number.
-	 * When we reach the end of the scan, though, we report the starting page,
-	 * not the ending page, just so the starting positions for later scans
-	 * 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 (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);
-	}
-
-	return page;
-}
-
-/* ----------------
- *		heap_update_snapshot
- *
- *		Update snapshot info in heap scan descriptor.
- * ----------------
- */
-void
-heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
-{
-	Assert(IsMVCCSnapshot(snapshot));
-
-	RegisterSnapshot(snapshot);
-	scan->rs_snapshot = snapshot;
-	scan->rs_temp_snap = true;
-}
-
-/* ----------------
- *		heap_getnext	- retrieve next tuple in scan
- *
- *		Fix to work with index relations.
- *		We don't return the buffer anymore, but you can get it from the
- *		returned HeapTuple.
- * ----------------
- */
-
-#ifdef HEAPDEBUGALL
-#define HEAPDEBUG_1 \
-	elog(DEBUG2, "heap_getnext([%s,nkeys=%d],dir=%d) called", \
-		 RelationGetRelationName(scan->rs_rd), scan->rs_nkeys, (int) direction)
-#define HEAPDEBUG_2 \
-	elog(DEBUG2, "heap_getnext returning EOS")
-#define HEAPDEBUG_3 \
-	elog(DEBUG2, "heap_getnext returning tuple")
-#else
-#define HEAPDEBUG_1
-#define HEAPDEBUG_2
-#define HEAPDEBUG_3
-#endif							/* !defined(HEAPDEBUGALL) */
-
-
-HeapTuple
-heap_getnext(HeapScanDesc scan, ScanDirection direction)
-{
-	/* Note: no locking manipulations needed */
-
-	HEAPDEBUG_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)
-	{
-		HEAPDEBUG_2;			/* heap_getnext returning EOS */
-		return NULL;
-	}
-
-	/*
-	 * if we get here it means we have a new current scan tuple, so point to
-	 * the proper return buffer and return the tuple.
-	 */
-	HEAPDEBUG_3;				/* heap_getnext returning tuple */
-
-	pgstat_count_heap_getnext(scan->rs_rd);
-
-	return &(scan->rs_ctup);
-}
-
-/*
- *	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
-heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
-					   Snapshot snapshot, HeapTuple heapTuple,
-					   bool *all_dead, bool first_call)
-{
-	Page		dp = (Page) BufferGetPage(buffer);
-	TransactionId prev_xmax = InvalidTransactionId;
-	OffsetNumber offnum;
-	bool		at_chain_start;
-	bool		valid;
-	bool		skip;
-
-	/* If this is not the first call, previous call returned a (live!) tuple */
-	if (all_dead)
-		*all_dead = first_call;
-
-	Assert(TransactionIdIsValid(RecentGlobalXmin));
-
-	Assert(ItemPointerGetBlockNumber(tid) == BufferGetBlockNumber(buffer));
-	offnum = ItemPointerGetOffsetNumber(tid);
-	at_chain_start = first_call;
-	skip = !first_call;
-
-	heapTuple->t_self = *tid;
-
-	/* Scan through possible multiple members of HOT-chain */
-	for (;;)
-	{
-		ItemId		lp;
-
-		/* check for bogus TID */
-		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(dp))
-			break;
-
-		lp = PageGetItemId(dp, offnum);
-
-		/* check for unused, dead, or redirected items */
-		if (!ItemIdIsNormal(lp))
-		{
-			/* We should only see a redirect at start of chain */
-			if (ItemIdIsRedirected(lp) && at_chain_start)
-			{
-				/* Follow the redirect */
-				offnum = ItemIdGetRedirect(lp);
-				at_chain_start = false;
-				continue;
-			}
-			/* else must be end of chain */
-			break;
-		}
-
-		heapTuple->t_data = (HeapTupleHeader) PageGetItem(dp, lp);
-		heapTuple->t_len = ItemIdGetLength(lp);
-		heapTuple->t_tableOid = RelationGetRelid(relation);
-		ItemPointerSetOffsetNumber(&heapTuple->t_self, offnum);
-
-		/*
-		 * Shouldn't see a HEAP_ONLY tuple at chain start.
-		 */
-		if (at_chain_start && HeapTupleIsHeapOnly(heapTuple))
-			break;
-
-		/*
-		 * The xmin should match the previous xmax value, else chain is
-		 * broken.
-		 */
-		if (TransactionIdIsValid(prev_xmax) &&
-			!TransactionIdEquals(prev_xmax,
-								 HeapTupleHeaderGetXmin(heapTuple->t_data)))
-			break;
-
-		/*
-		 * When first_call is true (and thus, skip is initially false) we'll
-		 * return the first tuple we find.  But on later passes, heapTuple
-		 * will initially be pointing to the tuple we returned last time.
-		 * Returning it again would be incorrect (and would loop forever), so
-		 * we skip it and return the next match we find.
-		 */
-		if (!skip)
-		{
-			/*
-			 * For the benefit of logical decoding, have t_self point at the
-			 * element of the HOT chain we're currently investigating instead
-			 * of the root tuple of the HOT chain. This is important because
-			 * the *Satisfies routine for historical mvcc snapshots needs the
-			 * correct tid to decide about the visibility in some cases.
-			 */
-			ItemPointerSet(&(heapTuple->t_self), BufferGetBlockNumber(buffer), offnum);
-
-			/* If it's visible per the snapshot, we must return it */
-			valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, heapTuple, snapshot, buffer);
-			CheckForSerializableConflictOut(valid, relation, heapTuple,
-											buffer, snapshot);
-			/* reset to original, non-redirected, tid */
-			heapTuple->t_self = *tid;
-
-			if (valid)
-			{
-				ItemPointerSetOffsetNumber(tid, offnum);
-				PredicateLockTuple(relation, heapTuple, snapshot);
-				if (all_dead)
-					*all_dead = false;
-				return true;
-			}
-		}
-		skip = false;
-
-		/*
-		 * If we can't see it, maybe no one else can either.  At caller
-		 * request, check whether all chain members are dead to all
-		 * transactions.
-		 *
-		 * Note: if you change the criterion here for what is "dead", fix the
-		 * planner's get_actual_variable_range() function to match.
-		 */
-		if (all_dead && *all_dead &&
-			!HeapTupleIsSurelyDead(heapTuple, RecentGlobalXmin))
-			*all_dead = false;
-
-		/*
-		 * Check to see if HOT chain continues past this tuple; if so fetch
-		 * the next offnum and loop around.
-		 */
-		if (HeapTupleIsHotUpdated(heapTuple))
-		{
-			Assert(ItemPointerGetBlockNumber(&heapTuple->t_data->t_ctid) ==
-				   ItemPointerGetBlockNumber(tid));
-			offnum = ItemPointerGetOffsetNumber(&heapTuple->t_data->t_ctid);
-			at_chain_start = false;
-			prev_xmax = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
-		}
-		else
-			break;				/* end of chain */
-	}
-
-	return false;
-}
-
-/*
- *	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
-heap_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 = heap_hot_search_buffer(tid, relation, buffer, snapshot,
-									&heapTuple, all_dead, true);
-	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-	ReleaseBuffer(buffer);
-	return result;
-}
-
-
 /*
  * UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends
  *
@@ -4762,32 +3420,6 @@ heap_execute_freeze_tuple(HeapTupleHeader tuple, xl_heap_freeze_tuple *frz)
 	tuple->t_infomask2 = frz->t_infomask2;
 }
 
-/*
- * heap_freeze_tuple
- *		Freeze tuple in place, without WAL logging.
- *
- * Useful for callers like CLUSTER that perform their own WAL logging.
- */
-bool
-heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
-				  TransactionId cutoff_multi)
-{
-	xl_heap_freeze_tuple frz;
-	bool		do_freeze;
-	bool		tuple_totally_frozen;
-
-	do_freeze = heap_prepare_freeze_tuple(tuple, cutoff_xid, cutoff_multi,
-										  &frz, &tuple_totally_frozen);
-
-	/*
-	 * Note that because this is not a WAL-logged operation, we don't need to
-	 * fill in the offset in the freeze record.
-	 */
-
-	if (do_freeze)
-		heap_execute_freeze_tuple(tuple, &frz);
-	return do_freeze;
-}
 
 /*
  * For a given MultiXactId, return the hint bits that should be set in the
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index a0e3272f67..12a8f56456 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -1666,6 +1666,1094 @@ HeapTupleSatisfiesHistoricMVCC(StorageTuple stup, Snapshot snapshot,
 		return true;
 }
 
+/* ----------------------------------------------------------------
+ *						 heap support routines
+ * ----------------------------------------------------------------
+ */
+
+/* ----------------
+ *		heap_parallelscan_startblock_init - find and set the scan's startblock
+ *
+ *		Determine where the parallel seq scan should start.  This function may
+ *		be called many times, once by each parallel worker.  We must be careful
+ *		only to set the startblock once.
+ * ----------------
+ */
+static void
+heap_parallelscan_startblock_init(HeapScanDesc scan)
+{
+	BlockNumber sync_startpage = InvalidBlockNumber;
+	ParallelHeapScanDesc parallel_scan;
+
+	Assert(scan->rs_parallel);
+	parallel_scan = scan->rs_parallel;
+
+retry:
+	/* Grab the spinlock. */
+	SpinLockAcquire(&parallel_scan->phs_mutex);
+
+	/*
+	 * If the scan's startblock has not yet been initialized, we must do so
+	 * now.  If this is not a synchronized scan, we just start at block 0, but
+	 * if it is a synchronized scan, we must get the starting position from
+	 * the synchronized scan machinery.  We can't hold the spinlock while
+	 * doing that, though, so release the spinlock, get the information we
+	 * need, and retry.  If nobody else has initialized the scan in the
+	 * meantime, we'll fill in the value we fetched on the second time
+	 * through.
+	 */
+	if (parallel_scan->phs_startblock == InvalidBlockNumber)
+	{
+		if (!parallel_scan->phs_syncscan)
+			parallel_scan->phs_startblock = 0;
+		else if (sync_startpage != InvalidBlockNumber)
+			parallel_scan->phs_startblock = sync_startpage;
+		else
+		{
+			SpinLockRelease(&parallel_scan->phs_mutex);
+			sync_startpage = ss_get_location(scan->rs_rd, scan->rs_nblocks);
+			goto retry;
+		}
+	}
+	SpinLockRelease(&parallel_scan->phs_mutex);
+}
+
+/* ----------------
+ *		heap_parallelscan_nextpage - get the next page to scan
+ *
+ *		Get the next page to scan.  Even if there are no pages left to scan,
+ *		another backend could have grabbed a page to scan and not yet finished
+ *		looking at it, so it doesn't follow that the scan is done when the
+ *		first backend gets an InvalidBlockNumber return.
+ * ----------------
+ */
+static BlockNumber
+heap_parallelscan_nextpage(HeapScanDesc scan)
+{
+	BlockNumber page;
+	ParallelHeapScanDesc parallel_scan;
+	uint64		nallocated;
+
+	Assert(scan->rs_parallel);
+	parallel_scan = scan->rs_parallel;
+
+	/*
+	 * phs_nallocated tracks how many pages have been allocated to workers
+	 * already.  When phs_nallocated >= rs_nblocks, all blocks have been
+	 * allocated.
+	 *
+	 * Because we use an atomic fetch-and-add to fetch the current value, the
+	 * phs_nallocated counter will exceed rs_nblocks, because workers will
+	 * still increment the value, when they try to allocate the next block but
+	 * all blocks have been allocated already. The counter must be 64 bits
+	 * wide because of that, to avoid wrapping around when rs_nblocks is close
+	 * to 2^32.
+	 *
+	 * The actual page to return is calculated by adding the counter to the
+	 * starting block number, modulo nblocks.
+	 */
+	nallocated = pg_atomic_fetch_add_u64(&parallel_scan->phs_nallocated, 1);
+	if (nallocated >= scan->rs_nblocks)
+		page = InvalidBlockNumber;	/* all blocks have been allocated */
+	else
+		page = (nallocated + parallel_scan->phs_startblock) % scan->rs_nblocks;
+
+	/*
+	 * Report scan location.  Normally, we report the current page number.
+	 * When we reach the end of the scan, though, we report the starting page,
+	 * not the ending page, just so the starting positions for later scans
+	 * 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 (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);
+	}
+
+	return page;
+}
+
+
+/* ----------------
+ *		initscan - scan code common to heap_beginscan and heap_rescan
+ * ----------------
+ */
+static void
+initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
+{
+	bool		allow_strat;
+	bool		allow_sync;
+
+	/*
+	 * Determine the number of blocks we have to scan.
+	 *
+	 * It is sufficient to do this once at scan start, since any tuples added
+	 * while the scan is in progress will be invisible to my snapshot anyway.
+	 * (That is not true when using a non-MVCC snapshot.  However, we couldn't
+	 * guarantee to return tuples added after scan start anyway, since they
+	 * might go into pages we already scanned.  To guarantee consistent
+	 * results for a non-MVCC snapshot, the caller must hold some higher-level
+	 * lock that ensures the interesting tuple(s) won't change.)
+	 */
+	if (scan->rs_parallel != NULL)
+		scan->rs_nblocks = scan->rs_parallel->phs_nblocks;
+	else
+		scan->rs_nblocks = RelationGetNumberOfBlocks(scan->rs_rd);
+
+	/*
+	 * If the table is large relative to NBuffers, use a bulk-read access
+	 * strategy and enable synchronized scanning (see syncscan.c).  Although
+	 * the thresholds for these features could be different, we make them the
+	 * same so that there are only two behaviors to tune rather than four.
+	 * (However, some callers need to be able to disable one or both of these
+	 * behaviors, independently of the size of the table; also there is a GUC
+	 * variable that can disable synchronized scanning.)
+	 *
+	 * 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)
+	{
+		allow_strat = scan->rs_allow_strat;
+		allow_sync = scan->rs_allow_sync;
+	}
+	else
+		allow_strat = allow_sync = false;
+
+	if (allow_strat)
+	{
+		/* During a rescan, keep the previous strategy object. */
+		if (scan->rs_strategy == NULL)
+			scan->rs_strategy = GetAccessStrategy(BAS_BULKREAD);
+	}
+	else
+	{
+		if (scan->rs_strategy != NULL)
+			FreeAccessStrategy(scan->rs_strategy);
+		scan->rs_strategy = NULL;
+	}
+
+	if (scan->rs_parallel != NULL)
+	{
+		/* For parallel scan, believe whatever ParallelHeapScanDesc says. */
+		scan->rs_syncscan = scan->rs_parallel->phs_syncscan;
+	}
+	else if (keep_startblock)
+	{
+		/*
+		 * When rescanning, we want to keep the previous startblock setting,
+		 * so that rewinding a cursor doesn't generate surprising results.
+		 * Reset the active syncscan setting, though.
+		 */
+		scan->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);
+	}
+	else
+	{
+		scan->rs_syncscan = false;
+		scan->rs_startblock = 0;
+	}
+
+	scan->rs_numblocks = InvalidBlockNumber;
+	scan->rs_inited = false;
+	scan->rs_ctup.t_data = NULL;
+	ItemPointerSetInvalid(&scan->rs_ctup.t_self);
+	scan->rs_cbuf = InvalidBuffer;
+	scan->rs_cblock = InvalidBlockNumber;
+
+	/* page-at-a-time fields are always invalid when not rs_inited */
+
+	/*
+	 * copy the scan key, if appropriate
+	 */
+	if (key != NULL)
+		memcpy(scan->rs_key, key, scan->rs_nkeys * sizeof(ScanKeyData));
+
+	/*
+	 * Currently, we don't have a stats counter for bitmap heap scans (but the
+	 * underlying bitmap index scans will be counted) or sample scans (we only
+	 * update stats for tuple fetches there)
+	 */
+	if (!scan->rs_bitmapscan && !scan->rs_samplescan)
+		pgstat_count_heap_scan(scan->rs_rd);
+}
+
+
+/* ----------------
+ *		heapgettup - fetch next heap tuple
+ *
+ *		Initialize the scan if not already done; then advance to the next
+ *		tuple as indicated by "dir"; return the next tuple in scan->rs_ctup,
+ *		or set scan->rs_ctup.t_data = NULL if no more tuples.
+ *
+ * dir == NoMovementScanDirection means "re-fetch the tuple indicated
+ * by scan->rs_ctup".
+ *
+ * Note: the reason nkeys/key are passed separately, even though they are
+ * kept in the scan descriptor, is that the caller may not want us to check
+ * the scankeys.
+ *
+ * Note: when we fall off the end of the scan in either direction, we
+ * reset rs_inited.  This means that a further request with the same
+ * scan direction will restart the scan, which is a bit odd, but a
+ * request with the opposite scan direction will start a fresh scan
+ * in the proper direction.  The latter is required behavior for cursors,
+ * while the former case is generally undefined behavior in Postgres
+ * so we don't care too much.
+ * ----------------
+ */
+static void
+heapgettup(HeapScanDesc scan,
+		   ScanDirection dir,
+		   int nkeys,
+		   ScanKey key)
+{
+	HeapTuple	tuple = &(scan->rs_ctup);
+	Snapshot	snapshot = scan->rs_snapshot;
+	bool		backward = ScanDirectionIsBackward(dir);
+	BlockNumber page;
+	bool		finished;
+	Page		dp;
+	int			lines;
+	OffsetNumber lineoff;
+	int			linesleft;
+	ItemId		lpp;
+
+	/*
+	 * calculate next starting lineoff, given scan direction
+	 */
+	if (ScanDirectionIsForward(dir))
+	{
+		if (!scan->rs_inited)
+		{
+			/*
+			 * return null immediately if relation is empty
+			 */
+			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			{
+				Assert(!BufferIsValid(scan->rs_cbuf));
+				tuple->t_data = NULL;
+				return;
+			}
+			if (scan->rs_parallel != NULL)
+			{
+				heap_parallelscan_startblock_init(scan);
+
+				page = heap_parallelscan_nextpage(scan);
+
+				/* Other processes might have already finished the scan. */
+				if (page == InvalidBlockNumber)
+				{
+					Assert(!BufferIsValid(scan->rs_cbuf));
+					tuple->t_data = NULL;
+					return;
+				}
+			}
+			else
+				page = scan->rs_startblock; /* first page */
+			heapgetpage(scan, page);
+			lineoff = FirstOffsetNumber;	/* first offnum */
+			scan->rs_inited = true;
+		}
+		else
+		{
+			/* continue from previously returned page/tuple */
+			page = scan->rs_cblock; /* current page */
+			lineoff =			/* next offnum */
+				OffsetNumberNext(ItemPointerGetOffsetNumber(&(tuple->t_self)));
+		}
+
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		lines = PageGetMaxOffsetNumber(dp);
+		/* page and lineoff now reference the physically next tid */
+
+		linesleft = lines - lineoff + 1;
+	}
+	else if (backward)
+	{
+		/* backward parallel scan not supported */
+		Assert(scan->rs_parallel == NULL);
+
+		if (!scan->rs_inited)
+		{
+			/*
+			 * return null immediately if relation is empty
+			 */
+			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			{
+				Assert(!BufferIsValid(scan->rs_cbuf));
+				tuple->t_data = NULL;
+				return;
+			}
+
+			/*
+			 * Disable reporting to syncscan logic in a backwards scan; it's
+			 * not very likely anyone else is doing the same thing at the same
+			 * time, and much more likely that we'll just bollix things for
+			 * forward scanners.
+			 */
+			scan->rs_syncscan = false;
+			/* start from last page of the scan */
+			if (scan->rs_startblock > 0)
+				page = scan->rs_startblock - 1;
+			else
+				page = scan->rs_nblocks - 1;
+			heapgetpage(scan, page);
+		}
+		else
+		{
+			/* continue from previously returned page/tuple */
+			page = scan->rs_cblock; /* current page */
+		}
+
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		lines = PageGetMaxOffsetNumber(dp);
+
+		if (!scan->rs_inited)
+		{
+			lineoff = lines;	/* final offnum */
+			scan->rs_inited = true;
+		}
+		else
+		{
+			lineoff =			/* previous offnum */
+				OffsetNumberPrev(ItemPointerGetOffsetNumber(&(tuple->t_self)));
+		}
+		/* page and lineoff now reference the physically previous tid */
+
+		linesleft = lineoff;
+	}
+	else
+	{
+		/*
+		 * ``no movement'' scan direction: refetch prior tuple
+		 */
+		if (!scan->rs_inited)
+		{
+			Assert(!BufferIsValid(scan->rs_cbuf));
+			tuple->t_data = NULL;
+			return;
+		}
+
+		page = ItemPointerGetBlockNumber(&(tuple->t_self));
+		if (page != scan->rs_cblock)
+			heapgetpage(scan, page);
+
+		/* Since the tuple was previously fetched, needn't lock page here */
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
+		lpp = PageGetItemId(dp, lineoff);
+		Assert(ItemIdIsNormal(lpp));
+
+		tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+		tuple->t_len = ItemIdGetLength(lpp);
+
+		return;
+	}
+
+	/*
+	 * advance the scan until we find a qualifying tuple or run out of stuff
+	 * to scan
+	 */
+	lpp = PageGetItemId(dp, lineoff);
+	for (;;)
+	{
+		while (linesleft > 0)
+		{
+			if (ItemIdIsNormal(lpp))
+			{
+				bool		valid;
+
+				tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+				tuple->t_len = ItemIdGetLength(lpp);
+				ItemPointerSet(&(tuple->t_self), page, lineoff);
+
+				/*
+				 * if current tuple qualifies, return it.
+				 */
+				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine,
+													 tuple,
+													 snapshot,
+													 scan->rs_cbuf);
+
+				CheckForSerializableConflictOut(valid, scan->rs_rd, tuple,
+												scan->rs_cbuf, snapshot);
+
+				if (valid && key != NULL)
+					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
+								nkeys, key, valid);
+
+				if (valid)
+				{
+					LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+					return;
+				}
+			}
+
+			/*
+			 * otherwise move to the next item on the page
+			 */
+			--linesleft;
+			if (backward)
+			{
+				--lpp;			/* move back in this page's ItemId array */
+				--lineoff;
+			}
+			else
+			{
+				++lpp;			/* move forward in this page's ItemId array */
+				++lineoff;
+			}
+		}
+
+		/*
+		 * 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);
+
+		/*
+		 * 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);
+			if (page == 0)
+				page = scan->rs_nblocks;
+			page--;
+		}
+		else if (scan->rs_parallel != NULL)
+		{
+			page = heap_parallelscan_nextpage(scan);
+			finished = (page == InvalidBlockNumber);
+		}
+		else
+		{
+			page++;
+			if (page >= scan->rs_nblocks)
+				page = 0;
+			finished = (page == scan->rs_startblock) ||
+				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+
+			/*
+			 * Report our new scan position for synchronization purposes. We
+			 * don't do that when moving backwards, however. That would just
+			 * mess up any other forward-moving scanners.
+			 *
+			 * Note: we do this before checking for end of scan so that the
+			 * final state of the position hint is back at the start of the
+			 * rel.  That's not strictly necessary, but otherwise when you run
+			 * the same query multiple times the starting position would shift
+			 * 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);
+		}
+
+		/*
+		 * return NULL if we've exhausted all the pages
+		 */
+		if (finished)
+		{
+			if (BufferIsValid(scan->rs_cbuf))
+				ReleaseBuffer(scan->rs_cbuf);
+			scan->rs_cbuf = InvalidBuffer;
+			scan->rs_cblock = InvalidBlockNumber;
+			tuple->t_data = NULL;
+			scan->rs_inited = false;
+			return;
+		}
+
+		heapgetpage(scan, page);
+
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		lines = PageGetMaxOffsetNumber((Page) dp);
+		linesleft = lines;
+		if (backward)
+		{
+			lineoff = lines;
+			lpp = PageGetItemId(dp, lines);
+		}
+		else
+		{
+			lineoff = FirstOffsetNumber;
+			lpp = PageGetItemId(dp, FirstOffsetNumber);
+		}
+	}
+}
+
+/* ----------------
+ *		heapgettup_pagemode - fetch next heap tuple in page-at-a-time mode
+ *
+ *		Same API as heapgettup, but used in page-at-a-time mode
+ *
+ * The internal logic is much the same as heapgettup's too, but there are some
+ * differences: we do not take the buffer content lock (that only needs to
+ * happen inside heapgetpage), and we iterate through just the tuples listed
+ * in rs_vistuples[] rather than all tuples on the page.  Notice that
+ * lineindex is 0-based, where the corresponding loop variable lineoff in
+ * heapgettup is 1-based.
+ * ----------------
+ */
+static void
+heapgettup_pagemode(HeapScanDesc scan,
+					ScanDirection dir,
+					int nkeys,
+					ScanKey key)
+{
+	HeapTuple	tuple = &(scan->rs_ctup);
+	bool		backward = ScanDirectionIsBackward(dir);
+	BlockNumber page;
+	bool		finished;
+	Page		dp;
+	int			lines;
+	int			lineindex;
+	OffsetNumber lineoff;
+	int			linesleft;
+	ItemId		lpp;
+
+	/*
+	 * calculate next starting lineindex, given scan direction
+	 */
+	if (ScanDirectionIsForward(dir))
+	{
+		if (!scan->rs_inited)
+		{
+			/*
+			 * return null immediately if relation is empty
+			 */
+			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			{
+				Assert(!BufferIsValid(scan->rs_cbuf));
+				tuple->t_data = NULL;
+				return;
+			}
+			if (scan->rs_parallel != NULL)
+			{
+				heap_parallelscan_startblock_init(scan);
+
+				page = heap_parallelscan_nextpage(scan);
+
+				/* Other processes might have already finished the scan. */
+				if (page == InvalidBlockNumber)
+				{
+					Assert(!BufferIsValid(scan->rs_cbuf));
+					tuple->t_data = NULL;
+					return;
+				}
+			}
+			else
+				page = scan->rs_startblock; /* first page */
+			heapgetpage(scan, page);
+			lineindex = 0;
+			scan->rs_inited = true;
+		}
+		else
+		{
+			/* continue from previously returned page/tuple */
+			page = scan->rs_cblock; /* current page */
+			lineindex = scan->rs_cindex + 1;
+		}
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
+		lines = scan->rs_ntuples;
+		/* page and lineindex now reference the next visible tid */
+
+		linesleft = lines - lineindex;
+	}
+	else if (backward)
+	{
+		/* backward parallel scan not supported */
+		Assert(scan->rs_parallel == NULL);
+
+		if (!scan->rs_inited)
+		{
+			/*
+			 * return null immediately if relation is empty
+			 */
+			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			{
+				Assert(!BufferIsValid(scan->rs_cbuf));
+				tuple->t_data = NULL;
+				return;
+			}
+
+			/*
+			 * Disable reporting to syncscan logic in a backwards scan; it's
+			 * not very likely anyone else is doing the same thing at the same
+			 * time, and much more likely that we'll just bollix things for
+			 * forward scanners.
+			 */
+			scan->rs_syncscan = false;
+			/* start from last page of the scan */
+			if (scan->rs_startblock > 0)
+				page = scan->rs_startblock - 1;
+			else
+				page = scan->rs_nblocks - 1;
+			heapgetpage(scan, page);
+		}
+		else
+		{
+			/* continue from previously returned page/tuple */
+			page = scan->rs_cblock; /* current page */
+		}
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
+		lines = scan->rs_ntuples;
+
+		if (!scan->rs_inited)
+		{
+			lineindex = lines - 1;
+			scan->rs_inited = true;
+		}
+		else
+		{
+			lineindex = scan->rs_cindex - 1;
+		}
+		/* page and lineindex now reference the previous visible tid */
+
+		linesleft = lineindex + 1;
+	}
+	else
+	{
+		/*
+		 * ``no movement'' scan direction: refetch prior tuple
+		 */
+		if (!scan->rs_inited)
+		{
+			Assert(!BufferIsValid(scan->rs_cbuf));
+			tuple->t_data = NULL;
+			return;
+		}
+
+		page = ItemPointerGetBlockNumber(&(tuple->t_self));
+		if (page != scan->rs_cblock)
+			heapgetpage(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);
+		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
+		lpp = PageGetItemId(dp, lineoff);
+		Assert(ItemIdIsNormal(lpp));
+
+		tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+		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]);
+
+		return;
+	}
+
+	/*
+	 * advance the scan until we find a qualifying tuple or run out of stuff
+	 * to scan
+	 */
+	for (;;)
+	{
+		while (linesleft > 0)
+		{
+			lineoff = scan->rs_vistuples[lineindex];
+			lpp = PageGetItemId(dp, lineoff);
+			Assert(ItemIdIsNormal(lpp));
+
+			tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+			tuple->t_len = ItemIdGetLength(lpp);
+			ItemPointerSet(&(tuple->t_self), page, lineoff);
+
+			/*
+			 * if current tuple qualifies, return it.
+			 */
+			if (key != NULL)
+			{
+				bool		valid;
+
+				HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
+							nkeys, key, valid);
+				if (valid)
+				{
+					scan->rs_cindex = lineindex;
+					return;
+				}
+			}
+			else
+			{
+				scan->rs_cindex = lineindex;
+				return;
+			}
+
+			/*
+			 * otherwise move to the next item on the page
+			 */
+			--linesleft;
+			if (backward)
+				--lineindex;
+			else
+				++lineindex;
+		}
+
+		/*
+		 * if we get here, it means we've exhausted the items on this page and
+		 * it's time to move to the next.
+		 */
+		if (backward)
+		{
+			finished = (page == scan->rs_startblock) ||
+				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+			if (page == 0)
+				page = scan->rs_nblocks;
+			page--;
+		}
+		else if (scan->rs_parallel != NULL)
+		{
+			page = heap_parallelscan_nextpage(scan);
+			finished = (page == InvalidBlockNumber);
+		}
+		else
+		{
+			page++;
+			if (page >= scan->rs_nblocks)
+				page = 0;
+			finished = (page == scan->rs_startblock) ||
+				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+
+			/*
+			 * Report our new scan position for synchronization purposes. We
+			 * don't do that when moving backwards, however. That would just
+			 * mess up any other forward-moving scanners.
+			 *
+			 * Note: we do this before checking for end of scan so that the
+			 * final state of the position hint is back at the start of the
+			 * rel.  That's not strictly necessary, but otherwise when you run
+			 * the same query multiple times the starting position would shift
+			 * 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);
+		}
+
+		/*
+		 * return NULL if we've exhausted all the pages
+		 */
+		if (finished)
+		{
+			if (BufferIsValid(scan->rs_cbuf))
+				ReleaseBuffer(scan->rs_cbuf);
+			scan->rs_cbuf = InvalidBuffer;
+			scan->rs_cblock = InvalidBlockNumber;
+			tuple->t_data = NULL;
+			scan->rs_inited = false;
+			return;
+		}
+
+		heapgetpage(scan, page);
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
+		lines = scan->rs_ntuples;
+		linesleft = lines;
+		if (backward)
+			lineindex = lines - 1;
+		else
+			lineindex = 0;
+	}
+}
+
+
+static HeapScanDesc
+heapam_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)
+{
+	HeapScanDesc scan;
+
+	/*
+	 * increment relation ref count while scanning relation
+	 *
+	 * This is just to make really sure the relcache entry won't go away while
+	 * the scan has a pointer to it.  Caller should be holding the rel open
+	 * anyway, so this is redundant in all normal scenarios...
+	 */
+	RelationIncrementReferenceCount(relation);
+
+	/*
+	 * allocate and initialize scan descriptor
+	 */
+	scan = (HeapScanDesc) palloc(sizeof(HeapScanDescData));
+
+	scan->rs_rd = relation;
+	scan->rs_snapshot = snapshot;
+	scan->rs_nkeys = nkeys;
+	scan->rs_bitmapscan = is_bitmapscan;
+	scan->rs_samplescan = is_samplescan;
+	scan->rs_strategy = NULL;	/* set in initscan */
+	scan->rs_allow_strat = allow_strat;
+	scan->rs_allow_sync = allow_sync;
+	scan->rs_temp_snap = temp_snap;
+	scan->rs_parallel = parallel_scan;
+
+	/*
+	 * we can use page-at-a-time mode if it's an MVCC-safe snapshot
+	 */
+	scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(snapshot);
+
+	/*
+	 * For a seqscan in a serializable transaction, acquire a predicate lock
+	 * on the entire relation. This is required not only to lock all the
+	 * matching tuples, but also to conflict with new insertions into the
+	 * table. In an indexscan, we take page locks on the index pages covering
+	 * the range specified in the scan qual, but in a heap scan there is
+	 * nothing more fine-grained to lock. A bitmap scan is a different story,
+	 * there we have already scanned the index and locked the index pages
+	 * covering the predicate. But in that case we still have to lock any
+	 * matching heap tuples.
+	 */
+	if (!is_bitmapscan)
+		PredicateLockRelation(relation, snapshot);
+
+	/* we only need to set this up once */
+	scan->rs_ctup.t_tableOid = RelationGetRelid(relation);
+
+	/*
+	 * we do this here instead of in initscan() because heap_rescan also calls
+	 * initscan() and we don't want to allocate memory again
+	 */
+	if (nkeys > 0)
+		scan->rs_key = (ScanKey) palloc(sizeof(ScanKeyData) * nkeys);
+	else
+		scan->rs_key = NULL;
+
+	initscan(scan, key, false);
+
+	return scan;
+}
+
+/* ----------------
+ *		heapam_rescan		- restart a relation scan
+ * ----------------
+ */
+static void
+heapam_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
+	 */
+	if (BufferIsValid(scan->rs_cbuf))
+		ReleaseBuffer(scan->rs_cbuf);
+
+	/*
+	 * reinitialize scan descriptor
+	 */
+	initscan(scan, key, true);
+
+	/*
+	 * 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);
+	}
+}
+
+/* ----------------
+ *		heapam_endscan	- end relation scan
+ *
+ *		See how to integrate with index scans.
+ *		Check handling if reldesc caching.
+ * ----------------
+ */
+static void
+heapam_endscan(HeapScanDesc scan)
+{
+	/* Note: no locking manipulations needed */
+
+	/*
+	 * unpin scan buffers
+	 */
+	if (BufferIsValid(scan->rs_cbuf))
+		ReleaseBuffer(scan->rs_cbuf);
+
+	/*
+	 * decrement relation reference count and free scan descriptor storage
+	 */
+	RelationDecrementReferenceCount(scan->rs_rd);
+
+	if (scan->rs_key)
+		pfree(scan->rs_key);
+
+	if (scan->rs_strategy != NULL)
+		FreeAccessStrategy(scan->rs_strategy);
+
+	if (scan->rs_temp_snap)
+		UnregisterSnapshot(scan->rs_snapshot);
+
+	pfree(scan);
+}
+
+/* ----------------
+ *		heapam_scan_update_snapshot
+ *
+ *		Update snapshot info in heap scan descriptor.
+ * ----------------
+ */
+static void
+heapam_scan_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
+{
+	Assert(IsMVCCSnapshot(snapshot));
+
+	RegisterSnapshot(snapshot);
+	scan->rs_snapshot = snapshot;
+	scan->rs_temp_snap = true;
+}
+
+/* ----------------
+ *		heapam_getnext	- retrieve next tuple in scan
+ *
+ *		Fix to work with index relations.
+ *		We don't return the buffer anymore, but you can get it from the
+ *		returned HeapTuple.
+ * ----------------
+ */
+
+#ifdef HEAPAMDEBUGALL
+#define HEAPAMDEBUG_1 \
+	elog(DEBUG2, "heapam_getnext([%s,nkeys=%d],dir=%d) called", \
+		 RelationGetRelationName(scan->rs_rd), scan->rs_nkeys, (int) direction)
+#define HEAPAMDEBUG_2 \
+	elog(DEBUG2, "heapam_getnext returning EOS")
+#define HEAPAMDEBUG_3 \
+	elog(DEBUG2, "heapam_getnext returning tuple")
+#else
+#define HEAPAMDEBUG_1
+#define HEAPAMDEBUG_2
+#define HEAPAMDEBUG_3
+#endif							/* !defined(HEAPDEBUGALL) */
+
+
+static StorageTuple
+heapam_getnext(HeapScanDesc sscan, ScanDirection direction)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	/* Note: no locking manipulations needed */
+
+	HEAPAMDEBUG_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)
+	{
+		HEAPAMDEBUG_2;			/* heap_getnext returning EOS */
+		return NULL;
+	}
+
+	/*
+	 * if we get here it means we have a new current scan tuple, so point to
+	 * the proper return buffer and return the tuple.
+	 */
+	HEAPAMDEBUG_3;				/* heap_getnext returning tuple */
+
+	pgstat_count_heap_getnext(scan->rs_rd);
+
+	return &(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
+
+static TupleTableSlot *
+heapam_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
@@ -1818,9 +2906,210 @@ heap_fetch(Relation relation,
 	return false;
 }
 
+/*
+ *	heapam_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.
+ */
+static bool
+heapam_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
+					   Snapshot snapshot, HeapTuple heapTuple,
+					   bool *all_dead, bool first_call)
+{
+	Page		dp = (Page) BufferGetPage(buffer);
+	TransactionId prev_xmax = InvalidTransactionId;
+	OffsetNumber offnum;
+	bool		at_chain_start;
+	bool		valid;
+	bool		skip;
+
+	/* If this is not the first call, previous call returned a (live!) tuple */
+	if (all_dead)
+		*all_dead = first_call;
+
+	Assert(TransactionIdIsValid(RecentGlobalXmin));
+
+	Assert(ItemPointerGetBlockNumber(tid) == BufferGetBlockNumber(buffer));
+	offnum = ItemPointerGetOffsetNumber(tid);
+	at_chain_start = first_call;
+	skip = !first_call;
+
+	heapTuple->t_self = *tid;
 
+	/* Scan through possible multiple members of HOT-chain */
+	for (;;)
+	{
+		ItemId		lp;
 
+		/* check for bogus TID */
+		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(dp))
+			break;
 
+		lp = PageGetItemId(dp, offnum);
+
+		/* check for unused, dead, or redirected items */
+		if (!ItemIdIsNormal(lp))
+		{
+			/* We should only see a redirect at start of chain */
+			if (ItemIdIsRedirected(lp) && at_chain_start)
+			{
+				/* Follow the redirect */
+				offnum = ItemIdGetRedirect(lp);
+				at_chain_start = false;
+				continue;
+			}
+			/* else must be end of chain */
+			break;
+		}
+
+		heapTuple->t_data = (HeapTupleHeader) PageGetItem(dp, lp);
+		heapTuple->t_len = ItemIdGetLength(lp);
+		heapTuple->t_tableOid = RelationGetRelid(relation);
+		ItemPointerSetOffsetNumber(&heapTuple->t_self, offnum);
+
+		/*
+		 * Shouldn't see a HEAP_ONLY tuple at chain start.
+		 */
+		if (at_chain_start && HeapTupleIsHeapOnly(heapTuple))
+			break;
+
+		/*
+		 * The xmin should match the previous xmax value, else chain is
+		 * broken.
+		 */
+		if (TransactionIdIsValid(prev_xmax) &&
+			!TransactionIdEquals(prev_xmax,
+								 HeapTupleHeaderGetXmin(heapTuple->t_data)))
+			break;
+
+		/*
+		 * When first_call is true (and thus, skip is initially false) we'll
+		 * return the first tuple we find.  But on later passes, heapTuple
+		 * will initially be pointing to the tuple we returned last time.
+		 * Returning it again would be incorrect (and would loop forever), so
+		 * we skip it and return the next match we find.
+		 */
+		if (!skip)
+		{
+			/*
+			 * For the benefit of logical decoding, have t_self point at the
+			 * element of the HOT chain we're currently investigating instead
+			 * of the root tuple of the HOT chain. This is important because
+			 * the *Satisfies routine for historical mvcc snapshots needs the
+			 * correct tid to decide about the visibility in some cases.
+			 */
+			ItemPointerSet(&(heapTuple->t_self), BufferGetBlockNumber(buffer), offnum);
+
+			/* If it's visible per the snapshot, we must return it */
+			valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, heapTuple, snapshot, buffer);
+			CheckForSerializableConflictOut(valid, relation, heapTuple,
+											buffer, snapshot);
+			/* reset to original, non-redirected, tid */
+			heapTuple->t_self = *tid;
+
+			if (valid)
+			{
+				ItemPointerSetOffsetNumber(tid, offnum);
+				PredicateLockTuple(relation, heapTuple, snapshot);
+				if (all_dead)
+					*all_dead = false;
+				return true;
+			}
+		}
+		skip = false;
+
+		/*
+		 * If we can't see it, maybe no one else can either.  At caller
+		 * request, check whether all chain members are dead to all
+		 * transactions.
+		 */
+		if (all_dead && *all_dead &&
+			!HeapTupleIsSurelyDead(heapTuple, RecentGlobalXmin))
+			*all_dead = false;
+
+		/*
+		 * Check to see if HOT chain continues past this tuple; if so fetch
+		 * the next offnum and loop around.
+		 */
+		if (HeapTupleIsHotUpdated(heapTuple))
+		{
+			Assert(ItemPointerGetBlockNumber(&heapTuple->t_data->t_ctid) ==
+				   ItemPointerGetBlockNumber(tid));
+			offnum = ItemPointerGetOffsetNumber(&heapTuple->t_data->t_ctid);
+			at_chain_start = false;
+			prev_xmax = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
+		}
+		else
+			break;				/* end of chain */
+	}
+
+	return false;
+}
+
+/*
+ * heapam_setscanlimits - restrict range of a heapscan
+ *
+ * startBlk is the page to start at
+ * numBlks is number of pages to scan (InvalidBlockNumber means "all")
+ */
+static void
+heapam_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	Assert(!scan->rs_inited);	/* else too late to change */
+	Assert(!scan->rs_syncscan); /* else rs_startblock is significant */
+
+	/* Check startBlk is valid (but allow case of zero blocks...) */
+	Assert(startBlk == 0 || startBlk < scan->rs_nblocks);
+
+	scan->rs_startblock = startBlk;
+	scan->rs_numblocks = numBlks;
+}
+
+
+/*
+ * heapam_freeze_tuple
+ *		Freeze tuple in place, without WAL logging.
+ *
+ * Useful for callers like CLUSTER that perform their own WAL logging.
+ */
+static bool
+heapam_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
+				  TransactionId cutoff_multi)
+{
+	xl_heap_freeze_tuple frz;
+	bool		do_freeze;
+	bool		tuple_totally_frozen;
+
+	do_freeze = heap_prepare_freeze_tuple(tuple, cutoff_xid, cutoff_multi,
+										  &frz, &tuple_totally_frozen);
+
+	/*
+	 * Note that because this is not a WAL-logged operation, we don't need to
+	 * fill in the offset in the freeze record.
+	 */
+
+	if (do_freeze)
+		heap_execute_freeze_tuple(tuple, &frz);
+	return do_freeze;
+}
 
 /* ----------------------------------------------------------------
  *				storage AM support routines for heapam
@@ -3793,6 +5082,16 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 {
 	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
 
+	amroutine->scan_begin = heapam_beginscan;
+	amroutine->scansetlimits = heapam_setscanlimits;
+	amroutine->scan_getnext = heapam_getnext;
+	amroutine->scan_getnextslot = heapam_getnextslot;
+	amroutine->scan_end = heapam_endscan;
+	amroutine->scan_rescan = heapam_rescan;
+	amroutine->scan_update_snapshot = heapam_scan_update_snapshot;
+	amroutine->tuple_freeze = heapam_freeze_tuple;
+	amroutine->hot_search_buffer = heapam_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/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 8fba61c4f1..a475a859b7 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -409,7 +409,7 @@ rewrite_heap_tuple(RewriteState state,
 	 * While we have our hands on the tuple, we may as well freeze any
 	 * eligible xmin or xmax, so that future VACUUM effort can be saved.
 	 */
-	heap_freeze_tuple(new_tuple->t_data, state->rs_freeze_xid,
+	storage_freeze_tuple(state->rs_new_rel, new_tuple->t_data, state->rs_freeze_xid,
 					  state->rs_cutoff_multi);
 
 	/*
diff --git a/src/backend/access/heap/storageam.c b/src/backend/access/heap/storageam.c
index d1d7364e7f..76b94dc8c3 100644
--- a/src/backend/access/heap/storageam.c
+++ b/src/backend/access/heap/storageam.c
@@ -48,6 +48,174 @@
 #include "utils/tqual.h"
 
 
+/* ----------------
+ *		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);
+}
+
 /*
  *	storage_fetch		- retrieve tuple with given tid
  *
@@ -99,6 +267,73 @@ storage_fetch(Relation relation,
 							userbuf, keep_buf, stats_relation);
 }
 
+/*
+ *	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;
+}
+
+/*
+ * heap_freeze_tuple
+ *		Freeze tuple in place, without WAL logging.
+ *
+ * Useful for callers like CLUSTER that perform their own WAL logging.
+ */
+bool
+storage_freeze_tuple(Relation rel, HeapTupleHeader tuple, TransactionId cutoff_xid,
+				  TransactionId cutoff_multi)
+{
+	return rel->rd_stamroutine->tuple_freeze(tuple, cutoff_xid, cutoff_multi);
+}
+
 
 /*
  *	storage_lock_tuple - lock a tuple in shared or exclusive mode
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 01321a2543..db5c93b4c7 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,7 +395,7 @@ 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,
+		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 bef4255369..349a127509 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,7 +606,7 @@ 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,
+	got_heap_tuple = storage_hot_search_buffer(tid, scan->heapRelation,
 											scan->xs_cbuf,
 											scan->xs_snapshot,
 											&scan->xs_ctup,
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index bf963fcdef..0e25e9a363 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,7 +326,7 @@ _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,
+				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/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 0453fd4ac1..975cd5be99 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 "bootstrap/bootstrap.h"
 #include "catalog/index.h"
@@ -573,18 +574,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,
@@ -592,7 +593,7 @@ boot_openrel(char *relname)
 				   sizeof((*app)->am_typ));
 			app++;
 		}
-		heap_endscan(scan);
+		storage_endscan(scan);
 		heap_close(rel, NoLock);
 	}
 
@@ -903,25 +904,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 ccde66a7dd..d2a8a06097 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"
@@ -797,14 +798,14 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 								ObjectIdGetDatum(namespaceId));
 
 					rel = heap_open(ProcedureRelationId, AccessShareLock);
-					scan = heap_beginscan_catalog(rel, 1, key);
+					scan = storage_beginscan_catalog(rel, 1, 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;
@@ -842,14 +843,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 0240df7a08..68c46f720d 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"
@@ -1904,10 +1905,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
 	{
@@ -2279,7 +2280,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	}
 
     method = heapRelation->rd_stamroutine;
-	scan = heap_beginscan_strat(heapRelation,	/* relation */
+	scan = storage_beginscan_strat(heapRelation,	/* relation */
 								snapshot,	/* snapshot */
 								0,	/* number of keys */
 								NULL,	/* scan key */
@@ -2288,7 +2289,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 
 	/* 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 */
@@ -2301,7 +2302,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;
 
@@ -2613,7 +2614,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		}
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	/* we can now forget our snapshot, if set */
 	if (IsBootstrapProcessingMode() || indexInfo->ii_Concurrent)
@@ -2684,14 +2685,14 @@ IndexCheckExclusion(Relation heapRelation,
 	 * Scan all live tuples in the base relation.
 	 */
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
-	scan = heap_beginscan_strat(heapRelation,	/* relation */
+	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 = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	while ((heapTuple = storage_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		CHECK_FOR_INTERRUPTS();
 
@@ -2727,7 +2728,7 @@ IndexCheckExclusion(Relation heapRelation,
 								   estate, true);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 
 	ExecDropSingleTupleTableSlot(slot);
@@ -3004,7 +3005,7 @@ 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 */
+	scan = storage_beginscan_strat(heapRelation,	/* relation */
 								snapshot,	/* snapshot */
 								0,	/* number of keys */
 								NULL,	/* scan key */
@@ -3014,7 +3015,7 @@ validate_index_heapscan(Relation heapRelation,
 	/*
 	 * 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;
@@ -3171,7 +3172,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 1ab6dba7ae..415421917b 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -988,7 +988,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);
 
 		/*
@@ -997,7 +997,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;
@@ -1013,7 +1013,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 beb7f050fe..be20ac7b2d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2028,10 +2028,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();
 
@@ -2043,7 +2043,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 e138539035..39850b1b37 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 b61aaac284..46bc3da1fb 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"
@@ -1948,8 +1949,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);
@@ -1989,7 +1990,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 c9e5ae0832..cd86fb9101 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4540,7 +4540,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
@@ -4548,7 +4548,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)
 			{
@@ -4661,7 +4661,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 
 		MemoryContextSwitchTo(oldCxt);
-		heap_endscan(scan);
+		storage_endscan(scan);
 		UnregisterSnapshot(snapshot);
 
 		ExecDropSingleTupleTableSlot(oldslot);
@@ -5064,9 +5064,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,
@@ -5078,7 +5078,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;
@@ -8243,7 +8243,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
@@ -8251,7 +8251,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);
 
@@ -8266,7 +8266,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	}
 
 	MemoryContextSwitchTo(oldcxt);
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 	ExecDropSingleTupleTableSlot(slot);
 	FreeExecutorState(estate);
@@ -8321,9 +8321,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;
@@ -8352,7 +8352,7 @@ validateForeignKeyConstraint(char *conname,
 		RI_FKey_check_ins(&fcinfo);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 }
 
@@ -10802,8 +10802,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;
@@ -10862,7 +10862,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 8559c3b6b3..cdfa8ffb3f 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/trigger.c b/src/backend/commands/trigger.c
index 8e2f351949..eb2c4ba081 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -15,8 +15,9 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
-#include "access/sysattr.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
+#include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 7ed16aeff4..c07f508d5b 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"
@@ -2315,8 +2316,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;
 
@@ -2345,7 +2346,7 @@ AlterDomainNotNull(List *names, bool notNull)
 					}
 				}
 			}
-			heap_endscan(scan);
+			storage_endscan(scan);
 			UnregisterSnapshot(snapshot);
 
 			/* Close each rel after processing, but keep lock */
@@ -2711,8 +2712,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;
 
@@ -2755,7 +2756,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 faa181207a..e24ac9f538 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"
@@ -447,9 +448,9 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
 
 		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);
 
@@ -469,7 +470,7 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
 			MemoryContextSwitchTo(oldcontext);
 		}
 
-		heap_endscan(scan);
+		storage_endscan(scan);
 		heap_close(pgclass, AccessShareLock);
 	}
 
@@ -1121,9 +1122,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;
@@ -1160,7 +1161,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..6ade9df823 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 89e189fa71..5e9daea662 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 8d625b6cbe..6f6861f500 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,20 @@ RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 
 	/* Start an index 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 +313,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 b7a2cbc023..0a5098d1df 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -681,7 +681,7 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 Datum
 ExecFetchSlotTupleDatum(TupleTableSlot *slot)
 {
-	HeapTuple	tup;
+	StorageTuple	tup;
 	TupleDesc	tupdesc;
 
 	/* Fetch slot's contents in regular-physical-tuple form */
@@ -765,7 +765,7 @@ ExecHeapifySlot(TupleTableSlot *slot)
 TupleTableSlot *
 ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
 {
-	HeapTuple	newTuple;
+	StorageTuple	newTuple;
 	MemoryContext oldContext;
 
 	/*
@@ -1085,7 +1085,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;
@@ -1093,7 +1093,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 42a4ca94e9..79b74ee2ff 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)
 {
@@ -597,7 +597,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 0ae5873868..d94169cfc8 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -3097,7 +3097,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,
@@ -3212,7 +3212,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 60a6cb03d8..7921025178 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 "executor/execdebug.h"
 #include "executor/nodeBitmapHeapscan.h"
@@ -400,7 +401,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 			HeapTupleData heapTuple;
 
 			ItemPointerSet(&tid, page, offnum);
-			if (heap_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
+			if (storage_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
 									   &heapTuple, NULL, true))
 				scan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
 		}
@@ -685,7 +686,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);
 
 	if (node->tbmiterator)
 		tbm_end_iterate(node->tbmiterator);
@@ -764,7 +765,7 @@ ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 	/*
 	 * close heap scan
 	 */
-	heap_endscan(scanDesc);
+	storage_endscan(scanDesc);
 
 	/*
 	 * close the heap relation.
@@ -865,7 +866,7 @@ 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,
+	scanstate->ss.ss_currentScanDesc = storage_beginscan_bm(currentRelation,
 														 estate->es_snapshot,
 														 0,
 														 NULL);
@@ -1023,5 +1024,5 @@ ExecBitmapHeapInitializeWorker(BitmapHeapScanState *node, shm_toc *toc)
 	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 02f6c816aa..abec3a9379 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 8370037c43..a6686ff8a8 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -45,7 +45,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);
 
 
@@ -251,7 +251,7 @@ gather_getnext(GatherState *gatherstate)
 	TupleTableSlot *outerTupleSlot;
 	TupleTableSlot *fslot = gatherstate->funnel_slot;
 	MemoryContext tupleContext = gatherstate->ps.ps_ExprContext->ecxt_per_tuple_memory;
-	HeapTuple	tup;
+	StorageTuple	tup;
 
 	while (gatherstate->nreaders > 0 || gatherstate->need_to_scan_locally)
 	{
@@ -294,7 +294,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;
@@ -302,7 +302,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 70f33a9a28..e19cdb557e 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -44,7 +44,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 */
@@ -53,7 +53,7 @@ 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,
+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);
@@ -399,7 +399,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);
@@ -617,7 +617,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
@@ -689,12 +689,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;
 	MemoryContext oldContext;
 	MemoryContext tupleContext;
 
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index 5351cb8981..f770bc45c5 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,7 +186,7 @@ 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
+		 * 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.
 		 */
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 638b17b07c..7330ff983a 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..04f85e5f2d 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -29,9 +29,9 @@
 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,
-				   HeapScanDesc scan);
+static StorageTuple tablesample_getnext(SampleScanState *scanstate);
+static bool SampleTupleVisible(StorageTuple tuple, OffsetNumber tupoffset,
+		HeapScanDesc scan); //hari
 
 /* ----------------------------------------------------------------
  *						Scan Support
@@ -47,7 +47,7 @@ static bool SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset,
 static TupleTableSlot *
 SampleNext(SampleScanState *node)
 {
-	HeapTuple	tuple;
+	StorageTuple	tuple;
 	TupleTableSlot *slot;
 
 	/*
@@ -66,7 +66,8 @@ SampleNext(SampleScanState *node)
 	if (tuple)
 		ExecStoreTuple(tuple,	/* tuple to store */
 					   slot,	/* slot to store in */
-					   node->ss.ss_currentScanDesc->rs_cbuf,	/* tuple's buffer */
+					   //harinode->ss.ss_currentScanDesc->rs_cbuf,	/* tuple's buffer */
+					   InvalidBuffer,
 					   false);	/* don't pfree this pointer */
 	else
 		ExecClearTuple(slot);
@@ -244,7 +245,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,7 +350,7 @@ tablesample_init(SampleScanState *scanstate)
 	if (scanstate->ss.ss_currentScanDesc == NULL)
 	{
 		scanstate->ss.ss_currentScanDesc =
-			heap_beginscan_sampling(scanstate->ss.ss_currentRelation,
+			storage_beginscan_sampling(scanstate->ss.ss_currentRelation,
 									scanstate->ss.ps.state->es_snapshot,
 									0, NULL,
 									scanstate->use_bulkread,
@@ -358,7 +359,7 @@ tablesample_init(SampleScanState *scanstate)
 	}
 	else
 	{
-		heap_rescan_set_params(scanstate->ss.ss_currentScanDesc, NULL,
+		storage_rescan_set_params(scanstate->ss.ss_currentScanDesc, NULL,
 							   scanstate->use_bulkread,
 							   allow_sync,
 							   scanstate->use_pagemode);
@@ -376,7 +377,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 +555,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 d4ac939c9b..839d3a65ec 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,7 +69,7 @@ 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,
+		scandesc = storage_beginscan(node->ss.ss_currentRelation,
 								  estate->es_snapshot,
 								  0, NULL);
 		node->ss.ss_currentScanDesc = scandesc;
@@ -78,26 +78,7 @@ SeqNext(SeqScanState *node)
 	/*
 	 * 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,12 +251,12 @@ ExecEndSeqScan(SeqScanState *node)
 void
 ExecReScanSeqScan(SeqScanState *node)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 
 	scan = node->ss.ss_currentScanDesc;
 
 	if (scan != NULL)
-		heap_rescan(scan,		/* scan desc */
+		storage_rescan(scan,		/* scan desc */
 					NULL);		/* new scan keys */
 
 	ExecScanReScan((ScanState *) node);
@@ -322,7 +303,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);
 }
 
 /* ----------------------------------------------------------------
@@ -353,5 +334,5 @@ ExecSeqScanInitializeWorker(SeqScanState *node, shm_toc *toc)
 
 	pscan = shm_toc_lookup(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 80be46029f..d55a752c98 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -2091,7 +2091,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;
@@ -2159,7 +2159,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 afe231fca9..418c2a665c 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -627,11 +627,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)
 	{
@@ -655,7 +655,7 @@ SPI_copytuple(HeapTuple tuple)
 }
 
 HeapTupleHeader
-SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc)
+SPI_returntuple(StorageTuple tuple, TupleDesc tupdesc)
 {
 	MemoryContext oldcxt;
 	HeapTupleHeader dtup;
@@ -686,7 +686,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)
 {
@@ -854,7 +854,7 @@ char *
 SPI_gettype(TupleDesc tupdesc, int fnumber)
 {
 	Oid			typoid;
-	HeapTuple	typeTuple;
+	StorageTuple	typeTuple;
 	char	   *result;
 
 	SPI_result = 0;
@@ -962,7 +962,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);
@@ -1683,7 +1683,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);
@@ -1714,8 +1714,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 9a47276274..3d61d96775 100644
--- a/src/backend/executor/tqueue.c
+++ b/src/backend/executor/tqueue.c
@@ -166,7 +166,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 776b1c0a9d..fec203dee3 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1882,9 +1882,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;
@@ -1911,7 +1911,7 @@ get_database_list(void)
 		MemoryContextSwitchTo(oldcxt);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	CommitTransactionCommand();
@@ -2042,13 +2042,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;
@@ -2134,7 +2134,7 @@ do_autovacuum(void)
 		}
 	}
 
-	heap_endscan(relScan);
+	storage_endscan(relScan);
 
 	/* second pass: check TOAST tables */
 	ScanKeyInit(&key,
@@ -2142,8 +2142,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;
@@ -2189,7 +2189,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 accf302cf7..74113a75b8 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 44bdcab3b9..61981a0939 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 071b3a9ec9..4924daca76 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(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 cdd45ef313..4cddd73355 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -106,40 +106,16 @@ 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);
-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_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 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 Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 			int options, BulkInsertState bistate);
-extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
-					   Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
-					   bool *all_dead, bool first_call);
-extern bool heap_hot_search(ItemPointer tid, Relation relation,
-				Snapshot snapshot, bool *all_dead);
+
 extern void setLastTid(const ItemPointer tid);
 
 extern BulkInsertState GetBulkInsertState(void);
@@ -147,8 +123,6 @@ extern void FreeBulkInsertState(BulkInsertState);
 extern void ReleaseBulkInsertStatePin(BulkInsertState bistate);
 
 extern void heap_inplace_update(Relation relation, HeapTuple tuple);
-extern bool heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
-				  TransactionId cutoff_multi);
 extern bool heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
 						MultiXactId cutoff_multi, Buffer buf);
 extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
@@ -158,8 +132,6 @@ extern void simple_heap_delete(Relation relation, ItemPointer tid);
 extern void simple_heap_update(Relation relation, ItemPointer otid,
 				   HeapTuple tup);
 
-extern void heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
-
 /* 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/heapam_common.h b/src/include/access/heapam_common.h
index 799b4edada..66a96d7101 100644
--- a/src/include/access/heapam_common.h
+++ b/src/include/access/heapam_common.h
@@ -107,6 +107,9 @@ static const int MultiXactStatusLock[MaxMultiXactStatus + 1] =
 /* Get the LOCKMODE for a given MultiXactStatus */
 #define LOCKMODE_from_mxstatus(status) \
 			(tupleLockExtraInfo[TUPLOCK_from_mxstatus((status))].hwlock)
+
+extern bool	synchronize_seqscans;
+
 extern HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
 					TransactionId xid, CommandId cid, int options);
 
@@ -136,6 +139,11 @@ extern void MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 in
 extern MultiXactStatus get_mxact_status_for_lock(LockTupleMode mode, bool is_update);
 
 extern void heap_inplace_update(Relation relation, HeapTuple tuple);
+
+extern bool heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
+						MultiXactId cutoff_multi, Buffer buf);
+extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
+
 extern bool heap_acquire_tuplock(Relation relation, ItemPointer tid,
 					 LockTupleMode mode, LockWaitPolicy wait_policy,
 					 bool *have_tuple_lock);
diff --git a/src/include/access/storageam.h b/src/include/access/storageam.h
index 9502c92318..507af714a9 100644
--- a/src/include/access/storageam.h
+++ b/src/include/access/storageam.h
@@ -19,6 +19,7 @@
 
 /* A physical tuple coming from a storage AM scan */
 typedef void *StorageTuple;
+typedef void *StorageScanDesc;
 
 typedef union tuple_data
 {
@@ -36,6 +37,34 @@ typedef enum tuple_data_flags
 	CTID
 } tuple_data_flags;
 
+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,
@@ -44,6 +73,15 @@ 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 bool storage_freeze_tuple(Relation rel, HeapTupleHeader tuple, TransactionId cutoff_xid,
+				  TransactionId cutoff_multi);
+
 extern HTSU_Result storage_lock_tuple(Relation relation, ItemPointer tid, StorageTuple *stuple,
 				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
 				bool follow_updates,
@@ -72,10 +110,6 @@ extern bool storage_tuple_is_heaponly(Relation relation, StorageTuple tuple);
 
 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/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..d466c997fb 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,9 +117,9 @@ 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,
+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);
@@ -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..fe580d552a 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,
+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 951af2aad3..ab0e0916ea 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -229,7 +229,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.14.1.windows.1

#82Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Andres Freund (#68)
Re: Pluggable storage

Hi,

Thank you for review on this subject. I think it's extremely important for
PostgreSQL to eventually get pluggable storage API.
In general I agree with all your points. But I'd like to make couple of
comments.

On Tue, Aug 15, 2017 at 9:53 AM, Andres Freund <andres@anarazel.de> wrote:

- I don't think we should introduce this without a user besides
heapam. The likelihood that API will be usable by anything else
without a testcase seems fairly remote. I think some src/test/modules
type implementation of a per-session, in-memory storage - relatively
easy to implement - or such is necessary.

+1 for having a user before committing API. However, I'd like to note that
sample storage implementation should do something really different from out
current heap. In particular, if per-session, in-memory storage would be
just another way to keep heap in local buffers, it wouldn't be OK for me;
because such kind of storage could be achieved way more easier without so
complex API. But if per-session, in-memory storage would, for instance,
utilize different MVCC implementation, that would be very good sample of
storage API usage.

- Minor: don't think the _function suffix for Storis necessary, just
makes things long, and every member has it. Besides that, it's also
easy to misunderstand - for a second I understood
scan_getnext_function to be about getting the next function...

_function suffix looks long for me too. But we should look on this
question from uniformity point of view.
FdwRoutine, TsmRoutine, IndexAmRoutine use _function suffix. This is why I
think we should use _function suffix for StorageAmRoutine unless we're
going to change that for other *Routines too.

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

#83Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Haribabu Kommi (#71)
Re: Pluggable storage

On Wed, Aug 23, 2017 at 8:26 AM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:

- Minor: don't think the _function suffix for Storis necessary, just

makes things long, and every member has it. Besides that, it's also
easy to misunderstand - for a second I understood
scan_getnext_function to be about getting the next function...

OK. How about adding _hook?

I've answered to Andrew why I think _function suffix is OK for now.
And I don't particularly like _hook suffix for this purpose, because those
functions are parts of API implementation, not hooks.

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

#84Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Haribabu Kommi (#81)
Re: Pluggable storage

Hi!

I took a look on this patch. I've following notes for now.

tuple_insert_hook tuple_insert; /* heap_insert */

tuple_update_hook tuple_update; /* heap_update */
tuple_delete_hook tuple_delete; /* heap_delete */

I don't think this set of functions provides good enough level of
abstraction for storage AM. This functions encapsulate only low-level work
of insert/update/delete tuples into heap itself. However, it still assumed
that indexes are managed outside of storage AM. I don't think this is
right, assuming that one of most demanded storage API usage would be
different MVCC implementation (for instance, UNDO log based as discussed
upthread). Different MVCC implementation is likely going to manage indexes
in a different way. For example, storage AM utilizing UNDO would implement
in-place update even when indexed columns are modified. Therefore this
piece of code in ExecUpdate() wouldn't be relevant anymore.

/*

* 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) &&
!storage_tuple_is_heaponly(resultRelationDesc, tuple))
recheckIndexes = ExecInsertIndexTuples(slot, &(slot->tts_tid),
estate, false, NULL, NIL);

I'm firmly convinced that this logic should be encapsulated into storage AM
altogether with inserting new index tuples on storage insert. Also, HOT
should be completely encapsulated into heapam. It's quite evident for me
that storage utilizing UNDO wouldn't have anything like our current HOT.
Therefore, I think there shouldn't be hot_search_buffer() API function.
tuple_fetch() may cover hot_search_buffer(). That might require some
signature change of tuple_fetch() (probably, extra arguments).

LockTupleMode and HeapUpdateFailureData shouldn't be private of heapam.
Any fullweight OLTP storage AM should support our tuple lock modes and
should be able to report update failures. HeapUpdateFailureData should be
renamed to something like StorageUpdateFailureData. Contents of
HeapUpdateFailureData seems to me general enough to be supported by any
storage with ItemPointer tuple locator.

storage_setscanlimits() is used only during index build. I think that
since storage AM may have different MVCC implementation then storage AM
should decide how to communicate with indexes including index build.
Therefore, instead of exposing storage_setscanlimits(), the
whole IndexBuildHeapScan() should be encapsulated into storage AM.

Also, BulkInsertState should be private of structure of heapam. Another
storages may have another state for bulk insert. On API level we might
have some abstract pointer instead of BulkInsertState while having
GetBulkInsertState and others as API methods.

storage_freeze_tuple() is called only once from rewrite_heap_tuple(). That
makes me think that tuple_freeze API method is wrong for abstraction. We
probably should make rewrite_heap_tuple() or even the
whole rebuild_relation() an API method...

Heap reloptions are untouched for now. Storage AM should be able to
provide its own specific options just like index AMs do.

That's all I have for now.

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

#85Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alexander Korotkov (#84)
Re: Pluggable storage

On Wed, Sep 27, 2017 at 7:51 PM, Alexander Korotkov <
a.korotkov@postgrespro.ru> wrote:

I took a look on this patch. I've following notes for now.

tuple_insert_hook tuple_insert; /* heap_insert */

tuple_update_hook tuple_update; /* heap_update */
tuple_delete_hook tuple_delete; /* heap_delete */

I don't think this set of functions provides good enough level of
abstraction for storage AM. This functions encapsulate only low-level work
of insert/update/delete tuples into heap itself. However, it still assumed
that indexes are managed outside of storage AM. I don't think this is
right, assuming that one of most demanded storage API usage would be
different MVCC implementation (for instance, UNDO log based as discussed
upthread). Different MVCC implementation is likely going to manage indexes
in a different way. For example, storage AM utilizing UNDO would implement
in-place update even when indexed columns are modified. Therefore this
piece of code in ExecUpdate() wouldn't be relevant anymore.

/*

* 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) && !storage_tuple_is_heaponly(resultRelationDesc,
tuple))
recheckIndexes = ExecInsertIndexTuples(slot, &(slot->tts_tid),
estate, false, NULL, NIL);

I'm firmly convinced that this logic should be encapsulated into storage
AM altogether with inserting new index tuples on storage insert. Also, HOT
should be completely encapsulated into heapam. It's quite evident for me
that storage utilizing UNDO wouldn't have anything like our current HOT.
Therefore, I think there shouldn't be hot_search_buffer() API function.
tuple_fetch() may cover hot_search_buffer(). That might require some
signature change of tuple_fetch() (probably, extra arguments).

For me, it's crucial point that pluggable storages should be able to have
different MVCC implementation, and correspondingly have full control over
its interactions with indexes.
Thus, it would be good if we would get consensus on that point. I'd like
other discussion participants to comment whether they agree/disagree and
why.
Any comments?

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

#86Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alexander Korotkov (#85)
Re: Pluggable storage

Alexander Korotkov <a.korotkov@postgrespro.ru> writes:

For me, it's crucial point that pluggable storages should be able to have
different MVCC implementation, and correspondingly have full control over
its interactions with indexes.
Thus, it would be good if we would get consensus on that point. I'd like
other discussion participants to comment whether they agree/disagree and
why.
Any comments?

TBH, I think that's a good way of ensuring that nothing will ever get
committed. You're trying to draw the storage layer boundary at a point
that will take in most of the system. If we did build it like that,
what we'd end up with would be very reminiscent of mysql's storage
engines, complete with inconsistent behaviors and varying feature sets
across engines. I don't much want to go there.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#87Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Tom Lane (#86)
Re: Pluggable storage

On Mon, Oct 9, 2017 at 5:32 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Alexander Korotkov <a.korotkov@postgrespro.ru> writes:

For me, it's crucial point that pluggable storages should be able to have
different MVCC implementation, and correspondingly have full control over
its interactions with indexes.
Thus, it would be good if we would get consensus on that point. I'd like
other discussion participants to comment whether they agree/disagree and
why.
Any comments?

TBH, I think that's a good way of ensuring that nothing will ever get
committed. You're trying to draw the storage layer boundary at a point
that will take in most of the system. If we did build it like that,
what we'd end up with would be very reminiscent of mysql's storage
engines, complete with inconsistent behaviors and varying feature sets
across engines. I don't much want to go there.

However, if we insist that pluggable storage should have the same MVCC
implementation, interacts with indexes the same way and also use TIDs as
tuple identifiers, then what useful implementations might we have?
Per-page heap compression and encryption? Or different heap page layout?
Or tuple format? OK, but that doesn't justify such wide API as it's
implemented in the current version of patch in this thread. If we really
want to restrict applicability of pluggable storages that way, then we
probably should give up with "pluggable storages" and make it "pluggable
heap page format" at I proposed upthread.

Implementation of alternative storage would be hard and challenging task.
Yes, it would include reimplementation of significant part of the system.
But that seems inevitable if we're going to implement alternative really
storages (not just hacks over existing storage). And I don't think that
our pluggable storages would be reminiscent of mysql's storage engines
while we're keeping two properties:
1) All the storages use the same WAL stream,
2) All the storages use same transactions and snapshots.
If we keep these two properties, we wouldn't need neither 2PC to run
transactions across different storages, neither separate log for
replication. These two are major drawbacks of MySQL model.
Varying feature sets across engines seems inevitable and natural. We've to
invent alternative storages to have features whose are hard to have in our
current storage. So, no wonder that feature sets would be varying...

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

#88Robert Haas
robertmhaas@gmail.com
In reply to: Alexander Korotkov (#85)
Re: Pluggable storage

On Mon, Oct 9, 2017 at 10:22 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

For me, it's crucial point that pluggable storages should be able to have
different MVCC implementation, and correspondingly have full control over
its interactions with indexes.
Thus, it would be good if we would get consensus on that point. I'd like
other discussion participants to comment whether they agree/disagree and
why.
Any comments?

I think it's good for new storage managers to have full control over
interactions with indexes. I'm not sure about the MVCC part. I think
it would be legitimate to want a storage manager to ignore MVCC
altogether - e.g. to build a non-transactional table. I don't know
that it would be a very good idea to have two different full-fledged
MVCC implementations, though. Like Tom says, that would be
replicating a lot of the awfulness of the MySQL model.

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

In reply to: Robert Haas (#88)
Re: Pluggable storage

On Wed, Oct 11, 2017 at 1:08 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Oct 9, 2017 at 10:22 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

For me, it's crucial point that pluggable storages should be able to have
different MVCC implementation, and correspondingly have full control over
its interactions with indexes.
Thus, it would be good if we would get consensus on that point. I'd like
other discussion participants to comment whether they agree/disagree and
why.
Any comments?

I think it's good for new storage managers to have full control over
interactions with indexes. I'm not sure about the MVCC part. I think
it would be legitimate to want a storage manager to ignore MVCC
altogether - e.g. to build a non-transactional table.

I agree with Alexander -- if you're going to have a new MVCC
implementation, you have to do significant work within index access
methods. Adding "retail index tuple deletion" is probably just the
beginning. ISTM that you need something like InnoDB's purge thread
when index values change, since two versions of the same index tuple
(each with distinct attribute values) have to physically co-exist for
a time.

I don't know
that it would be a very good idea to have two different full-fledged
MVCC implementations, though. Like Tom says, that would be
replicating a lot of the awfulness of the MySQL model.

It's not just the MySQL model, FWIW. SQL-on-Hadoop systems like
Impala, certain NoSQL systems, and AFAIK any database system that
claims to have pluggable storage all do it this way. That is, core
transaction management functions (e.g. MVCC snapshot acquisition) is
outsourced to the storage engine. It *is* very cumbersome, but that's
what they do.

--
Peter Geoghegan

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#90Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Robert Haas (#88)
Re: Pluggable storage

On Wed, Oct 11, 2017 at 11:08 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Oct 9, 2017 at 10:22 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

For me, it's crucial point that pluggable storages should be able to have
different MVCC implementation, and correspondingly have full control over
its interactions with indexes.
Thus, it would be good if we would get consensus on that point. I'd like
other discussion participants to comment whether they agree/disagree and
why.
Any comments?

I think it's good for new storage managers to have full control over
interactions with indexes. I'm not sure about the MVCC part. I think
it would be legitimate to want a storage manager to ignore MVCC
altogether - e.g. to build a non-transactional table. I don't know
that it would be a very good idea to have two different full-fledged
MVCC implementations, though. Like Tom says, that would be
replicating a lot of the awfulness of the MySQL model.

It's probably that we imply different meaning to "MVCC implementation".
While writing "MVCC implementation" I meant that, for instance, alternative
storage
may implement UNDO chains to store versions of same row. Correspondingly,
it may not have any analogue of our HOT.

However I imply that alternative storage would share our "MVCC model". So,
it
should share our transactional model including transactions,
subtransactions, snapshots etc.
Therefore, if alternative storage is transactional, then in particular it
should be able to fetch tuple with
given TID according to given snapshot. However, how it's implemented
internally is
a black box for us. Thus, we don't insist that tuple should have different
TID after update;
we don't insist there is any analogue of HOT; we don't insist alternative
storage needs vacuum
(or if even it needs vacuum, it might be performed in completely different
way) and so on.

During conversations with you at PGCon and other conferences I had
impression
that you share this view on pluggable storages and MVCC. Probably, we just
express
this view in different words. Or alternatively I might understand you
terribly wrong.

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

#91Robert Haas
robertmhaas@gmail.com
In reply to: Alexander Korotkov (#90)
Re: Pluggable storage

On Thu, Oct 12, 2017 at 4:38 PM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

It's probably that we imply different meaning to "MVCC implementation".
While writing "MVCC implementation" I meant that, for instance, alternative
storage
may implement UNDO chains to store versions of same row. Correspondingly,
it may not have any analogue of our HOT.

Yes, the zheap project on which EnterpriseDB is working has precisely
this characteristic.

However I imply that alternative storage would share our "MVCC model". So,
it
should share our transactional model including transactions,
subtransactions, snapshots etc.
Therefore, if alternative storage is transactional, then in particular it
should be able to fetch tuple with
given TID according to given snapshot. However, how it's implemented
internally is
a black box for us. Thus, we don't insist that tuple should have different
TID after update;
we don't insist there is any analogue of HOT; we don't insist alternative
storage needs vacuum
(or if even it needs vacuum, it might be performed in completely different
way) and so on.

Fully agreed.

During conversations with you at PGCon and other conferences I had
impression
that you share this view on pluggable storages and MVCC. Probably, we just
express
this view in different words. Or alternatively I might understand you
terribly wrong.

No, it sounds like we are on the same page. I'm only hoping that we
don't end with a bunch of storage engines that each use a different
XID space or something icky like that. I don't think the API should
try to cater to that sort of development.

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#92Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Robert Haas (#91)
Re: Pluggable storage

On Fri, Oct 13, 2017 at 8:23 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Oct 12, 2017 at 4:38 PM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

It's probably that we imply different meaning to "MVCC implementation".
While writing "MVCC implementation" I meant that, for instance,

alternative

storage
may implement UNDO chains to store versions of same row.

Correspondingly,

it may not have any analogue of our HOT.

Yes, the zheap project on which EnterpriseDB is working has precisely
this characteristic.

However I imply that alternative storage would share our "MVCC model".

So,

it
should share our transactional model including transactions,
subtransactions, snapshots etc.
Therefore, if alternative storage is transactional, then in particular it
should be able to fetch tuple with
given TID according to given snapshot. However, how it's implemented
internally is
a black box for us. Thus, we don't insist that tuple should have

different

TID after update;
we don't insist there is any analogue of HOT; we don't insist alternative
storage needs vacuum
(or if even it needs vacuum, it might be performed in completely

different

way) and so on.

Fully agreed.

Currently I added a snapshot_satisfies API to find out whether the tuple
satisfies the visibility or not with different types of visibility
routines. I feel these
are some how enough to develop a different storage methods like UNDO.
The storage methods can decide internally how to provide the visibility.

+ amroutine->snapshot_satisfies[MVCC_VISIBILITY] = HeapTupleSatisfiesMVCC;
+ amroutine->snapshot_satisfies[SELF_VISIBILITY] = HeapTupleSatisfiesSelf;
+ amroutine->snapshot_satisfies[ANY_VISIBILITY] = HeapTupleSatisfiesAny;
+ amroutine->snapshot_satisfies[TOAST_VISIBILITY] = HeapTupleSatisfiesToast;
+ amroutine->snapshot_satisfies[DIRTY_VISIBILITY] = HeapTupleSatisfiesDirty;
+ amroutine->snapshot_satisfies[HISTORIC_MVCC_VISIBILITY] =
HeapTupleSatisfiesHistoricMVCC;
+ amroutine->snapshot_satisfies[NON_VACUUMABLE_VISIBILTY] =
HeapTupleSatisfiesNonVacuumable;
+
+ amroutine->snapshot_satisfiesUpdate = HeapTupleSatisfiesUpdate;
+ amroutine->snapshot_satisfiesVacuum = HeapTupleSatisfiesVacuum;

Currently no changes are carried out in snapshot logic as that is kept
seperate
from storage API.

Regards,
Hari Babu
Fujitsu Australia

#93Robert Haas
robertmhaas@gmail.com
In reply to: Haribabu Kommi (#92)
Re: Pluggable storage

On Thu, Oct 12, 2017 at 8:00 PM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

Currently I added a snapshot_satisfies API to find out whether the tuple
satisfies the visibility or not with different types of visibility routines.
I feel these
are some how enough to develop a different storage methods like UNDO.
The storage methods can decide internally how to provide the visibility.

+ amroutine->snapshot_satisfies[MVCC_VISIBILITY] = HeapTupleSatisfiesMVCC;
+ amroutine->snapshot_satisfies[SELF_VISIBILITY] = HeapTupleSatisfiesSelf;
+ amroutine->snapshot_satisfies[ANY_VISIBILITY] = HeapTupleSatisfiesAny;
+ amroutine->snapshot_satisfies[TOAST_VISIBILITY] = HeapTupleSatisfiesToast;
+ amroutine->snapshot_satisfies[DIRTY_VISIBILITY] = HeapTupleSatisfiesDirty;
+ amroutine->snapshot_satisfies[HISTORIC_MVCC_VISIBILITY] =
HeapTupleSatisfiesHistoricMVCC;
+ amroutine->snapshot_satisfies[NON_VACUUMABLE_VISIBILTY] =
HeapTupleSatisfiesNonVacuumable;
+
+ amroutine->snapshot_satisfiesUpdate = HeapTupleSatisfiesUpdate;
+ amroutine->snapshot_satisfiesVacuum = HeapTupleSatisfiesVacuum;

Currently no changes are carried out in snapshot logic as that is kept
seperate
from storage API.

That seems like a strange choice of API. I think it should more
integrated with the scan logic. For example, if I'm doing an index
scan, and I get a TID, then I should be able to just say "here's a
TID, give me any tuples associated with that TID that are visible to
the scan snapshot". Then for the current heap it will do
heap_hot_search_buffer, and for zheap it will walk the undo chain and
return the relevant tuple from the chain.

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#94Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Robert Haas (#93)
Re: Pluggable storage

On Fri, Oct 13, 2017 at 11:55 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Oct 12, 2017 at 8:00 PM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

Currently I added a snapshot_satisfies API to find out whether the tuple
satisfies the visibility or not with different types of visibility

routines.

I feel these
are some how enough to develop a different storage methods like UNDO.
The storage methods can decide internally how to provide the visibility.

+ amroutine->snapshot_satisfies[MVCC_VISIBILITY] =

HeapTupleSatisfiesMVCC;

+ amroutine->snapshot_satisfies[SELF_VISIBILITY] =

HeapTupleSatisfiesSelf;

+ amroutine->snapshot_satisfies[ANY_VISIBILITY] = HeapTupleSatisfiesAny;
+ amroutine->snapshot_satisfies[TOAST_VISIBILITY] =

HeapTupleSatisfiesToast;

+ amroutine->snapshot_satisfies[DIRTY_VISIBILITY] =

HeapTupleSatisfiesDirty;

+ amroutine->snapshot_satisfies[HISTORIC_MVCC_VISIBILITY] =
HeapTupleSatisfiesHistoricMVCC;
+ amroutine->snapshot_satisfies[NON_VACUUMABLE_VISIBILTY] =
HeapTupleSatisfiesNonVacuumable;
+
+ amroutine->snapshot_satisfiesUpdate = HeapTupleSatisfiesUpdate;
+ amroutine->snapshot_satisfiesVacuum = HeapTupleSatisfiesVacuum;

Currently no changes are carried out in snapshot logic as that is kept
seperate
from storage API.

That seems like a strange choice of API. I think it should more
integrated with the scan logic. For example, if I'm doing an index
scan, and I get a TID, then I should be able to just say "here's a
TID, give me any tuples associated with that TID that are visible to
the scan snapshot". Then for the current heap it will do
heap_hot_search_buffer, and for zheap it will walk the undo chain and
return the relevant tuple from the chain.

OK, Understood.
I will check along these lines and come up with storage API's.

Regards,
Hari Babu
Fujitsu Australia

#95Kuntal Ghosh
kuntalghosh.2007@gmail.com
In reply to: Haribabu Kommi (#94)
Re: Pluggable storage

On Fri, Oct 13, 2017 at 1:58 PM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

On Fri, Oct 13, 2017 at 11:55 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Oct 12, 2017 at 8:00 PM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

That seems like a strange choice of API. I think it should more
integrated with the scan logic. For example, if I'm doing an index
scan, and I get a TID, then I should be able to just say "here's a
TID, give me any tuples associated with that TID that are visible to
the scan snapshot". Then for the current heap it will do
heap_hot_search_buffer, and for zheap it will walk the undo chain and
return the relevant tuple from the chain.

OK, Understood.
I will check along these lines and come up with storage API's.

I've some doubts regarding the following function hook:

+typedef bool (*hot_search_buffer_hook) (ItemPointer tid, Relation relation,
+    Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
+    bool *all_dead, bool first_call);

As per my understanding, with HOT feature a new tuple placed on the
same page and with all indexed columns the same as its parent row
version does not get new index entries (README.HOT). For some other
storage engine, if we maintain the older version in different storage,
undo for example, and don't require a new index entry, should we still
call it HOT-chain? If not, IMHO, we may have something like
*search_buffer_hook(tid,....,storageTuple,...). Depending on the
underlying storage, one can traverse hot-chain or undo-chain or some
other multi-version strategy for non-key updates.

After a successful index search, most of the index AMs set
(HeapTupleData)xs_ctup->t_self of IndexScanDescData to the tupleid in
the storage. IMHO, some changes are needed here to make it generic.

@@ -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);
For some storage engine, isn't it possible that the buffer is valid
and the tuple to be stored is formed in memory (for example, tuple
formed from UNDO, in-memory decrypted tuple etc.)

--
Thanks & Regards,
Kuntal Ghosh
EnterpriseDB: http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

In reply to: Robert Haas (#91)
Re: Pluggable storage

On Thu, Oct 12, 2017 at 2:23 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Oct 12, 2017 at 4:38 PM, Alexander Korotkov

However I imply that alternative storage would share our "MVCC model". So,
it
should share our transactional model including transactions,
subtransactions, snapshots etc.
Therefore, if alternative storage is transactional, then in particular it
should be able to fetch tuple with
given TID according to given snapshot. However, how it's implemented
internally is
a black box for us. Thus, we don't insist that tuple should have different
TID after update;
we don't insist there is any analogue of HOT; we don't insist alternative
storage needs vacuum
(or if even it needs vacuum, it might be performed in completely different
way) and so on.

Fully agreed.

If we implement that interface, where does that leave EvalPlanQual()?
Do those semantics have to be preserved?

--
Peter Geoghegan

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#97Robert Haas
robertmhaas@gmail.com
In reply to: Kuntal Ghosh (#95)
Re: Pluggable storage

On Fri, Oct 13, 2017 at 5:25 AM, Kuntal Ghosh
<kuntalghosh.2007@gmail.com> wrote:

For some other
storage engine, if we maintain the older version in different storage,
undo for example, and don't require a new index entry, should we still
call it HOT-chain?

I would say, emphatically, no. HOT is a creature of the existing
heap. If it's creeping into storage APIs they are not really
abstracted from what we have currently.

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#98Robert Haas
robertmhaas@gmail.com
In reply to: Peter Geoghegan (#96)
Re: Pluggable storage

On Fri, Oct 13, 2017 at 1:59 PM, Peter Geoghegan <pg@bowt.ie> wrote:

Fully agreed.

If we implement that interface, where does that leave EvalPlanQual()?
Do those semantics have to be preserved?

For a general-purpose heap storage format, I would say yes.

I mean, we don't really have control over how people use the API. If
somebody decides to implement a storage API that breaks EvalPlanQual
semantics horribly, I can't stop them, and I don't want to stop them.
Open source FTW.

But I don't really want that code in our tree, either. I think a
storage engine is and should be about the format in which data gets
stored on disk, and that it should only affect the performance of
queries not the answers that they give. I am sure there will be cases
where, for reasons of implementation complexity, that turns out not to
be true, but I think in general we should try to avoid it as much as
we can.

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#99Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Robert Haas (#97)
Re: Pluggable storage

On Fri, Oct 13, 2017 at 9:37 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Oct 13, 2017 at 5:25 AM, Kuntal Ghosh
<kuntalghosh.2007@gmail.com> wrote:

For some other
storage engine, if we maintain the older version in different storage,
undo for example, and don't require a new index entry, should we still
call it HOT-chain?

I would say, emphatically, no. HOT is a creature of the existing
heap. If it's creeping into storage APIs they are not really
abstracted from what we have currently.

+1,
different storage may need to insert entries to only *some* of indexes.
Wherein these new index entries may have either same or new TIDs.

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

#100Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Robert Haas (#98)
Re: Pluggable storage

On Fri, Oct 13, 2017 at 9:41 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Oct 13, 2017 at 1:59 PM, Peter Geoghegan <pg@bowt.ie> wrote:

Fully agreed.

If we implement that interface, where does that leave EvalPlanQual()?

From the first glance, it seems that pluggable storage should
override EvalPlanQualFetch(), rest of EvalPlanQual() looks quite generic.

Do those semantics have to be preserved?

For a general-purpose heap storage format, I would say yes.

+1

I mean, we don't really have control over how people use the API. If

somebody decides to implement a storage API that breaks EvalPlanQual
semantics horribly, I can't stop them, and I don't want to stop them.
Open source FTW.

Yeah. We don't have any kind of "safe extensions". Any extension can
break things really horribly.
For me that means user should absolutely trust extension developer.

But I don't really want that code in our tree, either.

We keep things in our tree as correct as we can. And for sure, we should
follow this politics for pluggable storages too.

I think a
storage engine is and should be about the format in which data gets
stored on disk, and that it should only affect the performance of
queries not the answers that they give.

Pretty same idea as index access methods. They also affects the
performance, but not query answers. When it's not true, this situation
is considered as bug, and it needs to be fixed.

I am sure there will be cases
where, for reasons of implementation complexity, that turns out not to
be true, but I think in general we should try to avoid it as much as
we can.

I think in some cases we can tolerate missing features (and document it),
but don't tolerate wrong features. For instance, we may have some pluggable
storage which doesn't support transactions at all (and that should be
documented for sure), but we shouldn't have pluggable storage which
transaction support is wrong.

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

#101Amit Kapila
amit.kapila16@gmail.com
In reply to: Haribabu Kommi (#94)
Re: Pluggable storage

On Fri, Oct 13, 2017 at 1:58 PM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

On Fri, Oct 13, 2017 at 11:55 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Thu, Oct 12, 2017 at 8:00 PM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

Currently I added a snapshot_satisfies API to find out whether the tuple
satisfies the visibility or not with different types of visibility
routines.
I feel these
are some how enough to develop a different storage methods like UNDO.
The storage methods can decide internally how to provide the visibility.

+ amroutine->snapshot_satisfies[MVCC_VISIBILITY] =
HeapTupleSatisfiesMVCC;
+ amroutine->snapshot_satisfies[SELF_VISIBILITY] =
HeapTupleSatisfiesSelf;
+ amroutine->snapshot_satisfies[ANY_VISIBILITY] = HeapTupleSatisfiesAny;
+ amroutine->snapshot_satisfies[TOAST_VISIBILITY] =
HeapTupleSatisfiesToast;
+ amroutine->snapshot_satisfies[DIRTY_VISIBILITY] =
HeapTupleSatisfiesDirty;
+ amroutine->snapshot_satisfies[HISTORIC_MVCC_VISIBILITY] =
HeapTupleSatisfiesHistoricMVCC;
+ amroutine->snapshot_satisfies[NON_VACUUMABLE_VISIBILTY] =
HeapTupleSatisfiesNonVacuumable;
+
+ amroutine->snapshot_satisfiesUpdate = HeapTupleSatisfiesUpdate;
+ amroutine->snapshot_satisfiesVacuum = HeapTupleSatisfiesVacuum;

Currently no changes are carried out in snapshot logic as that is kept
seperate
from storage API.

That seems like a strange choice of API. I think it should more
integrated with the scan logic. For example, if I'm doing an index
scan, and I get a TID, then I should be able to just say "here's a
TID, give me any tuples associated with that TID that are visible to
the scan snapshot". Then for the current heap it will do
heap_hot_search_buffer, and for zheap it will walk the undo chain and
return the relevant tuple from the chain.

OK, Understood.
I will check along these lines and come up with storage API's.

I think what we need here is a way to register satisfies function
(SnapshotSatisfiesFunc) in SnapshotData for different storage engines.
That is the core API to decide visibility with respect to different
storage engines.

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#102Amit Kapila
amit.kapila16@gmail.com
In reply to: Alexander Korotkov (#100)
Re: Pluggable storage

On Sat, Oct 14, 2017 at 1:09 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Fri, Oct 13, 2017 at 9:41 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Oct 13, 2017 at 1:59 PM, Peter Geoghegan <pg@bowt.ie> wrote:

Fully agreed.

If we implement that interface, where does that leave EvalPlanQual()?

From the first glance, it seems that pluggable storage should override
EvalPlanQualFetch(), rest of EvalPlanQual() looks quite generic.

I think there is more to it. Currently, EState->es_epqTuple is a
HeapTuple which is filled as part of EvalPlanQual mechanism and then
later used during the scan. We need to make it pluggable in some way
so that other heaps can work. We also need some work for
EvalPlanQualFetchRowMarks as that also seems to be tightly coupled
with HeapTuple.

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#103Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#101)
Re: Pluggable storage

On Fri, Oct 20, 2017 at 5:47 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:

I think what we need here is a way to register satisfies function
(SnapshotSatisfiesFunc) in SnapshotData for different storage engines.

I don't see how that helps very much. SnapshotSatisfiesFunc takes a
HeapTuple as an argument, and it cares in detail about that tuple's
xmin, xmax, and infomask, and it sets hint bits. All of that is bad,
because an alternative storage engine is likely to use a different
format than HeapTuple and to not have hint bits (or at least not in
the same form we have them now). Also, it doesn't necessarily have a
Boolean answer to the question "can this snapshot see this tuple?".
It may be more like "given this TID, what tuple if any can I see
there?" or "given this tuple, what version of it would I see with this
snapshot?".

Another thing to consider is that, if we could replace satisfiesfunc,
it would probably break some existing code. There are multiple places
in the code that compare snapshot->satisfies to
HeapTupleSatisfiesHistoricMVCC and HeapTupleSatisfiesMVCC.

I think the storage API should just leave snapshots alone. If a
storage engine wants to call HeapTupleSatisfiesVisibility() with that
snapshot, it can do so. Otherwise it can switch on
snapshot->satisfies and handle each case however it likes. I don't
see how generalizing a Snapshot for other storage engines really buys
us anything except complexity and the danger of reducing performance
for the existing heap.

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#104Amit Kapila
amit.kapila16@gmail.com
In reply to: Robert Haas (#103)
Re: Pluggable storage

On Wed, Oct 25, 2017 at 11:37 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Oct 20, 2017 at 5:47 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:

I think what we need here is a way to register satisfies function
(SnapshotSatisfiesFunc) in SnapshotData for different storage engines.

I don't see how that helps very much. SnapshotSatisfiesFunc takes a
HeapTuple as an argument, and it cares in detail about that tuple's
xmin, xmax, and infomask, and it sets hint bits. All of that is bad,
because an alternative storage engine is likely to use a different
format than HeapTuple and to not have hint bits (or at least not in
the same form we have them now). Also, it doesn't necessarily have a
Boolean answer to the question "can this snapshot see this tuple?".
It may be more like "given this TID, what tuple if any can I see
there?" or "given this tuple, what version of it would I see with this
snapshot?".

Another thing to consider is that, if we could replace satisfiesfunc,
it would probably break some existing code. There are multiple places
in the code that compare snapshot->satisfies to
HeapTupleSatisfiesHistoricMVCC and HeapTupleSatisfiesMVCC.

I think the storage API should just leave snapshots alone. If a
storage engine wants to call HeapTupleSatisfiesVisibility() with that
snapshot, it can do so. Otherwise it can switch on
snapshot->satisfies and handle each case however it likes.

How will it switch satisfies at runtime? There are places where we
might know which visibility function (*MVCC , *Dirty, etc) needs to be
called, but I think there are other places (like heap_fetch) where it
is not clear and we decide based on what is stored in
snapshot->satisfies.

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#105Robert Haas
robertmhaas@gmail.com
In reply to: Amit Kapila (#104)
Re: Pluggable storage

On Wed, Oct 25, 2017 at 1:59 PM, Amit Kapila <amit.kapila16@gmail.com> wrote:

Another thing to consider is that, if we could replace satisfiesfunc,
it would probably break some existing code. There are multiple places
in the code that compare snapshot->satisfies to
HeapTupleSatisfiesHistoricMVCC and HeapTupleSatisfiesMVCC.

I think the storage API should just leave snapshots alone. If a
storage engine wants to call HeapTupleSatisfiesVisibility() with that
snapshot, it can do so. Otherwise it can switch on
snapshot->satisfies and handle each case however it likes.

How will it switch satisfies at runtime? There are places where we
might know which visibility function (*MVCC , *Dirty, etc) needs to be
called, but I think there are other places (like heap_fetch) where it
is not clear and we decide based on what is stored in
snapshot->satisfies.

An alternative storage engine needs to provide its own implementation
of heap_fetch, and that replacement implementation can implement MVCC
and other snapshot behavior in any way it likes.

My point here is that I think it's better if the table access method
stuff doesn't end up modifying snapshots. I think it's fine for a
table access method to get passed a standard snapshot. Some code may
be needed to cater to the access method's specific needs, but that
code can live inside the table access method, without contaminating
the snapshot stuff. We have to try to draw some boundary around table
access methods -- we don't want to end up teaching everything in the
system about them.

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#106Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Robert Haas (#105)
8 attachment(s)
Re: Pluggable storage

On Fri, Oct 27, 2017 at 4:06 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, Oct 25, 2017 at 1:59 PM, Amit Kapila <amit.kapila16@gmail.com>
wrote:

Another thing to consider is that, if we could replace satisfiesfunc,
it would probably break some existing code. There are multiple places
in the code that compare snapshot->satisfies to
HeapTupleSatisfiesHistoricMVCC and HeapTupleSatisfiesMVCC.

I think the storage API should just leave snapshots alone. If a
storage engine wants to call HeapTupleSatisfiesVisibility() with that
snapshot, it can do so. Otherwise it can switch on
snapshot->satisfies and handle each case however it likes.

How will it switch satisfies at runtime? There are places where we
might know which visibility function (*MVCC , *Dirty, etc) needs to be
called, but I think there are other places (like heap_fetch) where it
is not clear and we decide based on what is stored in
snapshot->satisfies.

An alternative storage engine needs to provide its own implementation
of heap_fetch, and that replacement implementation can implement MVCC
and other snapshot behavior in any way it likes.

In the current set of patches, I changed the snapshot->satisfies function
point to an enum type, Based on the snapshot visibility type, internally
the storage AM will call the corresponding visibility function.

Additional changes that are done in the patches compared to earlier
patches apart from rebase.

0004-Adding tuple visibility:

Tuple visibility API functions are reduced to 3. It still needs further
optimization.

Tuple satisfies visibility check is added to heap function that don't have
currently.

0006-tuple-insert

Move the index tuple insertion logic inside storage Am with a function
pointer, applicable to insert and updates. yet to handle the insert index
tuples
for multi insert scenario.

Removed the speculative finish API. Yet to remove abort API.

Known pending items:

1. Provide a generic new API like heap_fetch to remove heap_hot_search
and visibility functions usage.
2. Move toast table details into storage AM, Toast method depends on
storage.
and also toast flattening function needs to be replaced with some generic
functions.
3. Provide new API to get the heaptuple from index or building the index.
may be the same API like heap_fetch may satisfy this requirement also.
4. Bulk insert functionality needs a separate API to deal with all storage
AMs.
5. Provide a framework to add reloptions based on storage.
6. Needs a generic API to support rewrite the heap, (cluster command)

Regards,
Hari Babu
Fujitsu Australia

Attachments:

0007-Scan-functions-are-added-to-storage-AM.patchapplication/octet-stream; name=0007-Scan-functions-are-added-to-storage-AM.patchDownload
From 46f3c93cf2acb4a0943644cb5db76577f140db22 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Tue, 31 Oct 2017 17:49:18 +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           | 1511 ++--------------------------
 src/backend/access/heap/heapam_storage.c   | 1305 ++++++++++++++++++++++++
 src/backend/access/heap/rewriteheap.c      |    4 +-
 src/backend/access/heap/storageam.c        |  235 +++++
 src/backend/access/index/genam.c           |   11 +-
 src/backend/access/index/indexam.c         |   13 +-
 src/backend/access/nbtree/nbtinsert.c      |    7 +-
 src/backend/bootstrap/bootstrap.c          |   25 +-
 src/backend/catalog/aclchk.c               |   13 +-
 src/backend/catalog/index.c                |   59 +-
 src/backend/catalog/partition.c            |   24 +-
 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/trigger.c             |    3 +-
 src/backend/commands/typecmds.c            |   13 +-
 src/backend/commands/vacuum.c              |   17 +-
 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/postmaster/postmaster.c        |    4 +-
 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                |   30 +-
 src/include/access/heapam_common.h         |    8 +
 src/include/access/storageam.h             |   39 +
 src/include/executor/functions.h           |    2 +-
 src/include/executor/spi.h                 |   12 +-
 src/include/executor/tqueue.h              |    4 +-
 src/include/funcapi.h                      |    2 +-
 57 files changed, 1982 insertions(+), 1784 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 327ad30b2d..c785268921 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -81,19 +81,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 XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 				Buffer newbuf, HeapTuple oldtup,
 				HeapTuple newtup, HeapTuple old_key_tup,
@@ -112,139 +99,6 @@ static bool Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
 static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_modified,
 					   bool *copy);
 
-/* ----------------------------------------------------------------
- *						 heap support routines
- * ----------------------------------------------------------------
- */
-
-/* ----------------
- *		initscan - scan code common to heap_beginscan and heap_rescan
- * ----------------
- */
-static void
-initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
-{
-	bool		allow_strat;
-	bool		allow_sync;
-
-	/*
-	 * Determine the number of blocks we have to scan.
-	 *
-	 * It is sufficient to do this once at scan start, since any tuples added
-	 * while the scan is in progress will be invisible to my snapshot anyway.
-	 * (That is not true when using a non-MVCC snapshot.  However, we couldn't
-	 * guarantee to return tuples added after scan start anyway, since they
-	 * might go into pages we already scanned.  To guarantee consistent
-	 * results for a non-MVCC snapshot, the caller must hold some higher-level
-	 * lock that ensures the interesting tuple(s) won't change.)
-	 */
-	if (scan->rs_parallel != NULL)
-		scan->rs_nblocks = scan->rs_parallel->phs_nblocks;
-	else
-		scan->rs_nblocks = RelationGetNumberOfBlocks(scan->rs_rd);
-
-	/*
-	 * If the table is large relative to NBuffers, use a bulk-read access
-	 * strategy and enable synchronized scanning (see syncscan.c).  Although
-	 * the thresholds for these features could be different, we make them the
-	 * same so that there are only two behaviors to tune rather than four.
-	 * (However, some callers need to be able to disable one or both of these
-	 * behaviors, independently of the size of the table; also there is a GUC
-	 * variable that can disable synchronized scanning.)
-	 *
-	 * 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)
-	{
-		allow_strat = scan->rs_allow_strat;
-		allow_sync = scan->rs_allow_sync;
-	}
-	else
-		allow_strat = allow_sync = false;
-
-	if (allow_strat)
-	{
-		/* During a rescan, keep the previous strategy object. */
-		if (scan->rs_strategy == NULL)
-			scan->rs_strategy = GetAccessStrategy(BAS_BULKREAD);
-	}
-	else
-	{
-		if (scan->rs_strategy != NULL)
-			FreeAccessStrategy(scan->rs_strategy);
-		scan->rs_strategy = NULL;
-	}
-
-	if (scan->rs_parallel != NULL)
-	{
-		/* For parallel scan, believe whatever ParallelHeapScanDesc says. */
-		scan->rs_syncscan = scan->rs_parallel->phs_syncscan;
-	}
-	else if (keep_startblock)
-	{
-		/*
-		 * When rescanning, we want to keep the previous startblock setting,
-		 * so that rewinding a cursor doesn't generate surprising results.
-		 * Reset the active syncscan setting, though.
-		 */
-		scan->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);
-	}
-	else
-	{
-		scan->rs_syncscan = false;
-		scan->rs_startblock = 0;
-	}
-
-	scan->rs_numblocks = InvalidBlockNumber;
-	scan->rs_inited = false;
-	scan->rs_ctup.t_data = NULL;
-	ItemPointerSetInvalid(&scan->rs_ctup.t_self);
-	scan->rs_cbuf = InvalidBuffer;
-	scan->rs_cblock = InvalidBlockNumber;
-
-	/* page-at-a-time fields are always invalid when not rs_inited */
-
-	/*
-	 * copy the scan key, if appropriate
-	 */
-	if (key != NULL)
-		memcpy(scan->rs_key, key, scan->rs_nkeys * sizeof(ScanKeyData));
-
-	/*
-	 * Currently, we don't have a stats counter for bitmap heap scans (but the
-	 * underlying bitmap index scans will be counted) or sample scans (we only
-	 * update stats for tuple fetches there)
-	 */
-	if (!scan->rs_bitmapscan && !scan->rs_samplescan)
-		pgstat_count_heap_scan(scan->rs_rd);
-}
-
-/*
- * 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
-heap_setscanlimits(HeapScanDesc scan, BlockNumber startBlk, BlockNumber numBlks)
-{
-	Assert(!scan->rs_inited);	/* else too late to change */
-	Assert(!scan->rs_syncscan); /* else rs_startblock is significant */
-
-	/* Check startBlk is valid (but allow case of zero blocks...) */
-	Assert(startBlk == 0 || startBlk < scan->rs_nblocks);
-
-	scan->rs_startblock = startBlk;
-	scan->rs_numblocks = numBlks;
-}
-
 /*
  * heapgetpage - subroutine for heapgettup()
  *
@@ -363,610 +217,6 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 	scan->rs_ntuples = ntup;
 }
 
-/* ----------------
- *		heapgettup - fetch next heap tuple
- *
- *		Initialize the scan if not already done; then advance to the next
- *		tuple as indicated by "dir"; return the next tuple in scan->rs_ctup,
- *		or set scan->rs_ctup.t_data = NULL if no more tuples.
- *
- * dir == NoMovementScanDirection means "re-fetch the tuple indicated
- * by scan->rs_ctup".
- *
- * Note: the reason nkeys/key are passed separately, even though they are
- * kept in the scan descriptor, is that the caller may not want us to check
- * the scankeys.
- *
- * Note: when we fall off the end of the scan in either direction, we
- * reset rs_inited.  This means that a further request with the same
- * scan direction will restart the scan, which is a bit odd, but a
- * request with the opposite scan direction will start a fresh scan
- * in the proper direction.  The latter is required behavior for cursors,
- * while the former case is generally undefined behavior in Postgres
- * so we don't care too much.
- * ----------------
- */
-static void
-heapgettup(HeapScanDesc scan,
-		   ScanDirection dir,
-		   int nkeys,
-		   ScanKey key)
-{
-	HeapTuple	tuple = &(scan->rs_ctup);
-	Snapshot	snapshot = scan->rs_snapshot;
-	bool		backward = ScanDirectionIsBackward(dir);
-	BlockNumber page;
-	bool		finished;
-	Page		dp;
-	int			lines;
-	OffsetNumber lineoff;
-	int			linesleft;
-	ItemId		lpp;
-
-	/*
-	 * calculate next starting lineoff, given scan direction
-	 */
-	if (ScanDirectionIsForward(dir))
-	{
-		if (!scan->rs_inited)
-		{
-			/*
-			 * return null immediately if relation is empty
-			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
-			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
-				tuple->t_data = NULL;
-				return;
-			}
-			if (scan->rs_parallel != NULL)
-			{
-				heap_parallelscan_startblock_init(scan);
-
-				page = heap_parallelscan_nextpage(scan);
-
-				/* Other processes might have already finished the scan. */
-				if (page == InvalidBlockNumber)
-				{
-					Assert(!BufferIsValid(scan->rs_cbuf));
-					tuple->t_data = NULL;
-					return;
-				}
-			}
-			else
-				page = scan->rs_startblock; /* first page */
-			heapgetpage(scan, page);
-			lineoff = FirstOffsetNumber;	/* first offnum */
-			scan->rs_inited = true;
-		}
-		else
-		{
-			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-			lineoff =			/* next offnum */
-				OffsetNumberNext(ItemPointerGetOffsetNumber(&(tuple->t_self)));
-		}
-
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
-		lines = PageGetMaxOffsetNumber(dp);
-		/* page and lineoff now reference the physically next tid */
-
-		linesleft = lines - lineoff + 1;
-	}
-	else if (backward)
-	{
-		/* backward parallel scan not supported */
-		Assert(scan->rs_parallel == NULL);
-
-		if (!scan->rs_inited)
-		{
-			/*
-			 * return null immediately if relation is empty
-			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
-			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
-				tuple->t_data = NULL;
-				return;
-			}
-
-			/*
-			 * Disable reporting to syncscan logic in a backwards scan; it's
-			 * not very likely anyone else is doing the same thing at the same
-			 * time, and much more likely that we'll just bollix things for
-			 * forward scanners.
-			 */
-			scan->rs_syncscan = false;
-			/* start from last page of the scan */
-			if (scan->rs_startblock > 0)
-				page = scan->rs_startblock - 1;
-			else
-				page = scan->rs_nblocks - 1;
-			heapgetpage(scan, page);
-		}
-		else
-		{
-			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-		}
-
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
-		lines = PageGetMaxOffsetNumber(dp);
-
-		if (!scan->rs_inited)
-		{
-			lineoff = lines;	/* final offnum */
-			scan->rs_inited = true;
-		}
-		else
-		{
-			lineoff =			/* previous offnum */
-				OffsetNumberPrev(ItemPointerGetOffsetNumber(&(tuple->t_self)));
-		}
-		/* page and lineoff now reference the physically previous tid */
-
-		linesleft = lineoff;
-	}
-	else
-	{
-		/*
-		 * ``no movement'' scan direction: refetch prior tuple
-		 */
-		if (!scan->rs_inited)
-		{
-			Assert(!BufferIsValid(scan->rs_cbuf));
-			tuple->t_data = NULL;
-			return;
-		}
-
-		page = ItemPointerGetBlockNumber(&(tuple->t_self));
-		if (page != scan->rs_cblock)
-			heapgetpage(scan, page);
-
-		/* Since the tuple was previously fetched, needn't lock page here */
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
-		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
-		lpp = PageGetItemId(dp, lineoff);
-		Assert(ItemIdIsNormal(lpp));
-
-		tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
-		tuple->t_len = ItemIdGetLength(lpp);
-
-		return;
-	}
-
-	/*
-	 * advance the scan until we find a qualifying tuple or run out of stuff
-	 * to scan
-	 */
-	lpp = PageGetItemId(dp, lineoff);
-	for (;;)
-	{
-		while (linesleft > 0)
-		{
-			if (ItemIdIsNormal(lpp))
-			{
-				bool		valid;
-
-				tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
-				tuple->t_len = ItemIdGetLength(lpp);
-				ItemPointerSet(&(tuple->t_self), page, lineoff);
-
-				/*
-				 * if current tuple qualifies, return it.
-				 */
-				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine,
-													 tuple,
-													 snapshot,
-													 scan->rs_cbuf);
-
-				CheckForSerializableConflictOut(valid, scan->rs_rd, tuple,
-												scan->rs_cbuf, snapshot);
-
-				if (valid && key != NULL)
-					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
-								nkeys, key, valid);
-
-				if (valid)
-				{
-					LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
-					return;
-				}
-			}
-
-			/*
-			 * otherwise move to the next item on the page
-			 */
-			--linesleft;
-			if (backward)
-			{
-				--lpp;			/* move back in this page's ItemId array */
-				--lineoff;
-			}
-			else
-			{
-				++lpp;			/* move forward in this page's ItemId array */
-				++lineoff;
-			}
-		}
-
-		/*
-		 * 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);
-
-		/*
-		 * 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);
-			if (page == 0)
-				page = scan->rs_nblocks;
-			page--;
-		}
-		else if (scan->rs_parallel != NULL)
-		{
-			page = heap_parallelscan_nextpage(scan);
-			finished = (page == InvalidBlockNumber);
-		}
-		else
-		{
-			page++;
-			if (page >= scan->rs_nblocks)
-				page = 0;
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
-
-			/*
-			 * Report our new scan position for synchronization purposes. We
-			 * don't do that when moving backwards, however. That would just
-			 * mess up any other forward-moving scanners.
-			 *
-			 * Note: we do this before checking for end of scan so that the
-			 * final state of the position hint is back at the start of the
-			 * rel.  That's not strictly necessary, but otherwise when you run
-			 * the same query multiple times the starting position would shift
-			 * 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);
-		}
-
-		/*
-		 * return NULL if we've exhausted all the pages
-		 */
-		if (finished)
-		{
-			if (BufferIsValid(scan->rs_cbuf))
-				ReleaseBuffer(scan->rs_cbuf);
-			scan->rs_cbuf = InvalidBuffer;
-			scan->rs_cblock = InvalidBlockNumber;
-			tuple->t_data = NULL;
-			scan->rs_inited = false;
-			return;
-		}
-
-		heapgetpage(scan, page);
-
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
-		lines = PageGetMaxOffsetNumber((Page) dp);
-		linesleft = lines;
-		if (backward)
-		{
-			lineoff = lines;
-			lpp = PageGetItemId(dp, lines);
-		}
-		else
-		{
-			lineoff = FirstOffsetNumber;
-			lpp = PageGetItemId(dp, FirstOffsetNumber);
-		}
-	}
-}
-
-/* ----------------
- *		heapgettup_pagemode - fetch next heap tuple in page-at-a-time mode
- *
- *		Same API as heapgettup, but used in page-at-a-time mode
- *
- * The internal logic is much the same as heapgettup's too, but there are some
- * differences: we do not take the buffer content lock (that only needs to
- * happen inside heapgetpage), and we iterate through just the tuples listed
- * in rs_vistuples[] rather than all tuples on the page.  Notice that
- * lineindex is 0-based, where the corresponding loop variable lineoff in
- * heapgettup is 1-based.
- * ----------------
- */
-static void
-heapgettup_pagemode(HeapScanDesc scan,
-					ScanDirection dir,
-					int nkeys,
-					ScanKey key)
-{
-	HeapTuple	tuple = &(scan->rs_ctup);
-	bool		backward = ScanDirectionIsBackward(dir);
-	BlockNumber page;
-	bool		finished;
-	Page		dp;
-	int			lines;
-	int			lineindex;
-	OffsetNumber lineoff;
-	int			linesleft;
-	ItemId		lpp;
-
-	/*
-	 * calculate next starting lineindex, given scan direction
-	 */
-	if (ScanDirectionIsForward(dir))
-	{
-		if (!scan->rs_inited)
-		{
-			/*
-			 * return null immediately if relation is empty
-			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
-			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
-				tuple->t_data = NULL;
-				return;
-			}
-			if (scan->rs_parallel != NULL)
-			{
-				heap_parallelscan_startblock_init(scan);
-
-				page = heap_parallelscan_nextpage(scan);
-
-				/* Other processes might have already finished the scan. */
-				if (page == InvalidBlockNumber)
-				{
-					Assert(!BufferIsValid(scan->rs_cbuf));
-					tuple->t_data = NULL;
-					return;
-				}
-			}
-			else
-				page = scan->rs_startblock; /* first page */
-			heapgetpage(scan, page);
-			lineindex = 0;
-			scan->rs_inited = true;
-		}
-		else
-		{
-			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-			lineindex = scan->rs_cindex + 1;
-		}
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
-		/* page and lineindex now reference the next visible tid */
-
-		linesleft = lines - lineindex;
-	}
-	else if (backward)
-	{
-		/* backward parallel scan not supported */
-		Assert(scan->rs_parallel == NULL);
-
-		if (!scan->rs_inited)
-		{
-			/*
-			 * return null immediately if relation is empty
-			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
-			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
-				tuple->t_data = NULL;
-				return;
-			}
-
-			/*
-			 * Disable reporting to syncscan logic in a backwards scan; it's
-			 * not very likely anyone else is doing the same thing at the same
-			 * time, and much more likely that we'll just bollix things for
-			 * forward scanners.
-			 */
-			scan->rs_syncscan = false;
-			/* start from last page of the scan */
-			if (scan->rs_startblock > 0)
-				page = scan->rs_startblock - 1;
-			else
-				page = scan->rs_nblocks - 1;
-			heapgetpage(scan, page);
-		}
-		else
-		{
-			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-		}
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
-
-		if (!scan->rs_inited)
-		{
-			lineindex = lines - 1;
-			scan->rs_inited = true;
-		}
-		else
-		{
-			lineindex = scan->rs_cindex - 1;
-		}
-		/* page and lineindex now reference the previous visible tid */
-
-		linesleft = lineindex + 1;
-	}
-	else
-	{
-		/*
-		 * ``no movement'' scan direction: refetch prior tuple
-		 */
-		if (!scan->rs_inited)
-		{
-			Assert(!BufferIsValid(scan->rs_cbuf));
-			tuple->t_data = NULL;
-			return;
-		}
-
-		page = ItemPointerGetBlockNumber(&(tuple->t_self));
-		if (page != scan->rs_cblock)
-			heapgetpage(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);
-		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
-		lpp = PageGetItemId(dp, lineoff);
-		Assert(ItemIdIsNormal(lpp));
-
-		tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
-		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]);
-
-		return;
-	}
-
-	/*
-	 * advance the scan until we find a qualifying tuple or run out of stuff
-	 * to scan
-	 */
-	for (;;)
-	{
-		while (linesleft > 0)
-		{
-			lineoff = scan->rs_vistuples[lineindex];
-			lpp = PageGetItemId(dp, lineoff);
-			Assert(ItemIdIsNormal(lpp));
-
-			tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
-			tuple->t_len = ItemIdGetLength(lpp);
-			ItemPointerSet(&(tuple->t_self), page, lineoff);
-
-			/*
-			 * if current tuple qualifies, return it.
-			 */
-			if (HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, tuple, scan->rs_snapshot, scan->rs_cbuf))
-			{
-				/*
-				 * if current tuple qualifies, return it.
-				 */
-				if (key != NULL)
-				{
-					bool		valid;
-
-					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
-								nkeys, key, valid);
-					if (valid)
-					{
-						scan->rs_cindex = lineindex;
-						return;
-					}
-				}
-				else
-				{
-					scan->rs_cindex = lineindex;
-					return;
-				}
-			}
-
-			/*
-			 * otherwise move to the next item on the page
-			 */
-			--linesleft;
-			if (backward)
-				--lineindex;
-			else
-				++lineindex;
-		}
-
-		/*
-		 * if we get here, it means we've exhausted the items on this page and
-		 * it's time to move to the next.
-		 */
-		if (backward)
-		{
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
-			if (page == 0)
-				page = scan->rs_nblocks;
-			page--;
-		}
-		else if (scan->rs_parallel != NULL)
-		{
-			page = heap_parallelscan_nextpage(scan);
-			finished = (page == InvalidBlockNumber);
-		}
-		else
-		{
-			page++;
-			if (page >= scan->rs_nblocks)
-				page = 0;
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
-
-			/*
-			 * Report our new scan position for synchronization purposes. We
-			 * don't do that when moving backwards, however. That would just
-			 * mess up any other forward-moving scanners.
-			 *
-			 * Note: we do this before checking for end of scan so that the
-			 * final state of the position hint is back at the start of the
-			 * rel.  That's not strictly necessary, but otherwise when you run
-			 * the same query multiple times the starting position would shift
-			 * 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);
-		}
-
-		/*
-		 * return NULL if we've exhausted all the pages
-		 */
-		if (finished)
-		{
-			if (BufferIsValid(scan->rs_cbuf))
-				ReleaseBuffer(scan->rs_cbuf);
-			scan->rs_cbuf = InvalidBuffer;
-			scan->rs_cblock = InvalidBlockNumber;
-			tuple->t_data = NULL;
-			scan->rs_inited = false;
-			return;
-		}
-
-		heapgetpage(scan, page);
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
-		linesleft = lines;
-		if (backward)
-			lineindex = lines - 1;
-		else
-			lineindex = 0;
-	}
-}
-
-
 #if defined(DISABLE_COMPLEX_MACRO)
 /*
  * This is formatted so oddly so that the correspondence to the macro
@@ -1189,320 +439,99 @@ relation_close(Relation relation, LOCKMODE lockmode)
 	RelationClose(relation);
 
 	if (lockmode != NoLock)
-		UnlockRelationId(&relid, lockmode);
-}
-
-
-/* ----------------
- *		heap_open - open a heap relation by relation OID
- *
- *		This is essentially relation_open plus check that the relation
- *		is not an index nor a composite type.  (The caller should also
- *		check that it's not a view or foreign table before assuming it has
- *		storage.)
- * ----------------
- */
-Relation
-heap_open(Oid relationId, LOCKMODE lockmode)
-{
-	Relation	r;
-
-	r = relation_open(relationId, lockmode);
-
-	if (r->rd_rel->relkind == RELKIND_INDEX)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is an index",
-						RelationGetRelationName(r))));
-	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is a composite type",
-						RelationGetRelationName(r))));
-
-	return r;
-}
-
-/* ----------------
- *		heap_openrv - open a heap relation specified
- *		by a RangeVar node
- *
- *		As above, but relation is specified by a RangeVar.
- * ----------------
- */
-Relation
-heap_openrv(const RangeVar *relation, LOCKMODE lockmode)
-{
-	Relation	r;
-
-	r = relation_openrv(relation, lockmode);
-
-	if (r->rd_rel->relkind == RELKIND_INDEX)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is an index",
-						RelationGetRelationName(r))));
-	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is a composite type",
-						RelationGetRelationName(r))));
-
-	return r;
-}
-
-/* ----------------
- *		heap_openrv_extended - open a heap relation specified
- *		by a RangeVar node
- *
- *		As above, but optionally return NULL instead of failing for
- *		relation-not-found.
- * ----------------
- */
-Relation
-heap_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
-					 bool missing_ok)
-{
-	Relation	r;
-
-	r = relation_openrv_extended(relation, lockmode, missing_ok);
-
-	if (r)
-	{
-		if (r->rd_rel->relkind == RELKIND_INDEX)
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("\"%s\" is an index",
-							RelationGetRelationName(r))));
-		else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("\"%s\" is a composite type",
-							RelationGetRelationName(r))));
-	}
-
-	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)
-{
-	HeapScanDesc scan;
-
-	/*
-	 * increment relation ref count while scanning relation
-	 *
-	 * This is just to make really sure the relcache entry won't go away while
-	 * the scan has a pointer to it.  Caller should be holding the rel open
-	 * anyway, so this is redundant in all normal scenarios...
-	 */
-	RelationIncrementReferenceCount(relation);
-
-	/*
-	 * allocate and initialize scan descriptor
-	 */
-	scan = (HeapScanDesc) palloc(sizeof(HeapScanDescData));
-
-	scan->rs_rd = relation;
-	scan->rs_snapshot = snapshot;
-	scan->rs_nkeys = nkeys;
-	scan->rs_bitmapscan = is_bitmapscan;
-	scan->rs_samplescan = is_samplescan;
-	scan->rs_strategy = NULL;	/* set in initscan */
-	scan->rs_allow_strat = allow_strat;
-	scan->rs_allow_sync = allow_sync;
-	scan->rs_temp_snap = temp_snap;
-	scan->rs_parallel = parallel_scan;
-
-	/*
-	 * we can use page-at-a-time mode if it's an MVCC-safe snapshot
-	 */
-	scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(snapshot);
-
-	/*
-	 * For a seqscan in a serializable transaction, acquire a predicate lock
-	 * on the entire relation. This is required not only to lock all the
-	 * matching tuples, but also to conflict with new insertions into the
-	 * table. In an indexscan, we take page locks on the index pages covering
-	 * the range specified in the scan qual, but in a heap scan there is
-	 * nothing more fine-grained to lock. A bitmap scan is a different story,
-	 * there we have already scanned the index and locked the index pages
-	 * covering the predicate. But in that case we still have to lock any
-	 * matching heap tuples.
-	 */
-	if (!is_bitmapscan)
-		PredicateLockRelation(relation, snapshot);
-
-	/* we only need to set this up once */
-	scan->rs_ctup.t_tableOid = RelationGetRelid(relation);
-
-	/*
-	 * we do this here instead of in initscan() because heap_rescan also calls
-	 * initscan() and we don't want to allocate memory again
-	 */
-	if (nkeys > 0)
-		scan->rs_key = (ScanKey) palloc(sizeof(ScanKeyData) * nkeys);
-	else
-		scan->rs_key = NULL;
-
-	initscan(scan, key, false);
-
-	return scan;
+		UnlockRelationId(&relid, lockmode);
 }
 
+
 /* ----------------
- *		heap_rescan		- restart a relation scan
+ *		heap_open - open a heap relation by relation OID
+ *
+ *		This is essentially relation_open plus check that the relation
+ *		is not an index nor a composite type.  (The caller should also
+ *		check that it's not a view or foreign table before assuming it has
+ *		storage.)
  * ----------------
  */
-void
-heap_rescan(HeapScanDesc scan,
-			ScanKey key)
+Relation
+heap_open(Oid relationId, LOCKMODE lockmode)
 {
-	/*
-	 * unpin scan buffers
-	 */
-	if (BufferIsValid(scan->rs_cbuf))
-		ReleaseBuffer(scan->rs_cbuf);
+	Relation	r;
 
-	/*
-	 * reinitialize scan descriptor
-	 */
-	initscan(scan, key, true);
+	r = relation_open(relationId, lockmode);
+
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is an index",
+						RelationGetRelationName(r))));
+	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is a composite type",
+						RelationGetRelationName(r))));
+
+	return r;
 }
 
 /* ----------------
- *		heap_rescan_set_params	- restart a relation scan after changing params
+ *		heap_openrv - open a heap relation specified
+ *		by a RangeVar node
  *
- * 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.
+ *		As above, but relation is specified by a RangeVar.
  * ----------------
  */
-void
-heap_rescan_set_params(HeapScanDesc scan, ScanKey key,
-					   bool allow_strat, bool allow_sync, bool allow_pagemode)
+Relation
+heap_openrv(const RangeVar *relation, LOCKMODE lockmode)
 {
-	/* 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);
+	Relation	r;
+
+	r = relation_openrv(relation, lockmode);
+
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is an index",
+						RelationGetRelationName(r))));
+	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is a composite type",
+						RelationGetRelationName(r))));
+
+	return r;
 }
 
 /* ----------------
- *		heap_endscan	- end relation scan
+ *		heap_openrv_extended - open a heap relation specified
+ *		by a RangeVar node
  *
- *		See how to integrate with index scans.
- *		Check handling if reldesc caching.
+ *		As above, but optionally return NULL instead of failing for
+ *		relation-not-found.
  * ----------------
  */
-void
-heap_endscan(HeapScanDesc scan)
+Relation
+heap_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
+					 bool missing_ok)
 {
-	/* Note: no locking manipulations needed */
-
-	/*
-	 * unpin scan buffers
-	 */
-	if (BufferIsValid(scan->rs_cbuf))
-		ReleaseBuffer(scan->rs_cbuf);
-
-	/*
-	 * decrement relation reference count and free scan descriptor storage
-	 */
-	RelationDecrementReferenceCount(scan->rs_rd);
-
-	if (scan->rs_key)
-		pfree(scan->rs_key);
+	Relation	r;
 
-	if (scan->rs_strategy != NULL)
-		FreeAccessStrategy(scan->rs_strategy);
+	r = relation_openrv_extended(relation, lockmode, missing_ok);
 
-	if (scan->rs_temp_snap)
-		UnregisterSnapshot(scan->rs_snapshot);
+	if (r)
+	{
+		if (r->rd_rel->relkind == RELKIND_INDEX)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("\"%s\" is an index",
+							RelationGetRelationName(r))));
+		else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("\"%s\" is a composite type",
+							RelationGetRelationName(r))));
+	}
 
-	pfree(scan);
+	return r;
 }
 
 /* ----------------
@@ -1556,382 +585,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
- *
- *		Determine where the parallel seq scan should start.  This function may
- *		be called many times, once by each parallel worker.  We must be careful
- *		only to set the startblock once.
- * ----------------
- */
-static void
-heap_parallelscan_startblock_init(HeapScanDesc scan)
-{
-	BlockNumber sync_startpage = InvalidBlockNumber;
-	ParallelHeapScanDesc parallel_scan;
-
-	Assert(scan->rs_parallel);
-	parallel_scan = scan->rs_parallel;
-
-retry:
-	/* Grab the spinlock. */
-	SpinLockAcquire(&parallel_scan->phs_mutex);
-
-	/*
-	 * If the scan's startblock has not yet been initialized, we must do so
-	 * now.  If this is not a synchronized scan, we just start at block 0, but
-	 * if it is a synchronized scan, we must get the starting position from
-	 * the synchronized scan machinery.  We can't hold the spinlock while
-	 * doing that, though, so release the spinlock, get the information we
-	 * need, and retry.  If nobody else has initialized the scan in the
-	 * meantime, we'll fill in the value we fetched on the second time
-	 * through.
-	 */
-	if (parallel_scan->phs_startblock == InvalidBlockNumber)
-	{
-		if (!parallel_scan->phs_syncscan)
-			parallel_scan->phs_startblock = 0;
-		else if (sync_startpage != InvalidBlockNumber)
-			parallel_scan->phs_startblock = sync_startpage;
-		else
-		{
-			SpinLockRelease(&parallel_scan->phs_mutex);
-			sync_startpage = ss_get_location(scan->rs_rd, scan->rs_nblocks);
-			goto retry;
-		}
-	}
-	SpinLockRelease(&parallel_scan->phs_mutex);
-}
-
-/* ----------------
- *		heap_parallelscan_nextpage - get the next page to scan
- *
- *		Get the next page to scan.  Even if there are no pages left to scan,
- *		another backend could have grabbed a page to scan and not yet finished
- *		looking at it, so it doesn't follow that the scan is done when the
- *		first backend gets an InvalidBlockNumber return.
- * ----------------
- */
-static BlockNumber
-heap_parallelscan_nextpage(HeapScanDesc scan)
-{
-	BlockNumber page;
-	ParallelHeapScanDesc parallel_scan;
-	uint64		nallocated;
-
-	Assert(scan->rs_parallel);
-	parallel_scan = scan->rs_parallel;
-
-	/*
-	 * phs_nallocated tracks how many pages have been allocated to workers
-	 * already.  When phs_nallocated >= rs_nblocks, all blocks have been
-	 * allocated.
-	 *
-	 * Because we use an atomic fetch-and-add to fetch the current value, the
-	 * phs_nallocated counter will exceed rs_nblocks, because workers will
-	 * still increment the value, when they try to allocate the next block but
-	 * all blocks have been allocated already. The counter must be 64 bits
-	 * wide because of that, to avoid wrapping around when rs_nblocks is close
-	 * to 2^32.
-	 *
-	 * The actual page to return is calculated by adding the counter to the
-	 * starting block number, modulo nblocks.
-	 */
-	nallocated = pg_atomic_fetch_add_u64(&parallel_scan->phs_nallocated, 1);
-	if (nallocated >= scan->rs_nblocks)
-		page = InvalidBlockNumber;	/* all blocks have been allocated */
-	else
-		page = (nallocated + parallel_scan->phs_startblock) % scan->rs_nblocks;
-
-	/*
-	 * Report scan location.  Normally, we report the current page number.
-	 * When we reach the end of the scan, though, we report the starting page,
-	 * not the ending page, just so the starting positions for later scans
-	 * 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 (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);
-	}
-
-	return page;
-}
-
-/* ----------------
- *		heap_update_snapshot
- *
- *		Update snapshot info in heap scan descriptor.
- * ----------------
- */
-void
-heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
-{
-	Assert(IsMVCCSnapshot(snapshot));
-
-	RegisterSnapshot(snapshot);
-	scan->rs_snapshot = snapshot;
-	scan->rs_temp_snap = true;
-}
-
-/* ----------------
- *		heap_getnext	- retrieve next tuple in scan
- *
- *		Fix to work with index relations.
- *		We don't return the buffer anymore, but you can get it from the
- *		returned HeapTuple.
- * ----------------
- */
-
-#ifdef HEAPDEBUGALL
-#define HEAPDEBUG_1 \
-	elog(DEBUG2, "heap_getnext([%s,nkeys=%d],dir=%d) called", \
-		 RelationGetRelationName(scan->rs_rd), scan->rs_nkeys, (int) direction)
-#define HEAPDEBUG_2 \
-	elog(DEBUG2, "heap_getnext returning EOS")
-#define HEAPDEBUG_3 \
-	elog(DEBUG2, "heap_getnext returning tuple")
-#else
-#define HEAPDEBUG_1
-#define HEAPDEBUG_2
-#define HEAPDEBUG_3
-#endif							/* !defined(HEAPDEBUGALL) */
-
-
-HeapTuple
-heap_getnext(HeapScanDesc scan, ScanDirection direction)
-{
-	/* Note: no locking manipulations needed */
-
-	HEAPDEBUG_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)
-	{
-		HEAPDEBUG_2;			/* heap_getnext returning EOS */
-		return NULL;
-	}
-
-	/*
-	 * if we get here it means we have a new current scan tuple, so point to
-	 * the proper return buffer and return the tuple.
-	 */
-	HEAPDEBUG_3;				/* heap_getnext returning tuple */
-
-	pgstat_count_heap_getnext(scan->rs_rd);
-
-	return heap_copytuple(&(scan->rs_ctup));
-}
-
-/*
- *	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
-heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
-					   Snapshot snapshot, HeapTuple heapTuple,
-					   bool *all_dead, bool first_call)
-{
-	Page		dp = (Page) BufferGetPage(buffer);
-	TransactionId prev_xmax = InvalidTransactionId;
-	OffsetNumber offnum;
-	bool		at_chain_start;
-	bool		valid;
-	bool		skip;
-
-	/* If this is not the first call, previous call returned a (live!) tuple */
-	if (all_dead)
-		*all_dead = first_call;
-
-	Assert(TransactionIdIsValid(RecentGlobalXmin));
-
-	Assert(ItemPointerGetBlockNumber(tid) == BufferGetBlockNumber(buffer));
-	offnum = ItemPointerGetOffsetNumber(tid);
-	at_chain_start = first_call;
-	skip = !first_call;
-
-	heapTuple->t_self = *tid;
-
-	/* Scan through possible multiple members of HOT-chain */
-	for (;;)
-	{
-		ItemId		lp;
-
-		/* check for bogus TID */
-		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(dp))
-			break;
-
-		lp = PageGetItemId(dp, offnum);
-
-		/* check for unused, dead, or redirected items */
-		if (!ItemIdIsNormal(lp))
-		{
-			/* We should only see a redirect at start of chain */
-			if (ItemIdIsRedirected(lp) && at_chain_start)
-			{
-				/* Follow the redirect */
-				offnum = ItemIdGetRedirect(lp);
-				at_chain_start = false;
-				continue;
-			}
-			/* else must be end of chain */
-			break;
-		}
-
-		heapTuple->t_data = (HeapTupleHeader) PageGetItem(dp, lp);
-		heapTuple->t_len = ItemIdGetLength(lp);
-		heapTuple->t_tableOid = RelationGetRelid(relation);
-		ItemPointerSetOffsetNumber(&heapTuple->t_self, offnum);
-
-		/*
-		 * Shouldn't see a HEAP_ONLY tuple at chain start.
-		 */
-		if (at_chain_start && HeapTupleIsHeapOnly(heapTuple))
-			break;
-
-		/*
-		 * The xmin should match the previous xmax value, else chain is
-		 * broken.
-		 */
-		if (TransactionIdIsValid(prev_xmax) &&
-			!HeapTupleUpdateXmaxMatchesXmin(prev_xmax, heapTuple->t_data))
-			break;
-
-		/*
-		 * When first_call is true (and thus, skip is initially false) we'll
-		 * return the first tuple we find.  But on later passes, heapTuple
-		 * will initially be pointing to the tuple we returned last time.
-		 * Returning it again would be incorrect (and would loop forever), so
-		 * we skip it and return the next match we find.
-		 */
-		if (!skip)
-		{
-			/*
-			 * For the benefit of logical decoding, have t_self point at the
-			 * element of the HOT chain we're currently investigating instead
-			 * of the root tuple of the HOT chain. This is important because
-			 * the *Satisfies routine for historical mvcc snapshots needs the
-			 * correct tid to decide about the visibility in some cases.
-			 */
-			ItemPointerSet(&(heapTuple->t_self), BufferGetBlockNumber(buffer), offnum);
-
-			/* If it's visible per the snapshot, we must return it */
-			valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, heapTuple, snapshot, buffer);
-			CheckForSerializableConflictOut(valid, relation, heapTuple,
-											buffer, snapshot);
-			/* reset to original, non-redirected, tid */
-			heapTuple->t_self = *tid;
-
-			if (valid)
-			{
-				ItemPointerSetOffsetNumber(tid, offnum);
-				PredicateLockTuple(relation, heapTuple, snapshot);
-				if (all_dead)
-					*all_dead = false;
-				return true;
-			}
-		}
-		skip = false;
-
-		/*
-		 * If we can't see it, maybe no one else can either.  At caller
-		 * request, check whether all chain members are dead to all
-		 * transactions.
-		 *
-		 * Note: if you change the criterion here for what is "dead", fix the
-		 * planner's get_actual_variable_range() function to match.
-		 */
-		if (all_dead && *all_dead &&
-			!HeapTupleIsSurelyDead(heapTuple, RecentGlobalXmin))
-			*all_dead = false;
-
-		/*
-		 * Check to see if HOT chain continues past this tuple; if so fetch
-		 * the next offnum and loop around.
-		 */
-		if (HeapTupleIsHotUpdated(heapTuple))
-		{
-			Assert(ItemPointerGetBlockNumber(&heapTuple->t_data->t_ctid) ==
-				   ItemPointerGetBlockNumber(tid));
-			offnum = ItemPointerGetOffsetNumber(&heapTuple->t_data->t_ctid);
-			at_chain_start = false;
-			prev_xmax = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
-		}
-		else
-			break;				/* end of chain */
-	}
-
-	return false;
-}
-
-/*
- *	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
-heap_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 = heap_hot_search_buffer(tid, relation, buffer, snapshot,
-									&heapTuple, all_dead, true);
-	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-	ReleaseBuffer(buffer);
-	return result;
-}
-
 /*
  * HeapTupleUpdateXmaxMatchesXmin - verify update chain xmax/xmin lineage
  *
@@ -4835,32 +3488,6 @@ heap_execute_freeze_tuple(HeapTupleHeader tuple, xl_heap_freeze_tuple *frz)
 	tuple->t_infomask2 = frz->t_infomask2;
 }
 
-/*
- * heap_freeze_tuple
- *		Freeze tuple in place, without WAL logging.
- *
- * Useful for callers like CLUSTER that perform their own WAL logging.
- */
-bool
-heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
-				  TransactionId cutoff_multi)
-{
-	xl_heap_freeze_tuple frz;
-	bool		do_freeze;
-	bool		tuple_totally_frozen;
-
-	do_freeze = heap_prepare_freeze_tuple(tuple, cutoff_xid, cutoff_multi,
-										  &frz, &tuple_totally_frozen);
-
-	/*
-	 * Note that because this is not a WAL-logged operation, we don't need to
-	 * fill in the offset in the freeze record.
-	 */
-
-	if (do_freeze)
-		heap_execute_freeze_tuple(tuple, &frz);
-	return do_freeze;
-}
 
 /*
  * For a given MultiXactId, return the hint bits that should be set in the
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index d6352df472..6e1817b1fd 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -1700,6 +1700,1100 @@ HeapTupleSatisfies(StorageTuple stup, Snapshot snapshot, Buffer buffer)
 	}
 }
 
+/* ----------------------------------------------------------------
+ *						 heap support routines
+ * ----------------------------------------------------------------
+ */
+
+/* ----------------
+ *		heap_parallelscan_startblock_init - find and set the scan's startblock
+ *
+ *		Determine where the parallel seq scan should start.  This function may
+ *		be called many times, once by each parallel worker.  We must be careful
+ *		only to set the startblock once.
+ * ----------------
+ */
+static void
+heap_parallelscan_startblock_init(HeapScanDesc scan)
+{
+	BlockNumber sync_startpage = InvalidBlockNumber;
+	ParallelHeapScanDesc parallel_scan;
+
+	Assert(scan->rs_parallel);
+	parallel_scan = scan->rs_parallel;
+
+retry:
+	/* Grab the spinlock. */
+	SpinLockAcquire(&parallel_scan->phs_mutex);
+
+	/*
+	 * If the scan's startblock has not yet been initialized, we must do so
+	 * now.  If this is not a synchronized scan, we just start at block 0, but
+	 * if it is a synchronized scan, we must get the starting position from
+	 * the synchronized scan machinery.  We can't hold the spinlock while
+	 * doing that, though, so release the spinlock, get the information we
+	 * need, and retry.  If nobody else has initialized the scan in the
+	 * meantime, we'll fill in the value we fetched on the second time
+	 * through.
+	 */
+	if (parallel_scan->phs_startblock == InvalidBlockNumber)
+	{
+		if (!parallel_scan->phs_syncscan)
+			parallel_scan->phs_startblock = 0;
+		else if (sync_startpage != InvalidBlockNumber)
+			parallel_scan->phs_startblock = sync_startpage;
+		else
+		{
+			SpinLockRelease(&parallel_scan->phs_mutex);
+			sync_startpage = ss_get_location(scan->rs_rd, scan->rs_nblocks);
+			goto retry;
+		}
+	}
+	SpinLockRelease(&parallel_scan->phs_mutex);
+}
+
+/* ----------------
+ *		heap_parallelscan_nextpage - get the next page to scan
+ *
+ *		Get the next page to scan.  Even if there are no pages left to scan,
+ *		another backend could have grabbed a page to scan and not yet finished
+ *		looking at it, so it doesn't follow that the scan is done when the
+ *		first backend gets an InvalidBlockNumber return.
+ * ----------------
+ */
+static BlockNumber
+heap_parallelscan_nextpage(HeapScanDesc scan)
+{
+	BlockNumber page;
+	ParallelHeapScanDesc parallel_scan;
+	uint64		nallocated;
+
+	Assert(scan->rs_parallel);
+	parallel_scan = scan->rs_parallel;
+
+	/*
+	 * phs_nallocated tracks how many pages have been allocated to workers
+	 * already.  When phs_nallocated >= rs_nblocks, all blocks have been
+	 * allocated.
+	 *
+	 * Because we use an atomic fetch-and-add to fetch the current value, the
+	 * phs_nallocated counter will exceed rs_nblocks, because workers will
+	 * still increment the value, when they try to allocate the next block but
+	 * all blocks have been allocated already. The counter must be 64 bits
+	 * wide because of that, to avoid wrapping around when rs_nblocks is close
+	 * to 2^32.
+	 *
+	 * The actual page to return is calculated by adding the counter to the
+	 * starting block number, modulo nblocks.
+	 */
+	nallocated = pg_atomic_fetch_add_u64(&parallel_scan->phs_nallocated, 1);
+	if (nallocated >= scan->rs_nblocks)
+		page = InvalidBlockNumber;	/* all blocks have been allocated */
+	else
+		page = (nallocated + parallel_scan->phs_startblock) % scan->rs_nblocks;
+
+	/*
+	 * Report scan location.  Normally, we report the current page number.
+	 * When we reach the end of the scan, though, we report the starting page,
+	 * not the ending page, just so the starting positions for later scans
+	 * 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 (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);
+	}
+
+	return page;
+}
+
+
+/* ----------------
+ *		initscan - scan code common to heap_beginscan and heap_rescan
+ * ----------------
+ */
+static void
+initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
+{
+	bool		allow_strat;
+	bool		allow_sync;
+
+	/*
+	 * Determine the number of blocks we have to scan.
+	 *
+	 * It is sufficient to do this once at scan start, since any tuples added
+	 * while the scan is in progress will be invisible to my snapshot anyway.
+	 * (That is not true when using a non-MVCC snapshot.  However, we couldn't
+	 * guarantee to return tuples added after scan start anyway, since they
+	 * might go into pages we already scanned.  To guarantee consistent
+	 * results for a non-MVCC snapshot, the caller must hold some higher-level
+	 * lock that ensures the interesting tuple(s) won't change.)
+	 */
+	if (scan->rs_parallel != NULL)
+		scan->rs_nblocks = scan->rs_parallel->phs_nblocks;
+	else
+		scan->rs_nblocks = RelationGetNumberOfBlocks(scan->rs_rd);
+
+	/*
+	 * If the table is large relative to NBuffers, use a bulk-read access
+	 * strategy and enable synchronized scanning (see syncscan.c).  Although
+	 * the thresholds for these features could be different, we make them the
+	 * same so that there are only two behaviors to tune rather than four.
+	 * (However, some callers need to be able to disable one or both of these
+	 * behaviors, independently of the size of the table; also there is a GUC
+	 * variable that can disable synchronized scanning.)
+	 *
+	 * 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)
+	{
+		allow_strat = scan->rs_allow_strat;
+		allow_sync = scan->rs_allow_sync;
+	}
+	else
+		allow_strat = allow_sync = false;
+
+	if (allow_strat)
+	{
+		/* During a rescan, keep the previous strategy object. */
+		if (scan->rs_strategy == NULL)
+			scan->rs_strategy = GetAccessStrategy(BAS_BULKREAD);
+	}
+	else
+	{
+		if (scan->rs_strategy != NULL)
+			FreeAccessStrategy(scan->rs_strategy);
+		scan->rs_strategy = NULL;
+	}
+
+	if (scan->rs_parallel != NULL)
+	{
+		/* For parallel scan, believe whatever ParallelHeapScanDesc says. */
+		scan->rs_syncscan = scan->rs_parallel->phs_syncscan;
+	}
+	else if (keep_startblock)
+	{
+		/*
+		 * When rescanning, we want to keep the previous startblock setting,
+		 * so that rewinding a cursor doesn't generate surprising results.
+		 * Reset the active syncscan setting, though.
+		 */
+		scan->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);
+	}
+	else
+	{
+		scan->rs_syncscan = false;
+		scan->rs_startblock = 0;
+	}
+
+	scan->rs_numblocks = InvalidBlockNumber;
+	scan->rs_inited = false;
+	scan->rs_ctup.t_data = NULL;
+	ItemPointerSetInvalid(&scan->rs_ctup.t_self);
+	scan->rs_cbuf = InvalidBuffer;
+	scan->rs_cblock = InvalidBlockNumber;
+
+	/* page-at-a-time fields are always invalid when not rs_inited */
+
+	/*
+	 * copy the scan key, if appropriate
+	 */
+	if (key != NULL)
+		memcpy(scan->rs_key, key, scan->rs_nkeys * sizeof(ScanKeyData));
+
+	/*
+	 * Currently, we don't have a stats counter for bitmap heap scans (but the
+	 * underlying bitmap index scans will be counted) or sample scans (we only
+	 * update stats for tuple fetches there)
+	 */
+	if (!scan->rs_bitmapscan && !scan->rs_samplescan)
+		pgstat_count_heap_scan(scan->rs_rd);
+}
+
+
+/* ----------------
+ *		heapgettup - fetch next heap tuple
+ *
+ *		Initialize the scan if not already done; then advance to the next
+ *		tuple as indicated by "dir"; return the next tuple in scan->rs_ctup,
+ *		or set scan->rs_ctup.t_data = NULL if no more tuples.
+ *
+ * dir == NoMovementScanDirection means "re-fetch the tuple indicated
+ * by scan->rs_ctup".
+ *
+ * Note: the reason nkeys/key are passed separately, even though they are
+ * kept in the scan descriptor, is that the caller may not want us to check
+ * the scankeys.
+ *
+ * Note: when we fall off the end of the scan in either direction, we
+ * reset rs_inited.  This means that a further request with the same
+ * scan direction will restart the scan, which is a bit odd, but a
+ * request with the opposite scan direction will start a fresh scan
+ * in the proper direction.  The latter is required behavior for cursors,
+ * while the former case is generally undefined behavior in Postgres
+ * so we don't care too much.
+ * ----------------
+ */
+static void
+heapgettup(HeapScanDesc scan,
+		   ScanDirection dir,
+		   int nkeys,
+		   ScanKey key)
+{
+	HeapTuple	tuple = &(scan->rs_ctup);
+	Snapshot	snapshot = scan->rs_snapshot;
+	bool		backward = ScanDirectionIsBackward(dir);
+	BlockNumber page;
+	bool		finished;
+	Page		dp;
+	int			lines;
+	OffsetNumber lineoff;
+	int			linesleft;
+	ItemId		lpp;
+
+	/*
+	 * calculate next starting lineoff, given scan direction
+	 */
+	if (ScanDirectionIsForward(dir))
+	{
+		if (!scan->rs_inited)
+		{
+			/*
+			 * return null immediately if relation is empty
+			 */
+			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			{
+				Assert(!BufferIsValid(scan->rs_cbuf));
+				tuple->t_data = NULL;
+				return;
+			}
+			if (scan->rs_parallel != NULL)
+			{
+				heap_parallelscan_startblock_init(scan);
+
+				page = heap_parallelscan_nextpage(scan);
+
+				/* Other processes might have already finished the scan. */
+				if (page == InvalidBlockNumber)
+				{
+					Assert(!BufferIsValid(scan->rs_cbuf));
+					tuple->t_data = NULL;
+					return;
+				}
+			}
+			else
+				page = scan->rs_startblock; /* first page */
+			heapgetpage(scan, page);
+			lineoff = FirstOffsetNumber;	/* first offnum */
+			scan->rs_inited = true;
+		}
+		else
+		{
+			/* continue from previously returned page/tuple */
+			page = scan->rs_cblock; /* current page */
+			lineoff =			/* next offnum */
+				OffsetNumberNext(ItemPointerGetOffsetNumber(&(tuple->t_self)));
+		}
+
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		lines = PageGetMaxOffsetNumber(dp);
+		/* page and lineoff now reference the physically next tid */
+
+		linesleft = lines - lineoff + 1;
+	}
+	else if (backward)
+	{
+		/* backward parallel scan not supported */
+		Assert(scan->rs_parallel == NULL);
+
+		if (!scan->rs_inited)
+		{
+			/*
+			 * return null immediately if relation is empty
+			 */
+			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			{
+				Assert(!BufferIsValid(scan->rs_cbuf));
+				tuple->t_data = NULL;
+				return;
+			}
+
+			/*
+			 * Disable reporting to syncscan logic in a backwards scan; it's
+			 * not very likely anyone else is doing the same thing at the same
+			 * time, and much more likely that we'll just bollix things for
+			 * forward scanners.
+			 */
+			scan->rs_syncscan = false;
+			/* start from last page of the scan */
+			if (scan->rs_startblock > 0)
+				page = scan->rs_startblock - 1;
+			else
+				page = scan->rs_nblocks - 1;
+			heapgetpage(scan, page);
+		}
+		else
+		{
+			/* continue from previously returned page/tuple */
+			page = scan->rs_cblock; /* current page */
+		}
+
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		lines = PageGetMaxOffsetNumber(dp);
+
+		if (!scan->rs_inited)
+		{
+			lineoff = lines;	/* final offnum */
+			scan->rs_inited = true;
+		}
+		else
+		{
+			lineoff =			/* previous offnum */
+				OffsetNumberPrev(ItemPointerGetOffsetNumber(&(tuple->t_self)));
+		}
+		/* page and lineoff now reference the physically previous tid */
+
+		linesleft = lineoff;
+	}
+	else
+	{
+		/*
+		 * ``no movement'' scan direction: refetch prior tuple
+		 */
+		if (!scan->rs_inited)
+		{
+			Assert(!BufferIsValid(scan->rs_cbuf));
+			tuple->t_data = NULL;
+			return;
+		}
+
+		page = ItemPointerGetBlockNumber(&(tuple->t_self));
+		if (page != scan->rs_cblock)
+			heapgetpage(scan, page);
+
+		/* Since the tuple was previously fetched, needn't lock page here */
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
+		lpp = PageGetItemId(dp, lineoff);
+		Assert(ItemIdIsNormal(lpp));
+
+		tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+		tuple->t_len = ItemIdGetLength(lpp);
+
+		return;
+	}
+
+	/*
+	 * advance the scan until we find a qualifying tuple or run out of stuff
+	 * to scan
+	 */
+	lpp = PageGetItemId(dp, lineoff);
+	for (;;)
+	{
+		while (linesleft > 0)
+		{
+			if (ItemIdIsNormal(lpp))
+			{
+				bool		valid;
+
+				tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+				tuple->t_len = ItemIdGetLength(lpp);
+				ItemPointerSet(&(tuple->t_self), page, lineoff);
+
+				/*
+				 * if current tuple qualifies, return it.
+				 */
+				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine,
+													 tuple,
+													 snapshot,
+													 scan->rs_cbuf);
+
+				CheckForSerializableConflictOut(valid, scan->rs_rd, tuple,
+												scan->rs_cbuf, snapshot);
+
+				if (valid && key != NULL)
+					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
+								nkeys, key, valid);
+
+				if (valid)
+				{
+					LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+					return;
+				}
+			}
+
+			/*
+			 * otherwise move to the next item on the page
+			 */
+			--linesleft;
+			if (backward)
+			{
+				--lpp;			/* move back in this page's ItemId array */
+				--lineoff;
+			}
+			else
+			{
+				++lpp;			/* move forward in this page's ItemId array */
+				++lineoff;
+			}
+		}
+
+		/*
+		 * 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);
+
+		/*
+		 * 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);
+			if (page == 0)
+				page = scan->rs_nblocks;
+			page--;
+		}
+		else if (scan->rs_parallel != NULL)
+		{
+			page = heap_parallelscan_nextpage(scan);
+			finished = (page == InvalidBlockNumber);
+		}
+		else
+		{
+			page++;
+			if (page >= scan->rs_nblocks)
+				page = 0;
+			finished = (page == scan->rs_startblock) ||
+				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+
+			/*
+			 * Report our new scan position for synchronization purposes. We
+			 * don't do that when moving backwards, however. That would just
+			 * mess up any other forward-moving scanners.
+			 *
+			 * Note: we do this before checking for end of scan so that the
+			 * final state of the position hint is back at the start of the
+			 * rel.  That's not strictly necessary, but otherwise when you run
+			 * the same query multiple times the starting position would shift
+			 * 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);
+		}
+
+		/*
+		 * return NULL if we've exhausted all the pages
+		 */
+		if (finished)
+		{
+			if (BufferIsValid(scan->rs_cbuf))
+				ReleaseBuffer(scan->rs_cbuf);
+			scan->rs_cbuf = InvalidBuffer;
+			scan->rs_cblock = InvalidBlockNumber;
+			tuple->t_data = NULL;
+			scan->rs_inited = false;
+			return;
+		}
+
+		heapgetpage(scan, page);
+
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		lines = PageGetMaxOffsetNumber((Page) dp);
+		linesleft = lines;
+		if (backward)
+		{
+			lineoff = lines;
+			lpp = PageGetItemId(dp, lines);
+		}
+		else
+		{
+			lineoff = FirstOffsetNumber;
+			lpp = PageGetItemId(dp, FirstOffsetNumber);
+		}
+	}
+}
+
+/* ----------------
+ *		heapgettup_pagemode - fetch next heap tuple in page-at-a-time mode
+ *
+ *		Same API as heapgettup, but used in page-at-a-time mode
+ *
+ * The internal logic is much the same as heapgettup's too, but there are some
+ * differences: we do not take the buffer content lock (that only needs to
+ * happen inside heapgetpage), and we iterate through just the tuples listed
+ * in rs_vistuples[] rather than all tuples on the page.  Notice that
+ * lineindex is 0-based, where the corresponding loop variable lineoff in
+ * heapgettup is 1-based.
+ * ----------------
+ */
+static void
+heapgettup_pagemode(HeapScanDesc scan,
+					ScanDirection dir,
+					int nkeys,
+					ScanKey key)
+{
+	HeapTuple	tuple = &(scan->rs_ctup);
+	bool		backward = ScanDirectionIsBackward(dir);
+	BlockNumber page;
+	bool		finished;
+	Page		dp;
+	int			lines;
+	int			lineindex;
+	OffsetNumber lineoff;
+	int			linesleft;
+	ItemId		lpp;
+
+	/*
+	 * calculate next starting lineindex, given scan direction
+	 */
+	if (ScanDirectionIsForward(dir))
+	{
+		if (!scan->rs_inited)
+		{
+			/*
+			 * return null immediately if relation is empty
+			 */
+			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			{
+				Assert(!BufferIsValid(scan->rs_cbuf));
+				tuple->t_data = NULL;
+				return;
+			}
+			if (scan->rs_parallel != NULL)
+			{
+				heap_parallelscan_startblock_init(scan);
+
+				page = heap_parallelscan_nextpage(scan);
+
+				/* Other processes might have already finished the scan. */
+				if (page == InvalidBlockNumber)
+				{
+					Assert(!BufferIsValid(scan->rs_cbuf));
+					tuple->t_data = NULL;
+					return;
+				}
+			}
+			else
+				page = scan->rs_startblock; /* first page */
+			heapgetpage(scan, page);
+			lineindex = 0;
+			scan->rs_inited = true;
+		}
+		else
+		{
+			/* continue from previously returned page/tuple */
+			page = scan->rs_cblock; /* current page */
+			lineindex = scan->rs_cindex + 1;
+		}
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
+		lines = scan->rs_ntuples;
+		/* page and lineindex now reference the next visible tid */
+
+		linesleft = lines - lineindex;
+	}
+	else if (backward)
+	{
+		/* backward parallel scan not supported */
+		Assert(scan->rs_parallel == NULL);
+
+		if (!scan->rs_inited)
+		{
+			/*
+			 * return null immediately if relation is empty
+			 */
+			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			{
+				Assert(!BufferIsValid(scan->rs_cbuf));
+				tuple->t_data = NULL;
+				return;
+			}
+
+			/*
+			 * Disable reporting to syncscan logic in a backwards scan; it's
+			 * not very likely anyone else is doing the same thing at the same
+			 * time, and much more likely that we'll just bollix things for
+			 * forward scanners.
+			 */
+			scan->rs_syncscan = false;
+			/* start from last page of the scan */
+			if (scan->rs_startblock > 0)
+				page = scan->rs_startblock - 1;
+			else
+				page = scan->rs_nblocks - 1;
+			heapgetpage(scan, page);
+		}
+		else
+		{
+			/* continue from previously returned page/tuple */
+			page = scan->rs_cblock; /* current page */
+		}
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
+		lines = scan->rs_ntuples;
+
+		if (!scan->rs_inited)
+		{
+			lineindex = lines - 1;
+			scan->rs_inited = true;
+		}
+		else
+		{
+			lineindex = scan->rs_cindex - 1;
+		}
+		/* page and lineindex now reference the previous visible tid */
+
+		linesleft = lineindex + 1;
+	}
+	else
+	{
+		/*
+		 * ``no movement'' scan direction: refetch prior tuple
+		 */
+		if (!scan->rs_inited)
+		{
+			Assert(!BufferIsValid(scan->rs_cbuf));
+			tuple->t_data = NULL;
+			return;
+		}
+
+		page = ItemPointerGetBlockNumber(&(tuple->t_self));
+		if (page != scan->rs_cblock)
+			heapgetpage(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);
+		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
+		lpp = PageGetItemId(dp, lineoff);
+		Assert(ItemIdIsNormal(lpp));
+
+		tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+		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]);
+
+		return;
+	}
+
+	/*
+	 * advance the scan until we find a qualifying tuple or run out of stuff
+	 * to scan
+	 */
+	for (;;)
+	{
+		while (linesleft > 0)
+		{
+			lineoff = scan->rs_vistuples[lineindex];
+			lpp = PageGetItemId(dp, lineoff);
+			Assert(ItemIdIsNormal(lpp));
+
+			tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+			tuple->t_len = ItemIdGetLength(lpp);
+			ItemPointerSet(&(tuple->t_self), page, lineoff);
+
+			/*
+			 * if current tuple qualifies, return it.
+			 */
+			if (HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, tuple, scan->rs_snapshot, scan->rs_cbuf))
+			{
+				/*
+				 * if current tuple qualifies, return it.
+				 */
+				if (key != NULL)
+				{
+					bool		valid;
+
+					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
+								nkeys, key, valid);
+					if (valid)
+					{
+						scan->rs_cindex = lineindex;
+						return;
+					}
+				}
+				else
+				{
+					scan->rs_cindex = lineindex;
+					return;
+				}
+			}
+
+			/*
+			 * otherwise move to the next item on the page
+			 */
+			--linesleft;
+			if (backward)
+				--lineindex;
+			else
+				++lineindex;
+		}
+
+		/*
+		 * if we get here, it means we've exhausted the items on this page and
+		 * it's time to move to the next.
+		 */
+		if (backward)
+		{
+			finished = (page == scan->rs_startblock) ||
+				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+			if (page == 0)
+				page = scan->rs_nblocks;
+			page--;
+		}
+		else if (scan->rs_parallel != NULL)
+		{
+			page = heap_parallelscan_nextpage(scan);
+			finished = (page == InvalidBlockNumber);
+		}
+		else
+		{
+			page++;
+			if (page >= scan->rs_nblocks)
+				page = 0;
+			finished = (page == scan->rs_startblock) ||
+				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+
+			/*
+			 * Report our new scan position for synchronization purposes. We
+			 * don't do that when moving backwards, however. That would just
+			 * mess up any other forward-moving scanners.
+			 *
+			 * Note: we do this before checking for end of scan so that the
+			 * final state of the position hint is back at the start of the
+			 * rel.  That's not strictly necessary, but otherwise when you run
+			 * the same query multiple times the starting position would shift
+			 * 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);
+		}
+
+		/*
+		 * return NULL if we've exhausted all the pages
+		 */
+		if (finished)
+		{
+			if (BufferIsValid(scan->rs_cbuf))
+				ReleaseBuffer(scan->rs_cbuf);
+			scan->rs_cbuf = InvalidBuffer;
+			scan->rs_cblock = InvalidBlockNumber;
+			tuple->t_data = NULL;
+			scan->rs_inited = false;
+			return;
+		}
+
+		heapgetpage(scan, page);
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
+		lines = scan->rs_ntuples;
+		linesleft = lines;
+		if (backward)
+			lineindex = lines - 1;
+		else
+			lineindex = 0;
+	}
+}
+
+
+static HeapScanDesc
+heapam_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)
+{
+	HeapScanDesc scan;
+
+	/*
+	 * increment relation ref count while scanning relation
+	 *
+	 * This is just to make really sure the relcache entry won't go away while
+	 * the scan has a pointer to it.  Caller should be holding the rel open
+	 * anyway, so this is redundant in all normal scenarios...
+	 */
+	RelationIncrementReferenceCount(relation);
+
+	/*
+	 * allocate and initialize scan descriptor
+	 */
+	scan = (HeapScanDesc) palloc(sizeof(HeapScanDescData));
+
+	scan->rs_rd = relation;
+	scan->rs_snapshot = snapshot;
+	scan->rs_nkeys = nkeys;
+	scan->rs_bitmapscan = is_bitmapscan;
+	scan->rs_samplescan = is_samplescan;
+	scan->rs_strategy = NULL;	/* set in initscan */
+	scan->rs_allow_strat = allow_strat;
+	scan->rs_allow_sync = allow_sync;
+	scan->rs_temp_snap = temp_snap;
+	scan->rs_parallel = parallel_scan;
+
+	/*
+	 * we can use page-at-a-time mode if it's an MVCC-safe snapshot
+	 */
+	scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(snapshot);
+
+	/*
+	 * For a seqscan in a serializable transaction, acquire a predicate lock
+	 * on the entire relation. This is required not only to lock all the
+	 * matching tuples, but also to conflict with new insertions into the
+	 * table. In an indexscan, we take page locks on the index pages covering
+	 * the range specified in the scan qual, but in a heap scan there is
+	 * nothing more fine-grained to lock. A bitmap scan is a different story,
+	 * there we have already scanned the index and locked the index pages
+	 * covering the predicate. But in that case we still have to lock any
+	 * matching heap tuples.
+	 */
+	if (!is_bitmapscan)
+		PredicateLockRelation(relation, snapshot);
+
+	/* we only need to set this up once */
+	scan->rs_ctup.t_tableOid = RelationGetRelid(relation);
+
+	/*
+	 * we do this here instead of in initscan() because heap_rescan also calls
+	 * initscan() and we don't want to allocate memory again
+	 */
+	if (nkeys > 0)
+		scan->rs_key = (ScanKey) palloc(sizeof(ScanKeyData) * nkeys);
+	else
+		scan->rs_key = NULL;
+
+	initscan(scan, key, false);
+
+	return scan;
+}
+
+/* ----------------
+ *		heapam_rescan		- restart a relation scan
+ * ----------------
+ */
+static void
+heapam_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
+	 */
+	if (BufferIsValid(scan->rs_cbuf))
+		ReleaseBuffer(scan->rs_cbuf);
+
+	/*
+	 * reinitialize scan descriptor
+	 */
+	initscan(scan, key, true);
+
+	/*
+	 * 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);
+	}
+}
+
+/* ----------------
+ *		heapam_endscan	- end relation scan
+ *
+ *		See how to integrate with index scans.
+ *		Check handling if reldesc caching.
+ * ----------------
+ */
+static void
+heapam_endscan(HeapScanDesc scan)
+{
+	/* Note: no locking manipulations needed */
+
+	/*
+	 * unpin scan buffers
+	 */
+	if (BufferIsValid(scan->rs_cbuf))
+		ReleaseBuffer(scan->rs_cbuf);
+
+	/*
+	 * decrement relation reference count and free scan descriptor storage
+	 */
+	RelationDecrementReferenceCount(scan->rs_rd);
+
+	if (scan->rs_key)
+		pfree(scan->rs_key);
+
+	if (scan->rs_strategy != NULL)
+		FreeAccessStrategy(scan->rs_strategy);
+
+	if (scan->rs_temp_snap)
+		UnregisterSnapshot(scan->rs_snapshot);
+
+	pfree(scan);
+}
+
+/* ----------------
+ *		heapam_scan_update_snapshot
+ *
+ *		Update snapshot info in heap scan descriptor.
+ * ----------------
+ */
+static void
+heapam_scan_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
+{
+	Assert(IsMVCCSnapshot(snapshot));
+
+	RegisterSnapshot(snapshot);
+	scan->rs_snapshot = snapshot;
+	scan->rs_temp_snap = true;
+}
+
+/* ----------------
+ *		heapam_getnext	- retrieve next tuple in scan
+ *
+ *		Fix to work with index relations.
+ *		We don't return the buffer anymore, but you can get it from the
+ *		returned HeapTuple.
+ * ----------------
+ */
+
+#ifdef HEAPAMDEBUGALL
+#define HEAPAMDEBUG_1 \
+	elog(DEBUG2, "heapam_getnext([%s,nkeys=%d],dir=%d) called", \
+		 RelationGetRelationName(scan->rs_rd), scan->rs_nkeys, (int) direction)
+#define HEAPAMDEBUG_2 \
+	elog(DEBUG2, "heapam_getnext returning EOS")
+#define HEAPAMDEBUG_3 \
+	elog(DEBUG2, "heapam_getnext returning tuple")
+#else
+#define HEAPAMDEBUG_1
+#define HEAPAMDEBUG_2
+#define HEAPAMDEBUG_3
+#endif							/* !defined(HEAPDEBUGALL) */
+
+
+static StorageTuple
+heapam_getnext(HeapScanDesc sscan, ScanDirection direction)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	/* Note: no locking manipulations needed */
+
+	HEAPAMDEBUG_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)
+	{
+		HEAPAMDEBUG_2;			/* heap_getnext returning EOS */
+		return NULL;
+	}
+
+	/*
+	 * if we get here it means we have a new current scan tuple, so point to
+	 * the proper return buffer and return the tuple.
+	 */
+	HEAPAMDEBUG_3;				/* heap_getnext returning tuple */
+
+	pgstat_count_heap_getnext(scan->rs_rd);
+
+	return &(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
+
+static TupleTableSlot *
+heapam_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
@@ -1852,9 +2946,210 @@ heap_fetch(Relation relation,
 	return false;
 }
 
+/*
+ *	heapam_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.
+ */
+static bool
+heapam_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
+						 Snapshot snapshot, HeapTuple heapTuple,
+						 bool *all_dead, bool first_call)
+{
+	Page		dp = (Page) BufferGetPage(buffer);
+	TransactionId prev_xmax = InvalidTransactionId;
+	OffsetNumber offnum;
+	bool		at_chain_start;
+	bool		valid;
+	bool		skip;
+
+	/* If this is not the first call, previous call returned a (live!) tuple */
+	if (all_dead)
+		*all_dead = first_call;
+
+	Assert(TransactionIdIsValid(RecentGlobalXmin));
+
+	Assert(ItemPointerGetBlockNumber(tid) == BufferGetBlockNumber(buffer));
+	offnum = ItemPointerGetOffsetNumber(tid);
+	at_chain_start = first_call;
+	skip = !first_call;
+
+	heapTuple->t_self = *tid;
+
+	/* Scan through possible multiple members of HOT-chain */
+	for (;;)
+	{
+		ItemId		lp;
+
+		/* check for bogus TID */
+		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(dp))
+			break;
+
+		lp = PageGetItemId(dp, offnum);
+
+		/* check for unused, dead, or redirected items */
+		if (!ItemIdIsNormal(lp))
+		{
+			/* We should only see a redirect at start of chain */
+			if (ItemIdIsRedirected(lp) && at_chain_start)
+			{
+				/* Follow the redirect */
+				offnum = ItemIdGetRedirect(lp);
+				at_chain_start = false;
+				continue;
+			}
+			/* else must be end of chain */
+			break;
+		}
+
+		heapTuple->t_data = (HeapTupleHeader) PageGetItem(dp, lp);
+		heapTuple->t_len = ItemIdGetLength(lp);
+		heapTuple->t_tableOid = RelationGetRelid(relation);
+		ItemPointerSetOffsetNumber(&heapTuple->t_self, offnum);
+
+		/*
+		 * Shouldn't see a HEAP_ONLY tuple at chain start.
+		 */
+		if (at_chain_start && HeapTupleIsHeapOnly(heapTuple))
+			break;
+
+		/*
+		 * The xmin should match the previous xmax value, else chain is
+		 * broken.
+		 */
+		if (TransactionIdIsValid(prev_xmax) &&
+			!TransactionIdEquals(prev_xmax,
+								 HeapTupleHeaderGetXmin(heapTuple->t_data)))
+			break;
+
+		/*
+		 * When first_call is true (and thus, skip is initially false) we'll
+		 * return the first tuple we find.  But on later passes, heapTuple
+		 * will initially be pointing to the tuple we returned last time.
+		 * Returning it again would be incorrect (and would loop forever), so
+		 * we skip it and return the next match we find.
+		 */
+		if (!skip)
+		{
+			/*
+			 * For the benefit of logical decoding, have t_self point at the
+			 * element of the HOT chain we're currently investigating instead
+			 * of the root tuple of the HOT chain. This is important because
+			 * the *Satisfies routine for historical mvcc snapshots needs the
+			 * correct tid to decide about the visibility in some cases.
+			 */
+			ItemPointerSet(&(heapTuple->t_self), BufferGetBlockNumber(buffer), offnum);
+
+			/* If it's visible per the snapshot, we must return it */
+			valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, heapTuple, snapshot, buffer);
+			CheckForSerializableConflictOut(valid, relation, heapTuple,
+											buffer, snapshot);
+			/* reset to original, non-redirected, tid */
+			heapTuple->t_self = *tid;
+
+			if (valid)
+			{
+				ItemPointerSetOffsetNumber(tid, offnum);
+				PredicateLockTuple(relation, heapTuple, snapshot);
+				if (all_dead)
+					*all_dead = false;
+				return true;
+			}
+		}
+		skip = false;
+
+		/*
+		 * If we can't see it, maybe no one else can either.  At caller
+		 * request, check whether all chain members are dead to all
+		 * transactions.
+		 */
+		if (all_dead && *all_dead &&
+			!HeapTupleIsSurelyDead(heapTuple, RecentGlobalXmin))
+			*all_dead = false;
+
+		/*
+		 * Check to see if HOT chain continues past this tuple; if so fetch
+		 * the next offnum and loop around.
+		 */
+		if (HeapTupleIsHotUpdated(heapTuple))
+		{
+			Assert(ItemPointerGetBlockNumber(&heapTuple->t_data->t_ctid) ==
+				   ItemPointerGetBlockNumber(tid));
+			offnum = ItemPointerGetOffsetNumber(&heapTuple->t_data->t_ctid);
+			at_chain_start = false;
+			prev_xmax = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
+		}
+		else
+			break;				/* end of chain */
+	}
+
+	return false;
+}
+
+/*
+ * heapam_setscanlimits - restrict range of a heapscan
+ *
+ * startBlk is the page to start at
+ * numBlks is number of pages to scan (InvalidBlockNumber means "all")
+ */
+static void
+heapam_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	Assert(!scan->rs_inited);	/* else too late to change */
+	Assert(!scan->rs_syncscan); /* else rs_startblock is significant */
+
+	/* Check startBlk is valid (but allow case of zero blocks...) */
+	Assert(startBlk == 0 || startBlk < scan->rs_nblocks);
+
+	scan->rs_startblock = startBlk;
+	scan->rs_numblocks = numBlks;
+}
+
+
+/*
+ * heapam_freeze_tuple
+ *		Freeze tuple in place, without WAL logging.
+ *
+ * Useful for callers like CLUSTER that perform their own WAL logging.
+ */
+static bool
+heapam_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
+					TransactionId cutoff_multi)
+{
+	xl_heap_freeze_tuple frz;
+	bool		do_freeze;
+	bool		tuple_totally_frozen;
 
+	do_freeze = heap_prepare_freeze_tuple(tuple, cutoff_xid, cutoff_multi,
+										  &frz, &tuple_totally_frozen);
 
+	/*
+	 * Note that because this is not a WAL-logged operation, we don't need to
+	 * fill in the offset in the freeze record.
+	 */
 
+	if (do_freeze)
+		heap_execute_freeze_tuple(tuple, &frz);
+	return do_freeze;
+}
 
 /* ----------------------------------------------------------------
  *				storage AM support routines for heapam
@@ -3876,6 +5171,16 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 {
 	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
 
+	amroutine->scan_begin = heapam_beginscan;
+	amroutine->scansetlimits = heapam_setscanlimits;
+	amroutine->scan_getnext = heapam_getnext;
+	amroutine->scan_getnextslot = heapam_getnextslot;
+	amroutine->scan_end = heapam_endscan;
+	amroutine->scan_rescan = heapam_rescan;
+	amroutine->scan_update_snapshot = heapam_scan_update_snapshot;
+	amroutine->tuple_freeze = heapam_freeze_tuple;
+	amroutine->hot_search_buffer = heapam_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/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 84ac4bfade..4691e44ecc 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -409,8 +409,8 @@ rewrite_heap_tuple(RewriteState state,
 	 * While we have our hands on the tuple, we may as well freeze any
 	 * eligible xmin or xmax, so that future VACUUM effort can be saved.
 	 */
-	heap_freeze_tuple(new_tuple->t_data, state->rs_freeze_xid,
-					  state->rs_cutoff_multi);
+	storage_freeze_tuple(state->rs_new_rel, new_tuple->t_data, state->rs_freeze_xid,
+						 state->rs_cutoff_multi);
 
 	/*
 	 * Invalid ctid means that ctid should point to the tuple itself. We'll
diff --git a/src/backend/access/heap/storageam.c b/src/backend/access/heap/storageam.c
index 870490d00f..0b83ecf1bc 100644
--- a/src/backend/access/heap/storageam.c
+++ b/src/backend/access/heap/storageam.c
@@ -48,6 +48,174 @@
 #include "utils/tqual.h"
 
 
+/* ----------------
+ *		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);
+}
+
 /*
  *	storage_fetch		- retrieve tuple with given tid
  *
@@ -99,6 +267,73 @@ storage_fetch(Relation relation,
 												 userbuf, keep_buf, stats_relation);
 }
 
+/*
+ *	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;
+}
+
+/*
+ * heap_freeze_tuple
+ *		Freeze tuple in place, without WAL logging.
+ *
+ * Useful for callers like CLUSTER that perform their own WAL logging.
+ */
+bool
+storage_freeze_tuple(Relation rel, HeapTupleHeader tuple, TransactionId cutoff_xid,
+					 TransactionId cutoff_multi)
+{
+	return rel->rd_stamroutine->tuple_freeze(tuple, cutoff_xid, cutoff_multi);
+}
+
 
 /*
  *	storage_lock_tuple - lock a tuple in shared or exclusive mode
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 bef4255369..407e381a91 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 bf963fcdef..d3b67abf4a 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/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 ccde66a7dd..d2a8a06097 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"
@@ -797,14 +798,14 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 								ObjectIdGetDatum(namespaceId));
 
 					rel = heap_open(ProcedureRelationId, AccessShareLock);
-					scan = heap_beginscan_catalog(rel, 1, key);
+					scan = storage_beginscan_catalog(rel, 1, 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;
@@ -842,14 +843,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 17be9a4042..a201b00cae 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"
@@ -1904,10 +1905,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
 	{
@@ -2279,16 +2280,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 */
@@ -2301,7 +2302,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;
 
@@ -2613,7 +2614,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		}
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	/* we can now forget our snapshot, if set */
 	if (IsBootstrapProcessingMode() || indexInfo->ii_Concurrent)
@@ -2684,14 +2685,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();
 
@@ -2727,7 +2728,7 @@ IndexCheckExclusion(Relation heapRelation,
 								   estate, true);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 
 	ExecDropSingleTupleTableSlot(slot);
@@ -3004,17 +3005,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;
@@ -3171,7 +3172,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 5daa8a1c19..a5a5268d12 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -18,6 +18,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"
@@ -709,11 +710,11 @@ extern PartitionBoundInfo
 partition_bounds_copy(PartitionBoundInfo src,
 					  PartitionKey key)
 {
-	PartitionBoundInfo	dest;
-	int		i;
-	int		ndatums;
-	int		partnatts;
-	int		num_indexes;
+	PartitionBoundInfo dest;
+	int			i;
+	int			ndatums;
+	int			partnatts;
+	int			num_indexes;
 
 	dest = (PartitionBoundInfo) palloc(sizeof(PartitionBoundInfoData));
 
@@ -732,11 +733,11 @@ partition_bounds_copy(PartitionBoundInfo src,
 	if (src->kind != NULL)
 	{
 		dest->kind = (PartitionRangeDatumKind **) palloc(ndatums *
-												sizeof(PartitionRangeDatumKind *));
+														 sizeof(PartitionRangeDatumKind *));
 		for (i = 0; i < ndatums; i++)
 		{
 			dest->kind[i] = (PartitionRangeDatumKind *) palloc(partnatts *
-												sizeof(PartitionRangeDatumKind));
+															   sizeof(PartitionRangeDatumKind));
 
 			memcpy(dest->kind[i], src->kind[i],
 				   sizeof(PartitionRangeDatumKind) * key->partnatts);
@@ -747,7 +748,8 @@ partition_bounds_copy(PartitionBoundInfo src,
 
 	for (i = 0; i < ndatums; i++)
 	{
-		int		j;
+		int			j;
+
 		dest->datums[i] = (Datum *) palloc(sizeof(Datum) * partnatts);
 
 		for (j = 0; j < partnatts; j++)
@@ -1074,7 +1076,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);
 
 		/*
@@ -1083,7 +1085,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;
@@ -1099,7 +1101,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 69af210dba..79f2085fb3 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2036,10 +2036,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();
 
@@ -2051,7 +2051,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 e138539035..39850b1b37 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 3f615b6260..5209083a72 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"
@@ -1947,8 +1948,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);
@@ -1988,7 +1989,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 e1ca60b35b..3f928a8051 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4540,7 +4540,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
@@ -4548,7 +4548,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)
 			{
@@ -4662,7 +4662,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 
 		MemoryContextSwitchTo(oldCxt);
-		heap_endscan(scan);
+		storage_endscan(scan);
 		UnregisterSnapshot(snapshot);
 
 		ExecDropSingleTupleTableSlot(oldslot);
@@ -5065,9 +5065,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,
@@ -5079,7 +5079,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;
@@ -8246,7 +8246,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
@@ -8254,7 +8254,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);
 
@@ -8269,7 +8269,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	}
 
 	MemoryContextSwitchTo(oldcxt);
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 	ExecDropSingleTupleTableSlot(slot);
 	FreeExecutorState(estate);
@@ -8324,9 +8324,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;
@@ -8355,7 +8355,7 @@ validateForeignKeyConstraint(char *conname,
 		RI_FKey_check_ins(&fcinfo);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 }
 
@@ -10805,8 +10805,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;
@@ -10865,7 +10865,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 8559c3b6b3..cdfa8ffb3f 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/trigger.c b/src/backend/commands/trigger.c
index 680a65cad5..a0f3db5bdb 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -15,8 +15,9 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
-#include "access/sysattr.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
+#include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 7df942b18b..b95e3fc5ab 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 cbd6e9b161..bafc15eac6 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"
@@ -68,7 +69,7 @@ static BufferAccessStrategy vac_strategy;
 
 
 /* non-export function prototypes */
-static List *expand_vacuum_rel(VacuumRelation *vrel);
+static List *expand_vacuum_rel(VacuumRelation * vrel);
 static List *get_all_vacuum_rels(void);
 static void vac_truncate_clog(TransactionId frozenXID,
 				  MultiXactId minMulti,
@@ -423,7 +424,7 @@ vacuum(int options, List *relations, VacuumParams *params,
  * are made in vac_context.
  */
 static List *
-expand_vacuum_rel(VacuumRelation *vrel)
+expand_vacuum_rel(VacuumRelation * vrel)
 {
 	List	   *vacrels = NIL;
 	MemoryContext oldcontext;
@@ -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 24b5ff7298..ed2c0ab80c 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 an index 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 d618b3d903..742f73176c 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -683,7 +683,7 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 Datum
 ExecFetchSlotTupleDatum(TupleTableSlot *slot)
 {
-	HeapTuple	tup;
+	StorageTuple tup;
 	TupleDesc	tupdesc;
 
 	/* Fetch slot's contents in regular-physical-tuple form */
@@ -767,7 +767,7 @@ ExecHeapifySlot(TupleTableSlot *slot)
 TupleTableSlot *
 ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
 {
-	HeapTuple	newTuple;
+	StorageTuple newTuple;
 	MemoryContext oldContext;
 
 	/*
@@ -1087,7 +1087,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;
@@ -1095,7 +1095,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 98eb777421..8ce51e5bd5 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)
 {
@@ -597,7 +597,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 2b118359b5..d8c4119972 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -3119,7 +3119,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,
@@ -3244,7 +3244,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 c34898f241..9e24b1abd8 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 "executor/execdebug.h"
 #include "executor/nodeBitmapHeapscan.h"
@@ -400,8 +401,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);
 		}
 	}
@@ -685,7 +686,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);
 
 	if (node->tbmiterator)
 		tbm_end_iterate(node->tbmiterator);
@@ -764,7 +765,7 @@ ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 	/*
 	 * close heap scan
 	 */
-	heap_endscan(scanDesc);
+	storage_endscan(scanDesc);
 
 	/*
 	 * close the heap relation.
@@ -865,10 +866,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.
@@ -1024,5 +1025,5 @@ ExecBitmapHeapInitializeWorker(BitmapHeapScanState *node, shm_toc *toc)
 	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 02f6c816aa..d3de6c5824 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 639f4f5af8..edb8046180 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -45,7 +45,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);
 
 
@@ -251,7 +251,7 @@ gather_getnext(GatherState *gatherstate)
 	TupleTableSlot *outerTupleSlot;
 	TupleTableSlot *fslot = gatherstate->funnel_slot;
 	MemoryContext tupleContext = gatherstate->ps.ps_ExprContext->ecxt_per_tuple_memory;
-	HeapTuple	tup;
+	StorageTuple tup;
 
 	while (gatherstate->nreaders > 0 || gatherstate->need_to_scan_locally)
 	{
@@ -294,7 +294,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;
@@ -302,7 +302,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 5625b12521..3381813455 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -44,7 +44,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 */
@@ -53,8 +53,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);
@@ -399,7 +399,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);
@@ -617,7 +617,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
@@ -689,12 +689,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;
 	MemoryContext oldContext;
 	MemoryContext tupleContext;
 
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index 9368ca04f8..07346435cd 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 262008240d..2726933d9d 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..64f3dd8be0 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 76bec780a8..0521df1fa9 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);
 }
 
 /* ----------------------------------------------------------------
@@ -354,5 +335,5 @@ ExecSeqScanInitializeWorker(SeqScanState *node, shm_toc *toc)
 
 	pscan = shm_toc_lookup(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 40292b86c1..557cb2175c 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 9a47276274..3d61d96775 100644
--- a/src/backend/executor/tqueue.c
+++ b/src/backend/executor/tqueue.c
@@ -166,7 +166,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 3a0b49c7c4..36808c4067 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/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 2b2b993e2c..88086f3d48 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -4268,8 +4268,8 @@ BackendInitialize(Port *port)
 	 *
 	 * postgres: walsender <user> <host> <activity>
 	 *
-	 * To achieve that, we pass "walsender" as username and username
-	 * as dbname to init_ps_display(). XXX: should add a new variant of
+	 * To achieve that, we pass "walsender" as username and username as dbname
+	 * to init_ps_display(). XXX: should add a new variant of
 	 * init_ps_display() to avoid abusing the parameters like this.
 	 */
 	if (am_walsender)
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 071b3a9ec9..4924daca76 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(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 b1cb80c251..db87b686c5 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -106,40 +106,16 @@ 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);
-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_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 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 Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 			int options, BulkInsertState bistate);
-extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
-					   Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
-					   bool *all_dead, bool first_call);
-extern bool heap_hot_search(ItemPointer tid, Relation relation,
-				Snapshot snapshot, bool *all_dead);
+
 extern void setLastTid(const ItemPointer tid);
 
 extern bool HeapTupleUpdateXmaxMatchesXmin(TransactionId xmax,
@@ -150,8 +126,6 @@ extern void FreeBulkInsertState(BulkInsertState);
 extern void ReleaseBulkInsertStatePin(BulkInsertState bistate);
 
 extern void heap_inplace_update(Relation relation, HeapTuple tuple);
-extern bool heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
-				  TransactionId cutoff_multi);
 extern bool heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
 						MultiXactId cutoff_multi, Buffer buf);
 extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
@@ -161,8 +135,6 @@ extern void simple_heap_delete(Relation relation, ItemPointer tid);
 extern void simple_heap_update(Relation relation, ItemPointer otid,
 				   HeapTuple tup);
 
-extern void heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
-
 /* 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/heapam_common.h b/src/include/access/heapam_common.h
index 14aad60546..597f2bd2f4 100644
--- a/src/include/access/heapam_common.h
+++ b/src/include/access/heapam_common.h
@@ -107,6 +107,9 @@ static const int MultiXactStatusLock[MaxMultiXactStatus + 1] =
 /* Get the LOCKMODE for a given MultiXactStatus */
 #define LOCKMODE_from_mxstatus(status) \
 			(tupleLockExtraInfo[TUPLOCK_from_mxstatus((status))].hwlock)
+
+extern bool synchronize_seqscans;
+
 extern HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
 					TransactionId xid, CommandId cid, int options);
 
@@ -136,6 +139,11 @@ extern void MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 in
 extern MultiXactStatus get_mxact_status_for_lock(LockTupleMode mode, bool is_update);
 
 extern void heap_inplace_update(Relation relation, HeapTuple tuple);
+
+extern bool heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
+						MultiXactId cutoff_multi, Buffer buf);
+extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
+
 extern bool heap_acquire_tuplock(Relation relation, ItemPointer tid,
 					 LockTupleMode mode, LockWaitPolicy wait_policy,
 					 bool *have_tuple_lock);
diff --git a/src/include/access/storageam.h b/src/include/access/storageam.h
index 447935ba10..ec5dc89a26 100644
--- a/src/include/access/storageam.h
+++ b/src/include/access/storageam.h
@@ -20,6 +20,7 @@
 
 /* A physical tuple coming from a storage AM scan */
 typedef void *StorageTuple;
+typedef void *StorageScanDesc;
 
 typedef union tuple_data
 {
@@ -42,6 +43,34 @@ typedef enum tuple_data_flags
 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,
@@ -50,6 +79,16 @@ 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 bool storage_freeze_tuple(Relation rel, HeapTupleHeader tuple, TransactionId cutoff_xid,
+					 TransactionId cutoff_multi);
+
 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/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.14.2.windows.1

0008-Remove-HeapScanDesc-usage-outside-heap.patchapplication/octet-stream; name=0008-Remove-HeapScanDesc-usage-outside-heap.patchDownload
From 6503363a3bda844c26abac48c145722dfc493c90 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Tue, 31 Oct 2017 17:49:50 +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           |  37 +--
 src/backend/access/heap/heapam_storage.c   | 424 +++++++++++++++++------------
 src/backend/access/heap/storageam.c        |  51 +++-
 src/backend/access/index/genam.c           |   4 +-
 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  |  67 ++---
 src/backend/executor/nodeSamplescan.c      |  50 ++--
 src/backend/executor/nodeSeqscan.c         |   5 +-
 src/backend/optimizer/util/plancat.c       |  10 +-
 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                |   4 +-
 src/include/access/relscan.h               |  47 ++--
 src/include/access/storageam.h             |  44 +--
 src/include/access/storageamapi.h          |  42 +--
 src/include/nodes/execnodes.h              |   4 +-
 39 files changed, 512 insertions(+), 399 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 c785268921..c8e9477da2 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -107,8 +107,9 @@ static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_mo
  * 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;
@@ -118,13 +119,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;
 	}
 
 	/*
@@ -135,20 +136,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
@@ -158,7 +159,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;
 
@@ -193,7 +194,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);
@@ -201,20 +202,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;
 }
 
 #if defined(DISABLE_COMPLEX_MACRO)
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 6e1817b1fd..bf9f3f39d9 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -1745,7 +1745,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;
 		}
 	}
@@ -1787,10 +1787,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.
@@ -1799,12 +1799,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;
@@ -1833,9 +1833,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
@@ -1849,8 +1849,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;
@@ -1861,20 +1861,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)
 	{
@@ -1883,25 +1883,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 */
 
@@ -1909,7 +1909,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
@@ -1917,7 +1917,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);
 }
 
 
@@ -1951,7 +1951,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;
@@ -1966,14 +1966,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;
 			}
@@ -1986,29 +1986,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 */
 
@@ -2019,14 +2019,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;
 			}
@@ -2037,30 +2037,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
 		{
@@ -2076,20 +2076,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));
@@ -2120,21 +2120,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;
 				}
 			}
@@ -2159,17 +2159,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)
@@ -2180,10 +2180,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
@@ -2197,8 +2197,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);
 		}
 
 		/*
@@ -2206,21 +2206,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)
@@ -2271,14 +2271,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;
 			}
@@ -2291,27 +2291,27 @@ 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;
 		}
 
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
+		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;
@@ -2321,14 +2321,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;
 			}
@@ -2339,32 +2339,32 @@ 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 */
 		}
 
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
+		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 */
 
@@ -2375,20 +2375,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));
@@ -2397,8 +2397,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;
 	}
@@ -2411,7 +2411,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));
 
@@ -2422,7 +2422,9 @@ 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.
@@ -2431,17 +2433,17 @@ 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;
+						scan->rs_pagescan.rs_cindex = lineindex;
 						return;
 					}
 				}
 				else
 				{
-					scan->rs_cindex = lineindex;
+					scan->rs_pagescan.rs_cindex = lineindex;
 					return;
 				}
 			}
@@ -2462,10 +2464,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)
@@ -2476,10 +2478,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
@@ -2493,8 +2495,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);
 		}
 
 		/*
@@ -2502,20 +2504,20 @@ 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);
 
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(scan->rs_snapshot, scan->rs_rd, dp);
-		lines = scan->rs_ntuples;
+		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;
@@ -2525,7 +2527,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 }
 
 
-static HeapScanDesc
+static StorageScanDesc
 heapam_beginscan(Relation relation, Snapshot snapshot,
 				 int nkeys, ScanKey key,
 				 ParallelHeapScanDesc parallel_scan,
@@ -2552,12 +2554,12 @@ heapam_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;
@@ -2566,7 +2568,7 @@ heapam_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
@@ -2590,13 +2592,29 @@ heapam_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;
+}
+
+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;
 }
 
 /* ----------------
@@ -2604,21 +2622,23 @@ heapam_beginscan(Relation relation, Snapshot snapshot,
  * ----------------
  */
 static void
-heapam_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
+heapam_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
@@ -2649,29 +2669,31 @@ heapam_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
  * ----------------
  */
 static void
-heapam_endscan(HeapScanDesc scan)
+heapam_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);
 }
@@ -2683,12 +2705,14 @@ heapam_endscan(HeapScanDesc scan)
  * ----------------
  */
 static void
-heapam_scan_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
+heapam_scan_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;
 }
 
@@ -2717,7 +2741,7 @@ heapam_scan_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
 
 
 static StorageTuple
-heapam_getnext(HeapScanDesc sscan, ScanDirection direction)
+heapam_getnext(StorageScanDesc sscan, ScanDirection direction)
 {
 	HeapScanDesc scan = (HeapScanDesc) sscan;
 
@@ -2725,11 +2749,11 @@ heapam_getnext(HeapScanDesc sscan, ScanDirection direction)
 
 	HEAPAMDEBUG_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)
 	{
@@ -2743,7 +2767,7 @@ heapam_getnext(HeapScanDesc sscan, ScanDirection direction)
 	 */
 	HEAPAMDEBUG_3;				/* heap_getnext returning tuple */
 
-	pgstat_count_heap_getnext(scan->rs_rd);
+	pgstat_count_heap_getnext(scan->rs_scan.rs_rd);
 
 	return &(scan->rs_ctup);
 }
@@ -2763,7 +2787,7 @@ heapam_getnext(HeapScanDesc sscan, ScanDirection direction)
 #endif
 
 static TupleTableSlot *
-heapam_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
+heapam_getnextslot(StorageScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
 {
 	HeapScanDesc scan = (HeapScanDesc) sscan;
 
@@ -2771,11 +2795,11 @@ heapam_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *
 
 	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)
 	{
@@ -2790,11 +2814,34 @@ heapam_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *
 	 */
 	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);
 }
 
+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);
+}
+
+
+
 /*
  *	heap_fetch		- retrieve tuple with given tid
  *
@@ -3109,18 +3156,19 @@ heapam_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
  * numBlks is number of pages to scan (InvalidBlockNumber means "all")
  */
 static void
-heapam_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
+heapam_setscanlimits(StorageScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
 {
 	HeapScanDesc scan = (HeapScanDesc) sscan;
 
-	Assert(!scan->rs_inited);	/* else too late to change */
-	Assert(!scan->rs_syncscan); /* else rs_startblock is significant */
+	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;
 }
 
 
@@ -5175,12 +5223,26 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 	amroutine->scansetlimits = heapam_setscanlimits;
 	amroutine->scan_getnext = heapam_getnext;
 	amroutine->scan_getnextslot = heapam_getnextslot;
+	amroutine->scan_fetch_tuple_from_offset = heapam_fetch_tuple_from_offset;
 	amroutine->scan_end = heapam_endscan;
 	amroutine->scan_rescan = heapam_rescan;
 	amroutine->scan_update_snapshot = heapam_scan_update_snapshot;
 	amroutine->tuple_freeze = heapam_freeze_tuple;
 	amroutine->hot_search_buffer = heapam_hot_search_buffer;
 
+	/*
+	 * 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;
 	amroutine->tuple_delete = heapam_heap_delete;
diff --git a/src/backend/access/heap/storageam.c b/src/backend/access/heap/storageam.c
index 0b83ecf1bc..9d6e9e9ea1 100644
--- a/src/backend/access/heap/storageam.c
+++ b/src/backend/access/heap/storageam.c
@@ -54,7 +54,7 @@
  *		Caller must hold a suitable lock on the correct relation.
  * ----------------
  */
-HeapScanDesc
+StorageScanDesc
 storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan)
 {
 	Snapshot	snapshot;
@@ -67,6 +67,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
  *
@@ -74,7 +93,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);
 }
@@ -104,7 +123,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)
 {
@@ -112,7 +131,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);
@@ -122,7 +141,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)
@@ -132,7 +151,7 @@ storage_beginscan_strat(Relation relation, Snapshot snapshot,
 												false, false, false);
 }
 
-HeapScanDesc
+StorageScanDesc
 storage_beginscan_bm(Relation relation, Snapshot snapshot,
 					 int nkeys, ScanKey key)
 {
@@ -140,7 +159,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)
@@ -155,7 +174,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);
@@ -171,7 +190,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,
@@ -186,7 +205,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);
 }
@@ -199,23 +218,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);
+}
+
 /*
  *	storage_fetch		- retrieve tuple with given tid
  *
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/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 d2a8a06097..281e3a8e2a 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -789,7 +789,7 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 				{
 					ScanKeyData key[1];
 					Relation	rel;
-					HeapScanDesc scan;
+					StorageScanDesc scan;
 					HeapTuple	tuple;
 
 					ScanKeyInit(&key[0],
@@ -830,7 +830,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 a201b00cae..49b75478bc 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1897,7 +1897,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],
@@ -2210,7 +2210,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];
@@ -2649,7 +2649,7 @@ IndexCheckExclusion(Relation heapRelation,
 					Relation indexRelation,
 					IndexInfo *indexInfo)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
@@ -2963,7 +2963,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 a5a5268d12..259129d456 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1017,7 +1017,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 79f2085fb3..3d419ae1a9 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2030,7 +2030,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 39850b1b37..09135774c8 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 5209083a72..b8440f8e73 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1874,7 +1874,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 3f928a8051..bdf79fe5b9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4481,7 +4481,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;
@@ -5054,7 +5054,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;
 
@@ -8205,7 +8205,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	Expr	   *origexpr;
 	ExprState  *exprstate;
 	TupleDesc	tupdesc;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	ExprContext *econtext;
 	MemoryContext oldcxt;
@@ -8288,7 +8288,7 @@ validateForeignKeyConstraint(char *conname,
 							 Oid pkindOid,
 							 Oid constraintOid)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	Trigger		trig;
 	Snapshot	snapshot;
@@ -10740,7 +10740,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 cdfa8ffb3f..be3c0db9e2 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 b95e3fc5ab..273bca0c2b 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 bafc15eac6..16622f3bee 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 9e24b1abd8..a1f1303e76 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -54,14 +54,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);
 
@@ -76,7 +76,8 @@ static TupleTableSlot *
 BitmapHeapNext(BitmapHeapScanState *node)
 {
 	ExprContext *econtext;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
+	HeapPageScanDesc pagescan;
 	TIDBitmap  *tbm;
 	TBMIterator *tbmiterator = NULL;
 	TBMSharedIterator *shared_tbmiterator = NULL;
@@ -92,6 +93,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;
@@ -191,8 +193,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 
 	for (;;)
 	{
-		Page		dp;
-		ItemId		lp;
+		StorageTuple tuple;
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -219,7 +220,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;
@@ -228,7 +229,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			/*
 			 * Fetch the current heap page and identify candidate tuples.
 			 */
-			bitgetpage(scan, tbmres);
+			bitgetpage(node, tbmres);
 
 			if (tbmres->ntuples >= 0)
 				node->exact_pages++;
@@ -238,7 +239,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);
@@ -248,7 +249,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			/*
 			 * Continuing in previously obtained page; advance rs_cindex
 			 */
-			scan->rs_cindex++;
+			pagescan->rs_cindex++;
 
 #ifdef USE_PREFETCH
 
@@ -275,7 +276,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;
@@ -293,23 +294,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);
@@ -350,8 +342,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;
@@ -360,7 +354,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,
@@ -403,7 +397,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
@@ -419,23 +413,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);
 		}
 	}
@@ -443,7 +434,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 
 	Assert(ntup <= MaxHeapTuplesPerPage);
-	scan->rs_ntuples = ntup;
+	pagescan->rs_ntuples = ntup;
 }
 
 /*
@@ -569,7 +560,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;
@@ -724,7 +715,7 @@ void
 ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 {
 	Relation	relation;
-	HeapScanDesc scanDesc;
+	StorageScanDesc scanDesc;
 
 	/*
 	 * extract information from the node
@@ -871,6 +862,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 64f3dd8be0..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 0521df1fa9..ac3754d91d 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 9d35a41e22..aa7808001d 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);
 
@@ -1825,7 +1827,7 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel,
 							Relation relation)
 {
 	PartitionDesc partdesc;
-	PartitionKey  partkey;
+	PartitionKey partkey;
 
 	Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 
@@ -1890,8 +1892,8 @@ find_partition_scheme(PlannerInfo *root, Relation relation)
 
 	/*
 	 * Did not find matching partition scheme. Create one copying relevant
-	 * information from the relcache. We need to copy the contents of the array
-	 * since the relcache entry may not survive after we have closed the
+	 * information from the relcache. We need to copy the contents of the
+	 * array since the relcache entry may not survive after we have closed the
 	 * relation.
 	 */
 	part_scheme = (PartitionScheme) palloc0(sizeof(PartitionSchemeData));
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 36808c4067..db4b091471 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 4924daca76..7122987ad7 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -420,7 +420,7 @@ DefineQueryRewrite(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 db87b686c5..04aa102ab1 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -97,6 +97,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;
 
@@ -106,7 +108,7 @@ typedef struct ParallelHeapScanDescData *ParallelHeapScanDesc;
  */
 #define HeapScanIsValid(scan) PointerIsValid(scan)
 
-extern void heapgetpage(HeapScanDesc scan, BlockNumber page);
+extern void heapgetpage(StorageScanDesc sscan, BlockNumber page);
 
 extern Size heap_parallelscan_estimate(Snapshot snapshot);
 extern void heap_parallelscan_initialize(ParallelHeapScanDesc target,
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/storageam.h b/src/include/access/storageam.h
index ec5dc89a26..4c1ff4cea5 100644
--- a/src/include/access/storageam.h
+++ b/src/include/access/storageam.h
@@ -20,7 +20,7 @@
 
 /* A physical tuple coming from a storage AM scan */
 typedef void *StorageTuple;
-typedef void *StorageScanDesc;
+typedef struct StorageScanDescData *StorageScanDesc;
 
 typedef union tuple_data
 {
@@ -43,29 +43,31 @@ typedef enum tuple_data_flags
 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,
+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 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_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 03a6aa425a..bfe2809d6a 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -20,32 +20,37 @@
 #include "utils/relcache.h"
 #include "utils/snapshot.h"
 
-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 6a3a88bdd4..5bda8a26fe 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1102,7 +1102,7 @@ typedef struct ScanState
 {
 	PlanState	ps;				/* its first field is NodeTag */
 	Relation	ss_currentRelation;
-	HeapScanDesc ss_currentScanDesc;
+	StorageScanDesc ss_currentScanDesc;
 	TupleTableSlot *ss_ScanTupleSlot;
 } ScanState;
 
@@ -1123,6 +1123,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 */
@@ -1347,6 +1348,7 @@ typedef struct ParallelBitmapHeapState
 typedef struct BitmapHeapScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
+	HeapPageScanDesc pagescan;
 	ExprState  *bitmapqualorig;
 	TIDBitmap  *tbm;
 	TBMIterator *tbmiterator;
-- 
2.14.2.windows.1

0001-Change-Create-Access-method-to-include-storage-handl.patchapplication/octet-stream; name=0001-Change-Create-Access-method-to-include-storage-handl.patchDownload
From af5c2b7ee1136ac308b3caa3f1121ee0292d6e5e Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
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 4c83a63f7d..278a6e8e1c 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
@@ -5174,16 +5176,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 93c031aad7..4a8ab151d7 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 ffdb452b02..ea352fa247 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.14.2.windows.1

0002-Storage-AM-API-hooks-and-related-functions.patchapplication/octet-stream; name=0002-Storage-AM-API-hooks-and-related-functions.patchDownload
From 394ca0ba07e969fa83669afa5e2b11bba7992763 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Wed, 30 Aug 2017 12:41:15 +1000
Subject: [PATCH 2/8] Storage AM API hooks and related functions

---
 src/backend/access/heap/Makefile         |   3 +-
 src/backend/access/heap/heapam_storage.c |  58 ++++++++
 src/backend/access/heap/storageamapi.c   | 103 ++++++++++++++
 src/include/access/htup.h                |  21 +++
 src/include/access/storageamapi.h        | 227 +++++++++++++++++++++++++++++++
 src/include/catalog/pg_am.h              |   3 +
 src/include/catalog/pg_proc.h            |   5 +
 src/include/nodes/nodes.h                |   1 +
 src/include/utils/tqual.h                |   9 --
 9 files changed, 420 insertions(+), 10 deletions(-)
 create mode 100644 src/backend/access/heap/heapam_storage.c
 create mode 100644 src/backend/access/heap/storageamapi.c
 create mode 100644 src/include/access/storageamapi.h

diff --git a/src/backend/access/heap/Makefile b/src/backend/access/heap/Makefile
index b83d496bcd..02a3909e7c 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 storageamapi.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..88827e7957
--- /dev/null
+++ b/src/backend/access/heap/heapam_storage.c
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/heapam.h"
+#include "access/heapam_xlog.h"
+#include "access/hio.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/relscan.h"
+#include "access/storageamapi.h"
+#include "access/subtrans.h"
+#include "access/tuptoaster.h"
+#include "access/valid.h"
+#include "access/visibilitymap.h"
+#include "access/xloginsert.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/bufmgr.h"
+#include "storage/lmgr.h"
+#include "storage/predicate.h"
+#include "storage/procarray.h"
+#include "storage/smgr.h"
+#include "storage/spin.h"
+#include "utils/builtins.h"
+#include "utils/inval.h"
+#include "utils/rel.h"
+#include "utils/tqual.h"
+
+
+Datum
+heapam_storage_handler(PG_FUNCTION_ARGS)
+{
+	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
+
+
+	PG_RETURN_POINTER(amroutine);
+}
diff --git a/src/backend/access/heap/storageamapi.c b/src/backend/access/heap/storageamapi.c
new file mode 100644
index 0000000000..bcbe14588b
--- /dev/null
+++ b/src/backend/access/heap/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/htup.h b/src/include/access/htup.h
index 61b3e68639..6459435c78 100644
--- a/src/include/access/htup.h
+++ b/src/include/access/htup.h
@@ -26,6 +26,27 @@ typedef struct MinimalTupleData MinimalTupleData;
 
 typedef MinimalTupleData *MinimalTuple;
 
+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 */
+
+	END_OF_VISIBILITY
+} tuple_visibility_type;
+
+/* 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;
 
 /*
  * HeapTupleData is an in-memory data structure that points to a tuple.
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
new file mode 100644
index 0000000000..2b08c6f4ff
--- /dev/null
+++ b/src/include/access/storageamapi.h
@@ -0,0 +1,227 @@
+/*---------------------------------------------------------------------
+ *
+ * storageamapi.h
+ *		API for Postgres storage access methods
+ *
+ * Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * src/include/access/storageamapi.h
+ *---------------------------------------------------------------------
+ */
+#ifndef STORAGEAMAPI_H
+#define STORAGEAMAPI_H
+
+#include "access/htup.h"
+#include "access/heapam.h"
+#include "access/sdir.h"
+#include "access/skey.h"
+#include "executor/tuptable.h"
+#include "utils/relcache.h"
+#include "utils/snapshot.h"
+
+/* A physical tuple coming from a storage AM scan */
+typedef void *StorageTuple;
+
+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;
+
+
+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);
+
+typedef Oid (*TupleInsert_function) (Relation relation,
+									 TupleTableSlot *tupslot,
+									 CommandId cid,
+									 int options,
+									 BulkInsertState bistate);
+
+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,
+											 CommandId cid,
+											 Snapshot crosscheck,
+											 bool wait,
+											 HeapUpdateFailureData *hufd,
+											 LockTupleMode *lockmode);
+
+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 bool (*TupleFreeze_function) (HeapTupleHeader tuple, TransactionId cutoff_xid,
+									  TransactionId cutoff_multi);
+
+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 (*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 (*SpeculativeFinish_function) (Relation rel,
+											TupleTableSlot *slot);
+typedef void (*SpeculativeAbort_function) (Relation rel,
+										   TupleTableSlot *slot);
+
+typedef void (*RelationSync_function) (Relation relation);
+
+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);
+
+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);
+
+/*
+ * 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;
+
+	/* 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 */
+
+	/* 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 */
+	TupleFreeze_function tuple_freeze;	/* heap_freeze_tuple */
+	TupleGetLatestTid_function tuple_get_latest_tid;	/* heap_get_latest_tid */
+
+	GetTupleData_function get_tuple_data;
+	TupleFromDatum_function tuple_from_datum;
+
+	slot_storageam_hook slot_storageam;
+
+	/*
+	 * 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:
+	 */
+	SpeculativeFinish_function speculative_finish;
+	SpeculativeAbort_function speculative_abort;
+
+
+	RelationSync_function relation_sync;	/* heap_sync */
+
+	SnapshotSatisfies_function snapshot_satisfies;
+	SnapshotSatisfiesUpdate_function snapshot_satisfiesUpdate;	/* HeapTupleSatisfiesUpdate */
+	SnapshotSatisfiesVacuum_function snapshot_satisfiesVacuum;	/* HeapTupleSatisfiesVacuum */
+}			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 4a8ab151d7..f1d0198848 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 ffeeb4919b..805d623cf0 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -497,6 +497,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;
diff --git a/src/include/utils/tqual.h b/src/include/utils/tqual.h
index 96eaf01ca0..4fda7f8384 100644
--- a/src/include/utils/tqual.h
+++ b/src/include/utils/tqual.h
@@ -45,15 +45,6 @@ extern PGDLLIMPORT SnapshotData CatalogSnapshotData;
 #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,
-- 
2.14.2.windows.1

0003-Adding-storageam-hanlder-to-relation-structure.patchapplication/octet-stream; name=0003-Adding-storageam-hanlder-to-relation-structure.patchDownload
From a811356acd49c83218b8fed53954263f09130d76 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
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 b8e37809b0..179906a16f 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -34,6 +34,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"
@@ -1368,10 +1369,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);
@@ -1868,6 +1886,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
@@ -2026,6 +2109,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
 	 */
@@ -2354,6 +2442,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)
@@ -3368,6 +3458,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.
 	 *
@@ -3889,6 +3987,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);
 
@@ -5607,6 +5717,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 4bc61e5380..82d8a534b1 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)
 	 *
@@ -427,6 +433,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 3c53cefe4b..03d996f5a1 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -71,6 +71,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.14.2.windows.1

0004-Adding-tuple-visibility-function-to-storage-AM.patchapplication/octet-stream; name=0004-Adding-tuple-visibility-function-to-storage-AM.patchDownload
From a46e2701bee092245d53a17f69b4aa84cb4d0359 Mon Sep 17 00:00:00 2001
From: Hari Babu Kommi <kommi.haribabu@gmail.com>
Date: Fri, 8 Sep 2017 15:38:28 +1000
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 heapam storage is moved into heapam_common.c
and heapam_common.h files.
---
 contrib/pg_visibility/pg_visibility.c       |   10 +-
 contrib/pgrowlocks/pgrowlocks.c             |    7 +-
 contrib/pgstattuple/pgstatapprox.c          |    7 +-
 contrib/pgstattuple/pgstattuple.c           |    3 +-
 src/backend/access/heap/Makefile            |    3 +-
 src/backend/access/heap/heapam.c            |   57 +-
 src/backend/access/heap/heapam_common.c     |  160 +++
 src/backend/access/heap/heapam_storage.c    | 1644 ++++++++++++++++++++++++
 src/backend/access/heap/pruneheap.c         |    4 +-
 src/backend/access/heap/rewriteheap.c       |    1 +
 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           |    6 +-
 src/backend/executor/nodeBitmapHeapscan.c   |    2 +-
 src/backend/executor/nodeModifyTable.c      |   11 +-
 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/backend/utils/time/tqual.c              | 1807 ---------------------------
 src/include/access/heapam_common.h          |   96 ++
 src/include/access/htup.h                   |    7 +-
 src/include/storage/bufmgr.h                |    6 +-
 src/include/utils/snapshot.h                |    2 +-
 src/include/utils/tqual.h                   |   65 +-
 29 files changed, 2014 insertions(+), 1928 deletions(-)
 create mode 100644 src/backend/access/heap/heapam_common.c
 delete mode 100644 src/backend/utils/time/tqual.c
 create mode 100644 src/include/access/heapam_common.h

diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c
index 2cc9575d9f..10d47cd46f 100644
--- a/contrib/pg_visibility/pg_visibility.c
+++ b/contrib/pg_visibility/pg_visibility.c
@@ -51,7 +51,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 +656,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 +681,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 +739,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 02a3909e7c..e6bc18e5ea 100644
--- a/src/backend/access/heap/Makefile
+++ b/src/backend/access/heap/Makefile
@@ -12,7 +12,8 @@ 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 storageamapi.o \
+OBJS = heapam.o heapam_common.o heapam_storage.o hio.o \
+	pruneheap.o rewriteheap.o storageamapi.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 52dda41cc4..0ee8c53282 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -40,11 +40,13 @@
 
 #include "access/bufmask.h"
 #include "access/heapam.h"
+#include "access/heapam_common.h"
 #include "access/heapam_xlog.h"
 #include "access/hio.h"
 #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 +440,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 +655,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);
 
@@ -954,23 +957,29 @@ 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;
+						return;
+					}
+				}
+				else
 				{
 					scan->rs_cindex = lineindex;
 					return;
 				}
 			}
-			else
-			{
-				scan->rs_cindex = lineindex;
-				return;
-			}
 
 			/*
 			 * otherwise move to the next item on the page
@@ -1831,7 +1840,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 +1959,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);
@@ -2096,7 +2105,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 */
@@ -2270,7 +2279,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;
@@ -2303,7 +2312,7 @@ heap_get_latest_tid(Relation relation,
 bool
 HeapTupleUpdateXmaxMatchesXmin(TransactionId xmax, HeapTupleHeader htup)
 {
-	TransactionId	xmin = HeapTupleHeaderGetXmin(htup);
+	TransactionId xmin = HeapTupleHeaderGetXmin(htup);
 
 	/*
 	 * If the xmax of the old tuple is identical to the xmin of the new one,
@@ -2313,8 +2322,8 @@ HeapTupleUpdateXmaxMatchesXmin(TransactionId xmax, HeapTupleHeader htup)
 		return true;
 
 	/*
-	 * If the Xmin that was in effect prior to a freeze matches the Xmax,
-	 * it's good too.
+	 * If the Xmin that was in effect prior to a freeze matches the Xmax, it's
+	 * good too.
 	 */
 	if (HeapTupleHeaderXminFrozen(htup) &&
 		TransactionIdEquals(HeapTupleHeaderGetRawXmin(htup), xmax))
@@ -3140,7 +3149,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)
 	{
@@ -3251,7 +3260,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;
 	}
 
@@ -3711,7 +3720,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);
@@ -3892,7 +3901,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;
 	}
 
@@ -4643,7 +4652,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_common.c b/src/backend/access/heap/heapam_common.c
new file mode 100644
index 0000000000..4cc3eeff0c
--- /dev/null
+++ b/src/backend/access/heap/heapam_common.c
@@ -0,0 +1,160 @@
+/*-------------------------------------------------------------------------
+ *
+ * heapam_common.c
+ *	  heapam 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/heap/heapam_common.c
+ *
+ *
+ * NOTES
+ *	  This file contains the storage_ routines which implement
+ *	  the POSTGRES storage access method used for all POSTGRES
+ *	  relations.
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/heapam_common.h"
+#include "access/htup_details.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);
+}
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 88827e7957..f7e11d4946 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -21,6 +21,7 @@
 #include "postgres.h"
 
 #include "access/heapam.h"
+#include "access/heapam_common.h"
 #include "access/heapam_xlog.h"
 #include "access/hio.h"
 #include "access/htup_details.h"
@@ -47,12 +48,1655 @@
 #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
+ * has now committed or aborted (and it is safe to set the hint bits).
+ * If the hint bits are changed, MarkBufferDirtyHint is called on
+ * the passed-in buffer.  The caller must hold not only a pin, but at least
+ * shared buffer content lock on the buffer containing the tuple.
+ *
+ * NOTE: When using a non-MVCC snapshot, we must check
+ * TransactionIdIsInProgress (which looks in the PGXACT array)
+ * before TransactionIdDidCommit/TransactionIdDidAbort (which look in
+ * pg_xact).  Otherwise we have a race condition: we might decide that a
+ * just-committed transaction crashed, because none of the tests succeed.
+ * xact.c is careful to record commit/abort in pg_xact before it unsets
+ * MyPgXact->xid in the PGXACT array.  That fixes that problem, but it
+ * also means there is a window where TransactionIdIsInProgress and
+ * TransactionIdDidCommit will both return true.  If we check only
+ * TransactionIdDidCommit, we could consider a tuple committed when a
+ * later GetSnapshotData call will still think the originating transaction
+ * is in progress, which leads to application-level inconsistency.  The
+ * upshot is that we gotta check TransactionIdIsInProgress first in all
+ * code paths, except for a few cases where we are looking at
+ * subtransactions of our own main transaction and so there can't be any
+ * race condition.
+ *
+ * When using an MVCC snapshot, we rely on XidInMVCCSnapshot rather than
+ * TransactionIdIsInProgress, but the logic is otherwise the same: do not
+ * check pg_xact until after deciding that the xact is no longer in progress.
+ *
+ *
+ * Summary of visibility functions:
+ *
+ *	 HeapTupleSatisfiesMVCC()
+ *		  visible to supplied snapshot, excludes current command
+ *	 HeapTupleSatisfiesUpdate()
+ *		  visible to instant snapshot, with user-supplied command
+ *		  counter and more complex result
+ *	 HeapTupleSatisfiesSelf()
+ *		  visible to instant snapshot and current command
+ *	 HeapTupleSatisfiesDirty()
+ *		  like HeapTupleSatisfiesSelf(), but includes open transactions
+ *	 HeapTupleSatisfiesVacuum()
+ *		  visible to any running transaction, used by VACUUM
+ *   HeapTupleSatisfiesNonVacuumable()
+ *        Snapshot-style API for HeapTupleSatisfiesVacuum
+ *	 HeapTupleSatisfiesToast()
+ *		  visible unless part of interrupted vacuum, used for TOAST
+ *	 HeapTupleSatisfiesAny()
+ *		  all tuples are visible
+ *
+ * -------------------------------------------------------------------------
+ */
+
+/*
+ * HeapTupleSatisfiesSelf
+ *		True iff heap tuple is valid "for itself".
+ *
+ *	Here, we consider the effects of:
+ *		all committed transactions (as of the current instant)
+ *		previous commands of this transaction
+ *		changes made by the current command
+ *
+ * Note:
+ *		Assumes heap tuple is valid.
+ *
+ * The satisfaction of "itself" requires the following:
+ *
+ * ((Xmin == my-transaction &&				the row was updated by the current transaction, and
+ *		(Xmax is null						it was not deleted
+ *		 [|| Xmax != my-transaction)])			[or it was deleted by another transaction]
+ * ||
+ *
+ * (Xmin is committed &&					the row was modified by a committed transaction, and
+ *		(Xmax is null ||					the row has not been deleted, or
+ *			(Xmax != my-transaction &&			the row was deleted by another transaction
+ *			 Xmax is not committed)))			that has not been committed
+ */
+static bool
+HeapTupleSatisfiesSelf(StorageTuple stup, Snapshot snapshot, Buffer buffer)
+{
+	HeapTuple	htup = (HeapTuple) stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return false;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return false;
+			if (!TransactionIdIsInProgress(xvac))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (TransactionIdIsInProgress(xvac))
+					return false;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return true;
+
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
+				return true;
+
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleGetUpdateXid(tuple);
+
+				/* not LOCKED_ONLY, so it has to have an xmax */
+				Assert(TransactionIdIsValid(xmax));
+
+				/* updating subtransaction must have aborted */
+				if (!TransactionIdIsCurrentTransactionId(xmax))
+					return true;
+				else
+					return false;
+			}
+
+			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+			{
+				/* deleting subtransaction must have aborted */
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+				return true;
+			}
+
+			return false;
+		}
+		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+			return false;
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return false;
+		}
+	}
+
+	/* by here, the inserting transaction has committed */
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
+		return true;
+
+	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+		return false;			/* updated by other */
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+
+		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 true;
+		if (TransactionIdDidCommit(xmax))
+			return false;
+		/* it must have aborted or crashed */
+		return true;
+	}
+
+	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+		return false;
+	}
+
+	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+		return true;
+
+	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		/* it must have aborted or crashed */
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return true;
+	}
+
+	/* xmax transaction committed */
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	{
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return true;
+	}
+
+	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+				HeapTupleHeaderGetRawXmax(tuple));
+	return false;
+}
+
+/*
+ * HeapTupleSatisfiesAny
+ *		Dummy "satisfies" routine: any tuple satisfies SnapshotAny.
+ */
+static bool
+HeapTupleSatisfiesAny(StorageTuple stup, Snapshot snapshot, Buffer buffer)
+{
+	return true;
+}
+
+/*
+ * HeapTupleSatisfiesToast
+ *		True iff heap tuple is valid as a TOAST row.
+ *
+ * This is a simplified version that only checks for VACUUM moving conditions.
+ * It's appropriate for TOAST usage because TOAST really doesn't want to do
+ * its own time qual checks; if you can see the main table row that contains
+ * a TOAST reference, you should be able to see the TOASTed value.  However,
+ * vacuuming a TOAST table is independent of the main table, and in case such
+ * a vacuum fails partway through, we'd better do this much checking.
+ *
+ * Among other things, this means you can't do UPDATEs of rows in a TOAST
+ * table.
+ */
+static bool
+HeapTupleSatisfiesToast(StorageTuple stup, Snapshot snapshot,
+						Buffer buffer)
+{
+	HeapTuple	htup = (HeapTuple) stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return false;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return false;
+			if (!TransactionIdIsInProgress(xvac))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (TransactionIdIsInProgress(xvac))
+					return false;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+			}
+		}
+
+		/*
+		 * An invalid Xmin can be left behind by a speculative insertion that
+		 * is canceled by super-deleting the tuple.  This also applies to
+		 * TOAST tuples created during speculative insertion.
+		 */
+		else if (!TransactionIdIsValid(HeapTupleHeaderGetXmin(tuple)))
+			return false;
+	}
+
+	/* otherwise assume the tuple is valid for TOAST. */
+	return true;
+}
+
+/*
+ * HeapTupleSatisfiesUpdate
+ *
+ *	This function returns a more detailed result code than most of the
+ *	functions in this file, since UPDATE needs to know more than "is it
+ *	visible?".  It also allows for user-supplied CommandId rather than
+ *	relying on CurrentCommandId.
+ *
+ *	The possible return codes are:
+ *
+ *	HeapTupleInvisible: the tuple didn't exist at all when the scan started,
+ *	e.g. it was created by a later CommandId.
+ *
+ *	HeapTupleMayBeUpdated: The tuple is valid and visible, so it may be
+ *	updated.
+ *
+ *	HeapTupleSelfUpdated: The tuple was updated by the current transaction,
+ *	after the current scan started.
+ *
+ *	HeapTupleUpdated: The tuple was updated by a committed transaction.
+ *
+ *	HeapTupleBeingUpdated: The tuple is being updated by an in-progress
+ *	transaction other than the current transaction.  (Note: this includes
+ *	the case where the tuple is share-locked by a MultiXact, even if the
+ *	MultiXact includes the current transaction.  Callers that want to
+ *	distinguish that case must test for it themselves.)
+ */
+static HTSU_Result
+HeapTupleSatisfiesUpdate(StorageTuple stup, CommandId curcid,
+						 Buffer buffer)
+{
+	HeapTuple	htup = (HeapTuple) stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return HeapTupleInvisible;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return HeapTupleInvisible;
+			if (!TransactionIdIsInProgress(xvac))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return HeapTupleInvisible;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (TransactionIdIsInProgress(xvac))
+					return HeapTupleInvisible;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return HeapTupleInvisible;
+				}
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (HeapTupleHeaderGetCmin(tuple) >= curcid)
+				return HeapTupleInvisible;	/* inserted after scan started */
+
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return HeapTupleMayBeUpdated;
+
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleHeaderGetRawXmax(tuple);
+
+				/*
+				 * Careful here: even though this tuple was created by our own
+				 * transaction, it might be locked by other transactions, if
+				 * the original version was key-share locked when we updated
+				 * it.
+				 */
+
+				if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+				{
+					if (MultiXactIdIsRunning(xmax, true))
+						return HeapTupleBeingUpdated;
+					else
+						return HeapTupleMayBeUpdated;
+				}
+
+				/*
+				 * If the locker is gone, then there is nothing of interest
+				 * left in this Xmax; otherwise, report the tuple as
+				 * locked/updated.
+				 */
+				if (!TransactionIdIsInProgress(xmax))
+					return HeapTupleMayBeUpdated;
+				return HeapTupleBeingUpdated;
+			}
+
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleGetUpdateXid(tuple);
+
+				/* not LOCKED_ONLY, so it has to have an xmax */
+				Assert(TransactionIdIsValid(xmax));
+
+				/* deleting subtransaction must have aborted */
+				if (!TransactionIdIsCurrentTransactionId(xmax))
+				{
+					if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
+											 false))
+						return HeapTupleBeingUpdated;
+					return HeapTupleMayBeUpdated;
+				}
+				else
+				{
+					if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+						return HeapTupleSelfUpdated;	/* updated after scan
+														 * started */
+					else
+						return HeapTupleInvisible;	/* updated before scan
+													 * started */
+				}
+			}
+
+			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+			{
+				/* deleting subtransaction must have aborted */
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+				return HeapTupleMayBeUpdated;
+			}
+
+			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+				return HeapTupleSelfUpdated;	/* updated after scan started */
+			else
+				return HeapTupleInvisible;	/* updated before scan started */
+		}
+		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+			return HeapTupleInvisible;
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return HeapTupleInvisible;
+		}
+	}
+
+	/* by here, the inserting transaction has committed */
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
+		return HeapTupleMayBeUpdated;
+
+	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return HeapTupleMayBeUpdated;
+		return HeapTupleUpdated;	/* updated by other */
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		if (HEAP_LOCKED_UPGRADED(tuple->t_infomask))
+			return HeapTupleMayBeUpdated;
+
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+		{
+			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), true))
+				return HeapTupleBeingUpdated;
+
+			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
+			return HeapTupleMayBeUpdated;
+		}
+
+		xmax = HeapTupleGetUpdateXid(tuple);
+		if (!TransactionIdIsValid(xmax))
+		{
+			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+				return HeapTupleBeingUpdated;
+		}
+
+		/* not LOCKED_ONLY, so it has to have an xmax */
+		Assert(TransactionIdIsValid(xmax));
+
+		if (TransactionIdIsCurrentTransactionId(xmax))
+		{
+			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+				return HeapTupleSelfUpdated;	/* updated after scan started */
+			else
+				return HeapTupleInvisible;	/* updated before scan started */
+		}
+
+		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+			return HeapTupleBeingUpdated;
+
+		if (TransactionIdDidCommit(xmax))
+			return HeapTupleUpdated;
+
+		/*
+		 * By here, the update in the Xmax is either aborted or crashed, but
+		 * what about the other members?
+		 */
+
+		if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+		{
+			/*
+			 * There's no member, even just a locker, alive anymore, so we can
+			 * mark the Xmax as invalid.
+			 */
+			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+						InvalidTransactionId);
+			return HeapTupleMayBeUpdated;
+		}
+		else
+		{
+			/* There are lockers running */
+			return HeapTupleBeingUpdated;
+		}
+	}
+
+	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return HeapTupleBeingUpdated;
+		if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+			return HeapTupleSelfUpdated;	/* updated after scan started */
+		else
+			return HeapTupleInvisible;	/* updated before scan started */
+	}
+
+	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+		return HeapTupleBeingUpdated;
+
+	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		/* it must have aborted or crashed */
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return HeapTupleMayBeUpdated;
+	}
+
+	/* xmax transaction committed */
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	{
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return HeapTupleMayBeUpdated;
+	}
+
+	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+				HeapTupleHeaderGetRawXmax(tuple));
+	return HeapTupleUpdated;	/* updated by other */
+}
+
+/*
+ * HeapTupleSatisfiesDirty
+ *		True iff heap tuple is valid including effects of open transactions.
+ *
+ *	Here, we consider the effects of:
+ *		all committed and in-progress transactions (as of the current instant)
+ *		previous commands of this transaction
+ *		changes made by the current command
+ *
+ * This is essentially like HeapTupleSatisfiesSelf as far as effects of
+ * the current transaction and committed/aborted xacts are concerned.
+ * However, we also include the effects of other xacts still in progress.
+ *
+ * A special hack is that the passed-in snapshot struct is used as an
+ * output argument to return the xids of concurrent xacts that affected the
+ * tuple.  snapshot->xmin is set to the tuple's xmin if that is another
+ * transaction that's still in progress; or to InvalidTransactionId if the
+ * tuple's xmin is committed good, committed dead, or my own xact.
+ * Similarly for snapshot->xmax and the tuple's xmax.  If the tuple was
+ * inserted speculatively, meaning that the inserter might still back down
+ * on the insertion without aborting the whole transaction, the associated
+ * token is also returned in snapshot->speculativeToken.
+ */
+static bool
+HeapTupleSatisfiesDirty(StorageTuple stup, Snapshot snapshot,
+						Buffer buffer)
+{
+	HeapTuple	htup = (HeapTuple) stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	snapshot->xmin = snapshot->xmax = InvalidTransactionId;
+	snapshot->speculativeToken = 0;
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return false;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return false;
+			if (!TransactionIdIsInProgress(xvac))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (TransactionIdIsInProgress(xvac))
+					return false;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return true;
+
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
+				return true;
+
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleGetUpdateXid(tuple);
+
+				/* not LOCKED_ONLY, so it has to have an xmax */
+				Assert(TransactionIdIsValid(xmax));
+
+				/* updating subtransaction must have aborted */
+				if (!TransactionIdIsCurrentTransactionId(xmax))
+					return true;
+				else
+					return false;
+			}
+
+			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+			{
+				/* deleting subtransaction must have aborted */
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+				return true;
+			}
+
+			return false;
+		}
+		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			/*
+			 * Return the speculative token to caller.  Caller can worry about
+			 * xmax, since it requires a conclusively locked row version, and
+			 * a concurrent update to this tuple is a conflict of its
+			 * purposes.
+			 */
+			if (HeapTupleHeaderIsSpeculative(tuple))
+			{
+				snapshot->speculativeToken =
+					HeapTupleHeaderGetSpeculativeToken(tuple);
+
+				Assert(snapshot->speculativeToken != 0);
+			}
+
+			snapshot->xmin = HeapTupleHeaderGetRawXmin(tuple);
+			/* XXX shouldn't we fall through to look at xmax? */
+			return true;		/* in insertion by other */
+		}
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return false;
+		}
+	}
+
+	/* by here, the inserting transaction has committed */
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
+		return true;
+
+	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+		return false;			/* updated by other */
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+
+		xmax = HeapTupleGetUpdateXid(tuple);
+
+		/* not LOCKED_ONLY, so it has to have an xmax */
+		Assert(TransactionIdIsValid(xmax));
+
+		if (TransactionIdIsCurrentTransactionId(xmax))
+			return false;
+		if (TransactionIdIsInProgress(xmax))
+		{
+			snapshot->xmax = xmax;
+			return true;
+		}
+		if (TransactionIdDidCommit(xmax))
+			return false;
+		/* it must have aborted or crashed */
+		return true;
+	}
+
+	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+		return false;
+	}
+
+	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			snapshot->xmax = HeapTupleHeaderGetRawXmax(tuple);
+		return true;
+	}
+
+	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		/* it must have aborted or crashed */
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return true;
+	}
+
+	/* xmax transaction committed */
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	{
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return true;
+	}
+
+	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+				HeapTupleHeaderGetRawXmax(tuple));
+	return false;				/* updated by other */
+}
+
+/*
+ * HeapTupleSatisfiesMVCC
+ *		True iff heap tuple is valid for the given MVCC snapshot.
+ *
+ *	Here, we consider the effects of:
+ *		all transactions committed as of the time of the given snapshot
+ *		previous commands of this transaction
+ *
+ *	Does _not_ include:
+ *		transactions shown as in-progress by the snapshot
+ *		transactions started after the snapshot was taken
+ *		changes made by the current command
+ *
+ * Notice that here, we will not update the tuple status hint bits if the
+ * inserting/deleting transaction is still running according to our snapshot,
+ * even if in reality it's committed or aborted by now.  This is intentional.
+ * Checking the true transaction state would require access to high-traffic
+ * shared data structures, creating contention we'd rather do without, and it
+ * would not change the result of our visibility check anyway.  The hint bits
+ * will be updated by the first visitor that has a snapshot new enough to see
+ * the inserting/deleting transaction as done.  In the meantime, the cost of
+ * leaving the hint bits unset is basically that each HeapTupleSatisfiesMVCC
+ * call will need to run TransactionIdIsCurrentTransactionId in addition to
+ * XidInMVCCSnapshot (but it would have to do the latter anyway).  In the old
+ * coding where we tried to set the hint bits as soon as possible, we instead
+ * did TransactionIdIsInProgress in each call --- to no avail, as long as the
+ * inserting/deleting transaction was still running --- which was more cycles
+ * and more contention on the PGXACT array.
+ */
+static bool
+HeapTupleSatisfiesMVCC(StorageTuple stup, Snapshot snapshot,
+					   Buffer buffer)
+{
+	HeapTuple	htup = (HeapTuple) stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return false;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return false;
+			if (!XidInMVCCSnapshot(xvac, snapshot))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (XidInMVCCSnapshot(xvac, snapshot))
+					return false;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (HeapTupleHeaderGetCmin(tuple) >= snapshot->curcid)
+				return false;	/* inserted after scan started */
+
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return true;
+
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
+				return true;
+
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleGetUpdateXid(tuple);
+
+				/* not LOCKED_ONLY, so it has to have an xmax */
+				Assert(TransactionIdIsValid(xmax));
+
+				/* updating subtransaction must have aborted */
+				if (!TransactionIdIsCurrentTransactionId(xmax))
+					return true;
+				else if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+					return true;	/* updated after scan started */
+				else
+					return false;	/* updated before scan started */
+			}
+
+			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+			{
+				/* deleting subtransaction must have aborted */
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+				return true;
+			}
+
+			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+				return true;	/* deleted after scan started */
+			else
+				return false;	/* deleted before scan started */
+		}
+		else if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
+			return false;
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return false;
+		}
+	}
+	else
+	{
+		/* xmin is committed, but maybe not according to our snapshot */
+		if (!HeapTupleHeaderXminFrozen(tuple) &&
+			XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
+			return false;		/* treat as still in progress */
+	}
+
+	/* by here, the inserting transaction has committed */
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
+		return true;
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+		return true;
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		/* already checked above */
+		Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
+
+		xmax = HeapTupleGetUpdateXid(tuple);
+
+		/* not LOCKED_ONLY, so it has to have an xmax */
+		Assert(TransactionIdIsValid(xmax));
+
+		if (TransactionIdIsCurrentTransactionId(xmax))
+		{
+			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+				return true;	/* deleted after scan started */
+			else
+				return false;	/* deleted before scan started */
+		}
+		if (XidInMVCCSnapshot(xmax, snapshot))
+			return true;
+		if (TransactionIdDidCommit(xmax))
+			return false;		/* updating transaction committed */
+		/* it must have aborted or crashed */
+		return true;
+	}
+
+	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
+	{
+		if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+		{
+			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+				return true;	/* deleted after scan started */
+			else
+				return false;	/* deleted before scan started */
+		}
+
+		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
+			return true;
+
+		if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+						InvalidTransactionId);
+			return true;
+		}
+
+		/* xmax transaction committed */
+		SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+					HeapTupleHeaderGetRawXmax(tuple));
+	}
+	else
+	{
+		/* xmax is committed, but maybe not according to our snapshot */
+		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
+			return true;		/* treat as still in progress */
+	}
+
+	/* xmax transaction committed */
+
+	return false;
+}
+
+
+/*
+ * HeapTupleSatisfiesVacuum
+ *
+ *	Determine the status of tuples for VACUUM purposes.  Here, what
+ *	we mainly want to know is if a tuple is potentially visible to *any*
+ *	running transaction.  If so, it can't be removed yet by VACUUM.
+ *
+ * OldestXmin is a cutoff XID (obtained from GetOldestXmin()).  Tuples
+ * deleted by XIDs >= OldestXmin are deemed "recently dead"; they might
+ * still be visible to some open transaction, so we can't remove them,
+ * even if we see that the deleting transaction has committed.
+ */
+static HTSV_Result
+HeapTupleSatisfiesVacuum(StorageTuple stup, TransactionId OldestXmin,
+						 Buffer buffer)
+{
+	HeapTuple	htup = (HeapTuple) stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	/*
+	 * Has inserting transaction committed?
+	 *
+	 * If the inserting transaction aborted, then the tuple was never visible
+	 * to any other transaction, so we can delete it immediately.
+	 */
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return HEAPTUPLE_DEAD;
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return HEAPTUPLE_DELETE_IN_PROGRESS;
+			if (TransactionIdIsInProgress(xvac))
+				return HEAPTUPLE_DELETE_IN_PROGRESS;
+			if (TransactionIdDidCommit(xvac))
+			{
+				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+							InvalidTransactionId);
+				return HEAPTUPLE_DEAD;
+			}
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						InvalidTransactionId);
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return HEAPTUPLE_INSERT_IN_PROGRESS;
+			if (TransactionIdIsInProgress(xvac))
+				return HEAPTUPLE_INSERT_IN_PROGRESS;
+			if (TransactionIdDidCommit(xvac))
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			else
+			{
+				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+							InvalidTransactionId);
+				return HEAPTUPLE_DEAD;
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return HEAPTUPLE_INSERT_IN_PROGRESS;
+			/* only locked? run infomask-only check first, for performance */
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask) ||
+				HeapTupleHeaderIsOnlyLocked(tuple))
+				return HEAPTUPLE_INSERT_IN_PROGRESS;
+			/* inserted and then deleted by same xact */
+			if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple)))
+				return HEAPTUPLE_DELETE_IN_PROGRESS;
+			/* deleting subtransaction must have aborted */
+			return HEAPTUPLE_INSERT_IN_PROGRESS;
+		}
+		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			/*
+			 * It'd be possible to discern between INSERT/DELETE in progress
+			 * here by looking at xmax - but that doesn't seem beneficial for
+			 * the majority of callers and even detrimental for some. We'd
+			 * rather have callers look at/wait for xmin than xmax. It's
+			 * always correct to return INSERT_IN_PROGRESS because that's
+			 * what's happening from the view of other backends.
+			 */
+			return HEAPTUPLE_INSERT_IN_PROGRESS;
+		}
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/*
+			 * Not in Progress, Not Committed, so either Aborted or crashed
+			 */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return HEAPTUPLE_DEAD;
+		}
+
+		/*
+		 * At this point the xmin is known committed, but we might not have
+		 * been able to set the hint bit yet; so we can no longer Assert that
+		 * it's set.
+		 */
+	}
+
+	/*
+	 * Okay, the inserter committed, so it was good at some point.  Now what
+	 * about the deleting transaction?
+	 */
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)
+		return HEAPTUPLE_LIVE;
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	{
+		/*
+		 * "Deleting" xact really only locked it, so the tuple is live in any
+		 * case.  However, we should make sure that either XMAX_COMMITTED or
+		 * XMAX_INVALID gets set once the xact is gone, to reduce the costs of
+		 * examining the tuple for future xacts.
+		 */
+		if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
+		{
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				/*
+				 * If it's a pre-pg_upgrade tuple, the multixact cannot
+				 * possibly be running; otherwise have to check.
+				 */
+				if (!HEAP_LOCKED_UPGRADED(tuple->t_infomask) &&
+					MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
+										 true))
+					return HEAPTUPLE_LIVE;
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
+			}
+			else
+			{
+				if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+					return HEAPTUPLE_LIVE;
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+			}
+		}
+
+		/*
+		 * We don't really care whether xmax did commit, abort or crash. We
+		 * know that xmax did lock the tuple, but it did not and will never
+		 * actually update it.
+		 */
+
+		return HEAPTUPLE_LIVE;
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+		{
+			/* already checked above */
+			Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
+
+			xmax = HeapTupleGetUpdateXid(tuple);
+
+			/* not LOCKED_ONLY, so it has to have an xmax */
+			Assert(TransactionIdIsValid(xmax));
+
+			if (TransactionIdIsInProgress(xmax))
+				return HEAPTUPLE_DELETE_IN_PROGRESS;
+			else if (TransactionIdDidCommit(xmax))
+				/* there are still lockers around -- can't return DEAD here */
+				return HEAPTUPLE_RECENTLY_DEAD;
+			/* updating transaction aborted */
+			return HEAPTUPLE_LIVE;
+		}
+
+		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED));
+
+		xmax = HeapTupleGetUpdateXid(tuple);
+
+		/* not LOCKED_ONLY, so it has to have an xmax */
+		Assert(TransactionIdIsValid(xmax));
+
+		/* multi is not running -- updating xact cannot be */
+		Assert(!TransactionIdIsInProgress(xmax));
+		if (TransactionIdDidCommit(xmax))
+		{
+			if (!TransactionIdPrecedes(xmax, OldestXmin))
+				return HEAPTUPLE_RECENTLY_DEAD;
+			else
+				return HEAPTUPLE_DEAD;
+		}
+
+		/*
+		 * Not in Progress, Not Committed, so either Aborted or crashed.
+		 * Remove the Xmax.
+		 */
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
+		return HEAPTUPLE_LIVE;
+	}
+
+	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
+	{
+		if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+			return HEAPTUPLE_DELETE_IN_PROGRESS;
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+						HeapTupleHeaderGetRawXmax(tuple));
+		else
+		{
+			/*
+			 * Not in Progress, Not Committed, so either Aborted or crashed
+			 */
+			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+						InvalidTransactionId);
+			return HEAPTUPLE_LIVE;
+		}
+
+		/*
+		 * At this point the xmax is known committed, but we might not have
+		 * been able to set the hint bit yet; so we can no longer Assert that
+		 * it's set.
+		 */
+	}
+
+	/*
+	 * Deleter committed, but perhaps it was recent enough that some open
+	 * transactions could still see the tuple.
+	 */
+	if (!TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin))
+		return HEAPTUPLE_RECENTLY_DEAD;
+
+	/* Otherwise, it's dead and removable */
+	return HEAPTUPLE_DEAD;
+}
+
+/*
+ * HeapTupleSatisfiesNonVacuumable
+ *
+ *     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.
+ */
+static bool
+HeapTupleSatisfiesNonVacuumable(StorageTuple htup, Snapshot snapshot,
+								Buffer buffer)
+{
+	return HeapTupleSatisfiesVacuum(htup, snapshot->xmin, buffer)
+		!= HEAPTUPLE_DEAD;
+}
+
+/*
+ * 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;
+}
+
+/*
+ * check whether the transaction id 'xid' is in the pre-sorted array 'xip'.
+ */
+static bool
+TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num)
+{
+	return bsearch(&xid, xip, num,
+				   sizeof(TransactionId), xidComparator) != NULL;
+}
+
+/*
+ * See the comments for HeapTupleSatisfiesMVCC for the semantics this function
+ * obeys.
+ *
+ * Only usable on tuples from catalog tables!
+ *
+ * We don't need to support HEAP_MOVED_(IN|OFF) for now because we only support
+ * reading catalog pages which couldn't have been created in an older version.
+ *
+ * We don't set any hint bits in here as it seems unlikely to be beneficial as
+ * those should already be set by normal access and it seems to be too
+ * dangerous to do so as the semantics of doing so during timetravel are more
+ * complicated than when dealing "only" with the present.
+ */
+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);
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	/* inserting transaction aborted */
+	if (HeapTupleHeaderXminInvalid(tuple))
+	{
+		Assert(!TransactionIdDidCommit(xmin));
+		return false;
+	}
+	/* check if it's one of our txids, toplevel is also in there */
+	else if (TransactionIdInArray(xmin, snapshot->subxip, snapshot->subxcnt))
+	{
+		bool		resolved;
+		CommandId	cmin = HeapTupleHeaderGetRawCommandId(tuple);
+		CommandId	cmax = InvalidCommandId;
+
+		/*
+		 * another transaction might have (tried to) delete this tuple or
+		 * cmin/cmax was stored in a combocid. So we need to lookup the actual
+		 * values externally.
+		 */
+		resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot,
+												 htup, buffer,
+												 &cmin, &cmax);
+
+		if (!resolved)
+			elog(ERROR, "could not resolve cmin/cmax of catalog tuple");
+
+		Assert(cmin != InvalidCommandId);
+
+		if (cmin >= snapshot->curcid)
+			return false;		/* inserted after scan started */
+		/* fall through */
+	}
+	/* committed before our xmin horizon. Do a normal visibility check. */
+	else if (TransactionIdPrecedes(xmin, snapshot->xmin))
+	{
+		Assert(!(HeapTupleHeaderXminCommitted(tuple) &&
+				 !TransactionIdDidCommit(xmin)));
+
+		/* check for hint bit first, consult clog afterwards */
+		if (!HeapTupleHeaderXminCommitted(tuple) &&
+			!TransactionIdDidCommit(xmin))
+			return false;
+		/* fall through */
+	}
+	/* beyond our xmax horizon, i.e. invisible */
+	else if (TransactionIdFollowsOrEquals(xmin, snapshot->xmax))
+	{
+		return false;
+	}
+	/* check if it's a committed transaction in [xmin, xmax) */
+	else if (TransactionIdInArray(xmin, snapshot->xip, snapshot->xcnt))
+	{
+		/* fall through */
+	}
+
+	/*
+	 * none of the above, i.e. between [xmin, xmax) but hasn't committed. I.e.
+	 * invisible.
+	 */
+	else
+	{
+		return false;
+	}
+
+	/* at this point we know xmin is visible, go on to check xmax */
+
+	/* xid invalid or aborted */
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)
+		return true;
+	/* locked tuples are always visible */
+	else if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+		return true;
+
+	/*
+	 * We can see multis here if we're looking at user tables or if somebody
+	 * SELECT ... FOR SHARE/UPDATE a system table.
+	 */
+	else if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		xmax = HeapTupleGetUpdateXid(tuple);
+	}
+
+	/* check if it's one of our txids, toplevel is also in there */
+	if (TransactionIdInArray(xmax, snapshot->subxip, snapshot->subxcnt))
+	{
+		bool		resolved;
+		CommandId	cmin;
+		CommandId	cmax = HeapTupleHeaderGetRawCommandId(tuple);
+
+		/* Lookup actual cmin/cmax values */
+		resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot,
+												 htup, buffer,
+												 &cmin, &cmax);
+
+		if (!resolved)
+			elog(ERROR, "could not resolve combocid to cmax");
+
+		Assert(cmax != InvalidCommandId);
+
+		if (cmax >= snapshot->curcid)
+			return true;		/* deleted after scan started */
+		else
+			return false;		/* deleted before scan started */
+	}
+	/* below xmin horizon, normal transaction state is valid */
+	else if (TransactionIdPrecedes(xmax, snapshot->xmin))
+	{
+		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED &&
+				 !TransactionIdDidCommit(xmax)));
+
+		/* check hint bit first */
+		if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+			return false;
+
+		/* check clog */
+		return !TransactionIdDidCommit(xmax);
+	}
+	/* above xmax horizon, we cannot possibly see the deleting transaction */
+	else if (TransactionIdFollowsOrEquals(xmax, snapshot->xmax))
+		return true;
+	/* xmax is between [xmin, xmax), check known committed array */
+	else if (TransactionIdInArray(xmax, snapshot->xip, snapshot->xcnt))
+		return false;
+	/* xmax is between [xmin, xmax), but known not to have committed yet */
+	else
+		return true;
+}
+
+static 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;
+	}
+}
 
 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/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 7753ee7b12..efeb9c1d3a 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/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index f93c194e18..3118c2d33f 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -108,6 +108,7 @@
 #include "miscadmin.h"
 
 #include "access/heapam.h"
+#include "access/heapam_common.h"
 #include "access/heapam_xlog.h"
 #include "access/rewriteheap.h"
 #include "access/transam.h"
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/catalog/index.c b/src/backend/catalog/index.c
index c7b2f031f0..17be9a4042 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2222,6 +2222,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	TransactionId OldestXmin;
 	BlockNumber root_blkno = InvalidBlockNumber;
 	OffsetNumber root_offsets[MaxHeapTuplesPerPage];
+	StorageAmRoutine *method;
 
 	/*
 	 * sanity checks
@@ -2277,6 +2278,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 */
@@ -2357,8 +2359,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 760d19142e..247d9c2f56 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1083,9 +1083,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 172d213fdb..c89fefff9a 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -355,7 +355,7 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
 									   params->log_min_duration))
 		{
 			StringInfoData buf;
-			char *msgfmt;
+			char	   *msgfmt;
 
 			TimestampDifference(starttime, endtime, &secs, &usecs);
 
@@ -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 6035b4dfd4..c34898f241 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -428,7 +428,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 0027d21253..dbc242c699 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -190,6 +190,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
  */
 static void
 ExecCheckHeapTupleVisible(EState *estate,
+						  Relation rel,
 						  HeapTuple tuple,
 						  Buffer buffer)
 {
@@ -201,7 +202,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
@@ -236,7 +237,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);
 }
 
@@ -1312,7 +1313,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);
@@ -1517,8 +1518,8 @@ ExecSetupTransitionCaptureState(ModifyTableState *mtstate, EState *estate)
 		if (mtstate->mt_partition_dispatch_info != NULL)
 		{
 			/*
-			 * For tuple routing among partitions, we need TupleDescs based
-			 * on the partition routing table.
+			 * For tuple routing among partitions, we need TupleDescs based on
+			 * the partition routing table.
 			 */
 			ResultRelInfo **resultRelInfos = mtstate->mt_partitions;
 
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 ba0e3ad87d..16e6755844 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -289,7 +289,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 294ab705f1..f823da09f4 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;
@@ -2040,7 +2040,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),
@@ -2137,7 +2137,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/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c
deleted file mode 100644
index b7aab0dd19..0000000000
--- a/src/backend/utils/time/tqual.c
+++ /dev/null
@@ -1,1807 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * tqual.c
- *	  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
- * has now committed or aborted (and it is safe to set the hint bits).
- * If the hint bits are changed, MarkBufferDirtyHint is called on
- * the passed-in buffer.  The caller must hold not only a pin, but at least
- * shared buffer content lock on the buffer containing the tuple.
- *
- * NOTE: When using a non-MVCC snapshot, we must check
- * TransactionIdIsInProgress (which looks in the PGXACT array)
- * before TransactionIdDidCommit/TransactionIdDidAbort (which look in
- * pg_xact).  Otherwise we have a race condition: we might decide that a
- * just-committed transaction crashed, because none of the tests succeed.
- * xact.c is careful to record commit/abort in pg_xact before it unsets
- * MyPgXact->xid in the PGXACT array.  That fixes that problem, but it
- * also means there is a window where TransactionIdIsInProgress and
- * TransactionIdDidCommit will both return true.  If we check only
- * TransactionIdDidCommit, we could consider a tuple committed when a
- * later GetSnapshotData call will still think the originating transaction
- * is in progress, which leads to application-level inconsistency.  The
- * upshot is that we gotta check TransactionIdIsInProgress first in all
- * code paths, except for a few cases where we are looking at
- * subtransactions of our own main transaction and so there can't be any
- * race condition.
- *
- * When using an MVCC snapshot, we rely on XidInMVCCSnapshot rather than
- * TransactionIdIsInProgress, but the logic is otherwise the same: do not
- * check pg_xact until after deciding that the xact is no longer in progress.
- *
- *
- * Summary of visibility functions:
- *
- *	 HeapTupleSatisfiesMVCC()
- *		  visible to supplied snapshot, excludes current command
- *	 HeapTupleSatisfiesUpdate()
- *		  visible to instant snapshot, with user-supplied command
- *		  counter and more complex result
- *	 HeapTupleSatisfiesSelf()
- *		  visible to instant snapshot and current command
- *	 HeapTupleSatisfiesDirty()
- *		  like HeapTupleSatisfiesSelf(), but includes open transactions
- *	 HeapTupleSatisfiesVacuum()
- *		  visible to any running transaction, used by VACUUM
- *	 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
- *		True iff heap tuple is valid "for itself".
- *
- *	Here, we consider the effects of:
- *		all committed transactions (as of the current instant)
- *		previous commands of this transaction
- *		changes made by the current command
- *
- * Note:
- *		Assumes heap tuple is valid.
- *
- * The satisfaction of "itself" requires the following:
- *
- * ((Xmin == my-transaction &&				the row was updated by the current transaction, and
- *		(Xmax is null						it was not deleted
- *		 [|| Xmax != my-transaction)])			[or it was deleted by another transaction]
- * ||
- *
- * (Xmin is committed &&					the row was modified by a committed transaction, and
- *		(Xmax is null ||					the row has not been deleted, or
- *			(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)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return false;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return false;
-			if (!TransactionIdIsInProgress(xvac))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (TransactionIdIsInProgress(xvac))
-					return false;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return true;
-
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
-				return true;
-
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleGetUpdateXid(tuple);
-
-				/* not LOCKED_ONLY, so it has to have an xmax */
-				Assert(TransactionIdIsValid(xmax));
-
-				/* updating subtransaction must have aborted */
-				if (!TransactionIdIsCurrentTransactionId(xmax))
-					return true;
-				else
-					return false;
-			}
-
-			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-			{
-				/* deleting subtransaction must have aborted */
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-				return true;
-			}
-
-			return false;
-		}
-		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
-			return false;
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return false;
-		}
-	}
-
-	/* by here, the inserting transaction has committed */
-
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
-		return true;
-
-	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-		return false;			/* updated by other */
-	}
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-
-		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 true;
-		if (TransactionIdDidCommit(xmax))
-			return false;
-		/* it must have aborted or crashed */
-		return true;
-	}
-
-	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-		return false;
-	}
-
-	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-		return true;
-
-	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		/* it must have aborted or crashed */
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return true;
-	}
-
-	/* xmax transaction committed */
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-	{
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return true;
-	}
-
-	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-				HeapTupleHeaderGetRawXmax(tuple));
-	return false;
-}
-
-/*
- * HeapTupleSatisfiesAny
- *		Dummy "satisfies" routine: any tuple satisfies SnapshotAny.
- */
-bool
-HeapTupleSatisfiesAny(HeapTuple htup, Snapshot snapshot, Buffer buffer)
-{
-	return true;
-}
-
-/*
- * HeapTupleSatisfiesToast
- *		True iff heap tuple is valid as a TOAST row.
- *
- * This is a simplified version that only checks for VACUUM moving conditions.
- * It's appropriate for TOAST usage because TOAST really doesn't want to do
- * its own time qual checks; if you can see the main table row that contains
- * a TOAST reference, you should be able to see the TOASTed value.  However,
- * vacuuming a TOAST table is independent of the main table, and in case such
- * a vacuum fails partway through, we'd better do this much checking.
- *
- * Among other things, this means you can't do UPDATEs of rows in a TOAST
- * table.
- */
-bool
-HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot,
-						Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return false;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return false;
-			if (!TransactionIdIsInProgress(xvac))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (TransactionIdIsInProgress(xvac))
-					return false;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-			}
-		}
-
-		/*
-		 * An invalid Xmin can be left behind by a speculative insertion that
-		 * is canceled by super-deleting the tuple.  This also applies to
-		 * TOAST tuples created during speculative insertion.
-		 */
-		else if (!TransactionIdIsValid(HeapTupleHeaderGetXmin(tuple)))
-			return false;
-	}
-
-	/* otherwise assume the tuple is valid for TOAST. */
-	return true;
-}
-
-/*
- * HeapTupleSatisfiesUpdate
- *
- *	This function returns a more detailed result code than most of the
- *	functions in this file, since UPDATE needs to know more than "is it
- *	visible?".  It also allows for user-supplied CommandId rather than
- *	relying on CurrentCommandId.
- *
- *	The possible return codes are:
- *
- *	HeapTupleInvisible: the tuple didn't exist at all when the scan started,
- *	e.g. it was created by a later CommandId.
- *
- *	HeapTupleMayBeUpdated: The tuple is valid and visible, so it may be
- *	updated.
- *
- *	HeapTupleSelfUpdated: The tuple was updated by the current transaction,
- *	after the current scan started.
- *
- *	HeapTupleUpdated: The tuple was updated by a committed transaction.
- *
- *	HeapTupleBeingUpdated: The tuple is being updated by an in-progress
- *	transaction other than the current transaction.  (Note: this includes
- *	the case where the tuple is share-locked by a MultiXact, even if the
- *	MultiXact includes the current transaction.  Callers that want to
- *	distinguish that case must test for it themselves.)
- */
-HTSU_Result
-HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
-						 Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return HeapTupleInvisible;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return HeapTupleInvisible;
-			if (!TransactionIdIsInProgress(xvac))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return HeapTupleInvisible;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (TransactionIdIsInProgress(xvac))
-					return HeapTupleInvisible;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return HeapTupleInvisible;
-				}
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (HeapTupleHeaderGetCmin(tuple) >= curcid)
-				return HeapTupleInvisible;	/* inserted after scan started */
-
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return HeapTupleMayBeUpdated;
-
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleHeaderGetRawXmax(tuple);
-
-				/*
-				 * Careful here: even though this tuple was created by our own
-				 * transaction, it might be locked by other transactions, if
-				 * the original version was key-share locked when we updated
-				 * it.
-				 */
-
-				if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-				{
-					if (MultiXactIdIsRunning(xmax, true))
-						return HeapTupleBeingUpdated;
-					else
-						return HeapTupleMayBeUpdated;
-				}
-
-				/*
-				 * If the locker is gone, then there is nothing of interest
-				 * left in this Xmax; otherwise, report the tuple as
-				 * locked/updated.
-				 */
-				if (!TransactionIdIsInProgress(xmax))
-					return HeapTupleMayBeUpdated;
-				return HeapTupleBeingUpdated;
-			}
-
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleGetUpdateXid(tuple);
-
-				/* not LOCKED_ONLY, so it has to have an xmax */
-				Assert(TransactionIdIsValid(xmax));
-
-				/* deleting subtransaction must have aborted */
-				if (!TransactionIdIsCurrentTransactionId(xmax))
-				{
-					if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
-											 false))
-						return HeapTupleBeingUpdated;
-					return HeapTupleMayBeUpdated;
-				}
-				else
-				{
-					if (HeapTupleHeaderGetCmax(tuple) >= curcid)
-						return HeapTupleSelfUpdated;	/* updated after scan
-														 * started */
-					else
-						return HeapTupleInvisible;	/* updated before scan
-													 * started */
-				}
-			}
-
-			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-			{
-				/* deleting subtransaction must have aborted */
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-				return HeapTupleMayBeUpdated;
-			}
-
-			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
-				return HeapTupleSelfUpdated;	/* updated after scan started */
-			else
-				return HeapTupleInvisible;	/* updated before scan started */
-		}
-		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
-			return HeapTupleInvisible;
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return HeapTupleInvisible;
-		}
-	}
-
-	/* by here, the inserting transaction has committed */
-
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
-		return HeapTupleMayBeUpdated;
-
-	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return HeapTupleMayBeUpdated;
-		return HeapTupleUpdated;	/* updated by other */
-	}
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		if (HEAP_LOCKED_UPGRADED(tuple->t_infomask))
-			return HeapTupleMayBeUpdated;
-
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-		{
-			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), true))
-				return HeapTupleBeingUpdated;
-
-			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
-			return HeapTupleMayBeUpdated;
-		}
-
-		xmax = HeapTupleGetUpdateXid(tuple);
-		if (!TransactionIdIsValid(xmax))
-		{
-			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
-				return HeapTupleBeingUpdated;
-		}
-
-		/* not LOCKED_ONLY, so it has to have an xmax */
-		Assert(TransactionIdIsValid(xmax));
-
-		if (TransactionIdIsCurrentTransactionId(xmax))
-		{
-			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
-				return HeapTupleSelfUpdated;	/* updated after scan started */
-			else
-				return HeapTupleInvisible;	/* updated before scan started */
-		}
-
-		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
-			return HeapTupleBeingUpdated;
-
-		if (TransactionIdDidCommit(xmax))
-			return HeapTupleUpdated;
-
-		/*
-		 * By here, the update in the Xmax is either aborted or crashed, but
-		 * what about the other members?
-		 */
-
-		if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
-		{
-			/*
-			 * There's no member, even just a locker, alive anymore, so we can
-			 * mark the Xmax as invalid.
-			 */
-			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-						InvalidTransactionId);
-			return HeapTupleMayBeUpdated;
-		}
-		else
-		{
-			/* There are lockers running */
-			return HeapTupleBeingUpdated;
-		}
-	}
-
-	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return HeapTupleBeingUpdated;
-		if (HeapTupleHeaderGetCmax(tuple) >= curcid)
-			return HeapTupleSelfUpdated;	/* updated after scan started */
-		else
-			return HeapTupleInvisible;	/* updated before scan started */
-	}
-
-	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-		return HeapTupleBeingUpdated;
-
-	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		/* it must have aborted or crashed */
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return HeapTupleMayBeUpdated;
-	}
-
-	/* xmax transaction committed */
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-	{
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return HeapTupleMayBeUpdated;
-	}
-
-	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-				HeapTupleHeaderGetRawXmax(tuple));
-	return HeapTupleUpdated;	/* updated by other */
-}
-
-/*
- * HeapTupleSatisfiesDirty
- *		True iff heap tuple is valid including effects of open transactions.
- *
- *	Here, we consider the effects of:
- *		all committed and in-progress transactions (as of the current instant)
- *		previous commands of this transaction
- *		changes made by the current command
- *
- * This is essentially like HeapTupleSatisfiesSelf as far as effects of
- * the current transaction and committed/aborted xacts are concerned.
- * However, we also include the effects of other xacts still in progress.
- *
- * A special hack is that the passed-in snapshot struct is used as an
- * output argument to return the xids of concurrent xacts that affected the
- * tuple.  snapshot->xmin is set to the tuple's xmin if that is another
- * transaction that's still in progress; or to InvalidTransactionId if the
- * tuple's xmin is committed good, committed dead, or my own xact.
- * Similarly for snapshot->xmax and the tuple's xmax.  If the tuple was
- * inserted speculatively, meaning that the inserter might still back down
- * on the insertion without aborting the whole transaction, the associated
- * token is also returned in snapshot->speculativeToken.
- */
-bool
-HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
-						Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	snapshot->xmin = snapshot->xmax = InvalidTransactionId;
-	snapshot->speculativeToken = 0;
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return false;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return false;
-			if (!TransactionIdIsInProgress(xvac))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (TransactionIdIsInProgress(xvac))
-					return false;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return true;
-
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
-				return true;
-
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleGetUpdateXid(tuple);
-
-				/* not LOCKED_ONLY, so it has to have an xmax */
-				Assert(TransactionIdIsValid(xmax));
-
-				/* updating subtransaction must have aborted */
-				if (!TransactionIdIsCurrentTransactionId(xmax))
-					return true;
-				else
-					return false;
-			}
-
-			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-			{
-				/* deleting subtransaction must have aborted */
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-				return true;
-			}
-
-			return false;
-		}
-		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			/*
-			 * Return the speculative token to caller.  Caller can worry about
-			 * xmax, since it requires a conclusively locked row version, and
-			 * a concurrent update to this tuple is a conflict of its
-			 * purposes.
-			 */
-			if (HeapTupleHeaderIsSpeculative(tuple))
-			{
-				snapshot->speculativeToken =
-					HeapTupleHeaderGetSpeculativeToken(tuple);
-
-				Assert(snapshot->speculativeToken != 0);
-			}
-
-			snapshot->xmin = HeapTupleHeaderGetRawXmin(tuple);
-			/* XXX shouldn't we fall through to look at xmax? */
-			return true;		/* in insertion by other */
-		}
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return false;
-		}
-	}
-
-	/* by here, the inserting transaction has committed */
-
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
-		return true;
-
-	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-		return false;			/* updated by other */
-	}
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-
-		xmax = HeapTupleGetUpdateXid(tuple);
-
-		/* not LOCKED_ONLY, so it has to have an xmax */
-		Assert(TransactionIdIsValid(xmax));
-
-		if (TransactionIdIsCurrentTransactionId(xmax))
-			return false;
-		if (TransactionIdIsInProgress(xmax))
-		{
-			snapshot->xmax = xmax;
-			return true;
-		}
-		if (TransactionIdDidCommit(xmax))
-			return false;
-		/* it must have aborted or crashed */
-		return true;
-	}
-
-	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-		return false;
-	}
-
-	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			snapshot->xmax = HeapTupleHeaderGetRawXmax(tuple);
-		return true;
-	}
-
-	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		/* it must have aborted or crashed */
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return true;
-	}
-
-	/* xmax transaction committed */
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-	{
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return true;
-	}
-
-	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-				HeapTupleHeaderGetRawXmax(tuple));
-	return false;				/* updated by other */
-}
-
-/*
- * HeapTupleSatisfiesMVCC
- *		True iff heap tuple is valid for the given MVCC snapshot.
- *
- *	Here, we consider the effects of:
- *		all transactions committed as of the time of the given snapshot
- *		previous commands of this transaction
- *
- *	Does _not_ include:
- *		transactions shown as in-progress by the snapshot
- *		transactions started after the snapshot was taken
- *		changes made by the current command
- *
- * Notice that here, we will not update the tuple status hint bits if the
- * inserting/deleting transaction is still running according to our snapshot,
- * even if in reality it's committed or aborted by now.  This is intentional.
- * Checking the true transaction state would require access to high-traffic
- * shared data structures, creating contention we'd rather do without, and it
- * would not change the result of our visibility check anyway.  The hint bits
- * will be updated by the first visitor that has a snapshot new enough to see
- * the inserting/deleting transaction as done.  In the meantime, the cost of
- * leaving the hint bits unset is basically that each HeapTupleSatisfiesMVCC
- * call will need to run TransactionIdIsCurrentTransactionId in addition to
- * XidInMVCCSnapshot (but it would have to do the latter anyway).  In the old
- * coding where we tried to set the hint bits as soon as possible, we instead
- * did TransactionIdIsInProgress in each call --- to no avail, as long as the
- * inserting/deleting transaction was still running --- which was more cycles
- * and more contention on the PGXACT array.
- */
-bool
-HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
-					   Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return false;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return false;
-			if (!XidInMVCCSnapshot(xvac, snapshot))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (XidInMVCCSnapshot(xvac, snapshot))
-					return false;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (HeapTupleHeaderGetCmin(tuple) >= snapshot->curcid)
-				return false;	/* inserted after scan started */
-
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return true;
-
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
-				return true;
-
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleGetUpdateXid(tuple);
-
-				/* not LOCKED_ONLY, so it has to have an xmax */
-				Assert(TransactionIdIsValid(xmax));
-
-				/* updating subtransaction must have aborted */
-				if (!TransactionIdIsCurrentTransactionId(xmax))
-					return true;
-				else if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
-					return true;	/* updated after scan started */
-				else
-					return false;	/* updated before scan started */
-			}
-
-			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-			{
-				/* deleting subtransaction must have aborted */
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-				return true;
-			}
-
-			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
-				return true;	/* deleted after scan started */
-			else
-				return false;	/* deleted before scan started */
-		}
-		else if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
-			return false;
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return false;
-		}
-	}
-	else
-	{
-		/* xmin is committed, but maybe not according to our snapshot */
-		if (!HeapTupleHeaderXminFrozen(tuple) &&
-			XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
-			return false;		/* treat as still in progress */
-	}
-
-	/* by here, the inserting transaction has committed */
-
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
-		return true;
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-		return true;
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		/* already checked above */
-		Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
-
-		xmax = HeapTupleGetUpdateXid(tuple);
-
-		/* not LOCKED_ONLY, so it has to have an xmax */
-		Assert(TransactionIdIsValid(xmax));
-
-		if (TransactionIdIsCurrentTransactionId(xmax))
-		{
-			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
-				return true;	/* deleted after scan started */
-			else
-				return false;	/* deleted before scan started */
-		}
-		if (XidInMVCCSnapshot(xmax, snapshot))
-			return true;
-		if (TransactionIdDidCommit(xmax))
-			return false;		/* updating transaction committed */
-		/* it must have aborted or crashed */
-		return true;
-	}
-
-	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
-	{
-		if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-		{
-			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
-				return true;	/* deleted after scan started */
-			else
-				return false;	/* deleted before scan started */
-		}
-
-		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
-			return true;
-
-		if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-						InvalidTransactionId);
-			return true;
-		}
-
-		/* xmax transaction committed */
-		SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-					HeapTupleHeaderGetRawXmax(tuple));
-	}
-	else
-	{
-		/* xmax is committed, but maybe not according to our snapshot */
-		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
-			return true;		/* treat as still in progress */
-	}
-
-	/* xmax transaction committed */
-
-	return false;
-}
-
-
-/*
- * HeapTupleSatisfiesVacuum
- *
- *	Determine the status of tuples for VACUUM purposes.  Here, what
- *	we mainly want to know is if a tuple is potentially visible to *any*
- *	running transaction.  If so, it can't be removed yet by VACUUM.
- *
- * OldestXmin is a cutoff XID (obtained from GetOldestXmin()).  Tuples
- * deleted by XIDs >= OldestXmin are deemed "recently dead"; they might
- * still be visible to some open transaction, so we can't remove them,
- * even if we see that the deleting transaction has committed.
- */
-HTSV_Result
-HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
-						 Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	/*
-	 * Has inserting transaction committed?
-	 *
-	 * If the inserting transaction aborted, then the tuple was never visible
-	 * to any other transaction, so we can delete it immediately.
-	 */
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return HEAPTUPLE_DEAD;
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return HEAPTUPLE_DELETE_IN_PROGRESS;
-			if (TransactionIdIsInProgress(xvac))
-				return HEAPTUPLE_DELETE_IN_PROGRESS;
-			if (TransactionIdDidCommit(xvac))
-			{
-				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-							InvalidTransactionId);
-				return HEAPTUPLE_DEAD;
-			}
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						InvalidTransactionId);
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return HEAPTUPLE_INSERT_IN_PROGRESS;
-			if (TransactionIdIsInProgress(xvac))
-				return HEAPTUPLE_INSERT_IN_PROGRESS;
-			if (TransactionIdDidCommit(xvac))
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			else
-			{
-				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-							InvalidTransactionId);
-				return HEAPTUPLE_DEAD;
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return HEAPTUPLE_INSERT_IN_PROGRESS;
-			/* only locked? run infomask-only check first, for performance */
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask) ||
-				HeapTupleHeaderIsOnlyLocked(tuple))
-				return HEAPTUPLE_INSERT_IN_PROGRESS;
-			/* inserted and then deleted by same xact */
-			if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple)))
-				return HEAPTUPLE_DELETE_IN_PROGRESS;
-			/* deleting subtransaction must have aborted */
-			return HEAPTUPLE_INSERT_IN_PROGRESS;
-		}
-		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			/*
-			 * It'd be possible to discern between INSERT/DELETE in progress
-			 * here by looking at xmax - but that doesn't seem beneficial for
-			 * the majority of callers and even detrimental for some. We'd
-			 * rather have callers look at/wait for xmin than xmax. It's
-			 * always correct to return INSERT_IN_PROGRESS because that's
-			 * what's happening from the view of other backends.
-			 */
-			return HEAPTUPLE_INSERT_IN_PROGRESS;
-		}
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/*
-			 * Not in Progress, Not Committed, so either Aborted or crashed
-			 */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return HEAPTUPLE_DEAD;
-		}
-
-		/*
-		 * At this point the xmin is known committed, but we might not have
-		 * been able to set the hint bit yet; so we can no longer Assert that
-		 * it's set.
-		 */
-	}
-
-	/*
-	 * Okay, the inserter committed, so it was good at some point.  Now what
-	 * about the deleting transaction?
-	 */
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)
-		return HEAPTUPLE_LIVE;
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-	{
-		/*
-		 * "Deleting" xact really only locked it, so the tuple is live in any
-		 * case.  However, we should make sure that either XMAX_COMMITTED or
-		 * XMAX_INVALID gets set once the xact is gone, to reduce the costs of
-		 * examining the tuple for future xacts.
-		 */
-		if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
-		{
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				/*
-				 * If it's a pre-pg_upgrade tuple, the multixact cannot
-				 * possibly be running; otherwise have to check.
-				 */
-				if (!HEAP_LOCKED_UPGRADED(tuple->t_infomask) &&
-					MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
-										 true))
-					return HEAPTUPLE_LIVE;
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
-			}
-			else
-			{
-				if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-					return HEAPTUPLE_LIVE;
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-			}
-		}
-
-		/*
-		 * We don't really care whether xmax did commit, abort or crash. We
-		 * know that xmax did lock the tuple, but it did not and will never
-		 * actually update it.
-		 */
-
-		return HEAPTUPLE_LIVE;
-	}
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
-		{
-			/* already checked above */
-			Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
-
-			xmax = HeapTupleGetUpdateXid(tuple);
-
-			/* not LOCKED_ONLY, so it has to have an xmax */
-			Assert(TransactionIdIsValid(xmax));
-
-			if (TransactionIdIsInProgress(xmax))
-				return HEAPTUPLE_DELETE_IN_PROGRESS;
-			else if (TransactionIdDidCommit(xmax))
-				/* there are still lockers around -- can't return DEAD here */
-				return HEAPTUPLE_RECENTLY_DEAD;
-			/* updating transaction aborted */
-			return HEAPTUPLE_LIVE;
-		}
-
-		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED));
-
-		xmax = HeapTupleGetUpdateXid(tuple);
-
-		/* not LOCKED_ONLY, so it has to have an xmax */
-		Assert(TransactionIdIsValid(xmax));
-
-		/* multi is not running -- updating xact cannot be */
-		Assert(!TransactionIdIsInProgress(xmax));
-		if (TransactionIdDidCommit(xmax))
-		{
-			if (!TransactionIdPrecedes(xmax, OldestXmin))
-				return HEAPTUPLE_RECENTLY_DEAD;
-			else
-				return HEAPTUPLE_DEAD;
-		}
-
-		/*
-		 * Not in Progress, Not Committed, so either Aborted or crashed.
-		 * Remove the Xmax.
-		 */
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
-		return HEAPTUPLE_LIVE;
-	}
-
-	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
-	{
-		if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-			return HEAPTUPLE_DELETE_IN_PROGRESS;
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-						HeapTupleHeaderGetRawXmax(tuple));
-		else
-		{
-			/*
-			 * Not in Progress, Not Committed, so either Aborted or crashed
-			 */
-			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-						InvalidTransactionId);
-			return HEAPTUPLE_LIVE;
-		}
-
-		/*
-		 * At this point the xmax is known committed, but we might not have
-		 * been able to set the hint bit yet; so we can no longer Assert that
-		 * it's set.
-		 */
-	}
-
-	/*
-	 * Deleter committed, but perhaps it was recent enough that some open
-	 * transactions could still see the tuple.
-	 */
-	if (!TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin))
-		return HEAPTUPLE_RECENTLY_DEAD;
-
-	/* Otherwise, it's dead and removable */
-	return HEAPTUPLE_DEAD;
-}
-
-
-/*
- * HeapTupleSatisfiesNonVacuumable
- *
- *	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.
- */
-bool
-HeapTupleSatisfiesNonVacuumable(HeapTuple 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'.
- */
-static bool
-TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num)
-{
-	return bsearch(&xid, xip, num,
-				   sizeof(TransactionId), xidComparator) != NULL;
-}
-
-/*
- * See the comments for HeapTupleSatisfiesMVCC for the semantics this function
- * obeys.
- *
- * Only usable on tuples from catalog tables!
- *
- * We don't need to support HEAP_MOVED_(IN|OFF) for now because we only support
- * reading catalog pages which couldn't have been created in an older version.
- *
- * We don't set any hint bits in here as it seems unlikely to be beneficial as
- * those should already be set by normal access and it seems to be too
- * 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,
-							   Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-	TransactionId xmin = HeapTupleHeaderGetXmin(tuple);
-	TransactionId xmax = HeapTupleHeaderGetRawXmax(tuple);
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	/* inserting transaction aborted */
-	if (HeapTupleHeaderXminInvalid(tuple))
-	{
-		Assert(!TransactionIdDidCommit(xmin));
-		return false;
-	}
-	/* check if it's one of our txids, toplevel is also in there */
-	else if (TransactionIdInArray(xmin, snapshot->subxip, snapshot->subxcnt))
-	{
-		bool		resolved;
-		CommandId	cmin = HeapTupleHeaderGetRawCommandId(tuple);
-		CommandId	cmax = InvalidCommandId;
-
-		/*
-		 * another transaction might have (tried to) delete this tuple or
-		 * cmin/cmax was stored in a combocid. So we need to lookup the actual
-		 * values externally.
-		 */
-		resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot,
-												 htup, buffer,
-												 &cmin, &cmax);
-
-		if (!resolved)
-			elog(ERROR, "could not resolve cmin/cmax of catalog tuple");
-
-		Assert(cmin != InvalidCommandId);
-
-		if (cmin >= snapshot->curcid)
-			return false;		/* inserted after scan started */
-		/* fall through */
-	}
-	/* committed before our xmin horizon. Do a normal visibility check. */
-	else if (TransactionIdPrecedes(xmin, snapshot->xmin))
-	{
-		Assert(!(HeapTupleHeaderXminCommitted(tuple) &&
-				 !TransactionIdDidCommit(xmin)));
-
-		/* check for hint bit first, consult clog afterwards */
-		if (!HeapTupleHeaderXminCommitted(tuple) &&
-			!TransactionIdDidCommit(xmin))
-			return false;
-		/* fall through */
-	}
-	/* beyond our xmax horizon, i.e. invisible */
-	else if (TransactionIdFollowsOrEquals(xmin, snapshot->xmax))
-	{
-		return false;
-	}
-	/* check if it's a committed transaction in [xmin, xmax) */
-	else if (TransactionIdInArray(xmin, snapshot->xip, snapshot->xcnt))
-	{
-		/* fall through */
-	}
-
-	/*
-	 * none of the above, i.e. between [xmin, xmax) but hasn't committed. I.e.
-	 * invisible.
-	 */
-	else
-	{
-		return false;
-	}
-
-	/* at this point we know xmin is visible, go on to check xmax */
-
-	/* xid invalid or aborted */
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)
-		return true;
-	/* locked tuples are always visible */
-	else if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-		return true;
-
-	/*
-	 * We can see multis here if we're looking at user tables or if somebody
-	 * SELECT ... FOR SHARE/UPDATE a system table.
-	 */
-	else if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		xmax = HeapTupleGetUpdateXid(tuple);
-	}
-
-	/* check if it's one of our txids, toplevel is also in there */
-	if (TransactionIdInArray(xmax, snapshot->subxip, snapshot->subxcnt))
-	{
-		bool		resolved;
-		CommandId	cmin;
-		CommandId	cmax = HeapTupleHeaderGetRawCommandId(tuple);
-
-		/* Lookup actual cmin/cmax values */
-		resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot,
-												 htup, buffer,
-												 &cmin, &cmax);
-
-		if (!resolved)
-			elog(ERROR, "could not resolve combocid to cmax");
-
-		Assert(cmax != InvalidCommandId);
-
-		if (cmax >= snapshot->curcid)
-			return true;		/* deleted after scan started */
-		else
-			return false;		/* deleted before scan started */
-	}
-	/* below xmin horizon, normal transaction state is valid */
-	else if (TransactionIdPrecedes(xmax, snapshot->xmin))
-	{
-		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED &&
-				 !TransactionIdDidCommit(xmax)));
-
-		/* check hint bit first */
-		if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
-			return false;
-
-		/* check clog */
-		return !TransactionIdDidCommit(xmax);
-	}
-	/* above xmax horizon, we cannot possibly see the deleting transaction */
-	else if (TransactionIdFollowsOrEquals(xmax, snapshot->xmax))
-		return true;
-	/* xmax is between [xmin, xmax), check known committed array */
-	else if (TransactionIdInArray(xmax, snapshot->xip, snapshot->xcnt))
-		return false;
-	/* xmax is between [xmin, xmax), but known not to have committed yet */
-	else
-		return true;
-}
diff --git a/src/include/access/heapam_common.h b/src/include/access/heapam_common.h
new file mode 100644
index 0000000000..ff63cf3615
--- /dev/null
+++ b/src/include/access/heapam_common.h
@@ -0,0 +1,96 @@
+/*-------------------------------------------------------------------------
+ *
+ * heapam_shared.h
+ *	  POSTGRES heap access method definitions shared across
+ *	  server and heapam methods.
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/heapam_shared.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef HEAPAM_SHARED_H
+#define HEAPAM_SHARED_H
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/sdir.h"
+#include "access/skey.h"
+#include "access/transam.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "nodes/lockoptions.h"
+#include "nodes/primnodes.h"
+#include "storage/bufpage.h"
+#include "storage/bufmgr.h"
+#include "storage/lockdefs.h"
+#include "storage/lmgr.h"
+#include "utils/relcache.h"
+#include "utils/snapshot.h"
+
+
+/* in heap/heapam_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);
+
+/*
+ * 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							/* HEAPAM_H */
diff --git a/src/include/access/htup.h b/src/include/access/htup.h
index 6459435c78..a5268242d7 100644
--- a/src/include/access/htup.h
+++ b/src/include/access/htup.h
@@ -28,15 +28,16 @@ typedef MinimalTupleData *MinimalTuple;
 
 typedef enum tuple_visibility_type
 {
-	MVCC_VISIBILITY = 0, 		/* HeapTupleSatisfiesMVCC */
-	SELF_VISIBILITY,				/* HeapTupleSatisfiesSelf */
+	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;
+}			tuple_visibility_type;
 
 /* Result codes for HeapTupleSatisfiesVacuum */
 typedef enum
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 98b63fc5ba..b1c94de683 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -14,13 +14,13 @@
 #ifndef BUFMGR_H
 #define BUFMGR_H
 
+#include "access/storageamapi.h"
 #include "storage/block.h"
 #include "storage/buf.h"
 #include "storage/bufpage.h"
 #include "storage/relfilenode.h"
 #include "utils/relcache.h"
 #include "utils/snapmgr.h"
-#include "utils/tqual.h"
 
 typedef void *Block;
 
@@ -268,8 +268,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..5752ee4731 100644
--- a/src/include/utils/snapshot.h
+++ b/src/include/utils/snapshot.h
@@ -52,7 +52,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 4fda7f8384..3deeb71970 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,64 +43,25 @@ 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))
+#define HeapTupleSatisfiesVisibility(method, tuple, snapshot, buffer) \
+	(((method)->snapshot_satisfies) (tuple, snapshot, buffer))
 
-
-/* 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);
-
-/*
- * To avoid leaking too much knowledge about reorderbuffer implementation
- * details this is implemented in reorderbuffer.c not tqual.c.
- */
-struct HTAB;
-extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data,
-							  Snapshot snapshot,
-							  HeapTuple htup,
-							  Buffer buffer,
-							  CommandId *cmin, CommandId *cmax);
-
 /*
  * We don't provide a static SnapshotDirty variable because it would be
  * non-reentrant.  Instead, users of that snapshot type should declare a
  * 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))
 
 /*
@@ -107,8 +69,19 @@ 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))
 
+/*
+ * To avoid leaking too much knowledge about reorderbuffer implementation
+ * details this is implemented in reorderbuffer.c not tqual.c.
+ */
+struct HTAB;
+extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data,
+							  Snapshot snapshot,
+							  HeapTuple htup,
+							  Buffer buffer,
+							  CommandId *cmin, CommandId *cmax);
+
 #endif							/* TQUAL_H */
-- 
2.14.2.windows.1

0005-slot-hooks-are-added-to-storage-AM.patchapplication/octet-stream; name=0005-slot-hooks-are-added-to-storage-AM.patchDownload
From 8dd4bf48a550f9f3372888d9215114e79859deea Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Fri, 27 Oct 2017 16:02:46 +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_common.c  | 404 +++++++++++++++++++++++++++++++
 src/backend/access/heap/heapam_storage.c |   5 +-
 src/backend/commands/copy.c              |   8 +-
 src/backend/commands/createas.c          |   2 +-
 src/backend/commands/matview.c           |   2 +-
 src/backend/commands/trigger.c           |  25 +-
 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/heapam_common.h       |   2 +
 src/include/access/htup_details.h        |  15 +-
 src/include/executor/tuptable.h          |  54 +++--
 17 files changed, 660 insertions(+), 585 deletions(-)

diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 13ee528e26..aabb4f0944 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_common.c b/src/backend/access/heap/heapam_common.c
index 4cc3eeff0c..6ebe0b290c 100644
--- a/src/backend/access/heap/heapam_common.c
+++ b/src/backend/access/heap/heapam_common.c
@@ -100,6 +100,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/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index f7e11d4946..a874e243c9 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -1108,7 +1108,6 @@ HeapTupleSatisfiesMVCC(StorageTuple stup, Snapshot snapshot,
 	return false;
 }
 
-
 /*
  * HeapTupleSatisfiesVacuum
  *
@@ -1695,8 +1694,10 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
 
 	amroutine->snapshot_satisfies = HeapTupleSatisfies;
-
 	amroutine->snapshot_satisfiesUpdate = HeapTupleSatisfiesUpdate;
 	amroutine->snapshot_satisfiesVacuum = HeapTupleSatisfiesVacuum;
+
+	amroutine->slot_storageam = heapam_storage_slot_handler;
+
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 8006df32a8..b56fae4e91 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -167,7 +167,7 @@ typedef struct CopyStateData
 	PartitionDispatch *partition_dispatch_info;
 	int			num_dispatch;	/* Number of entries in the above array */
 	int			num_partitions; /* Number of members in the following arrays */
-	ResultRelInfo **partitions;	/* Per partition result relation pointers */
+	ResultRelInfo **partitions; /* Per partition result relation pointers */
 	TupleConversionMap **partition_tupconv_maps;
 	TupleTableSlot *partition_tuple_slot;
 	TransitionCaptureState *transition_capture;
@@ -359,7 +359,7 @@ SendCopyBegin(CopyState cstate)
 		pq_sendbyte(&buf, format);	/* overall format */
 		pq_sendint16(&buf, natts);
 		for (i = 0; i < natts; i++)
-			pq_sendint16(&buf, format);	/* per-column formats */
+			pq_sendint16(&buf, format); /* per-column formats */
 		pq_endmessage(&buf);
 		cstate->copy_dest = COPY_NEW_FE;
 	}
@@ -392,7 +392,7 @@ ReceiveCopyBegin(CopyState cstate)
 		pq_sendbyte(&buf, format);	/* overall format */
 		pq_sendint16(&buf, natts);
 		for (i = 0; i < natts; i++)
-			pq_sendint16(&buf, format);	/* per-column formats */
+			pq_sendint16(&buf, format); /* per-column formats */
 		pq_endmessage(&buf);
 		cstate->copy_dest = COPY_NEW_FE;
 		cstate->fe_msgbuf = makeStringInfo();
@@ -2705,7 +2705,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 8d0345cd64..005bdbd023 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;
@@ -3608,9 +3608,9 @@ static void AfterTriggerExecute(AfterTriggerEvent event,
 					MemoryContext per_tuple_context,
 					TupleTableSlot *trig_tuple_slot1,
 					TupleTableSlot *trig_tuple_slot2);
-static AfterTriggersTableData *GetAfterTriggersTableData(Oid relid,
-						  CmdType cmdType);
-static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs);
+static AfterTriggersTableData * GetAfterTriggersTableData(Oid relid,
+														  CmdType cmdType);
+static void AfterTriggerFreeQuery(AfterTriggersQueryData * qs);
 static SetConstraintState SetConstraintStateCreate(int numalloc);
 static SetConstraintState SetConstraintStateCopy(SetConstraintState state);
 static SetConstraintState SetConstraintStateAddItem(SetConstraintState state,
@@ -3882,7 +3882,7 @@ afterTriggerRestoreEventList(AfterTriggerEventList *events,
  * ----------
  */
 static void
-afterTriggerDeleteHeadEventChunk(AfterTriggersQueryData *qs)
+afterTriggerDeleteHeadEventChunk(AfterTriggersQueryData * qs)
 {
 	AfterTriggerEventChunk *target = qs->events.head;
 	ListCell   *lc;
@@ -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;
@@ -4634,7 +4633,7 @@ AfterTriggerEndQuery(EState *estate)
  *	and then called again for the same query level.
  */
 static void
-AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
+AfterTriggerFreeQuery(AfterTriggersQueryData * qs)
 {
 	Tuplestorestate *ts;
 	List	   *tables;
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index a0f537b706..a7d20bc391 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 fb538c0297..07640a9992 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..93aeb3eb6e 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -81,6 +81,7 @@
  */
 #include "postgres.h"
 
+#include "access/heapam_common.h"
 #include "access/htup_details.h"
 #include "access/tuptoaster.h"
 #include "funcapi.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 20892d6d5f..02f6c816aa 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 dbc242c699..ec5c543bdd 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -171,7 +171,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;
@@ -271,7 +271,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
@@ -406,7 +406,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -419,7 +419,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		newId = InvalidOid;
 	}
@@ -437,7 +437,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
@@ -746,7 +746,7 @@ ExecDelete(ModifyTableState *mtstate,
 		 */
 		if (slot->tts_isempty)
 			ExecStoreAllNullTuple(slot);
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
 	}
 	else
@@ -897,7 +897,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))
@@ -958,7 +958,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
@@ -977,7 +977,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW UPDATE Triggers */
@@ -991,7 +991,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
@@ -1007,7 +1007,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
@@ -1123,7 +1123,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 e9a5d5a1a5..9a47276274 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 bc6d8246a7..4344a299e4 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -724,9 +724,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/heapam_common.h b/src/include/access/heapam_common.h
index ff63cf3615..feb35c5024 100644
--- a/src/include/access/heapam_common.h
+++ b/src/include/access/heapam_common.h
@@ -40,6 +40,8 @@ extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
 					 uint16 infomask, TransactionId xid);
 extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
 extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin);
+typedef struct StorageSlotAmRoutine StorageSlotAmRoutine;
+extern StorageSlotAmRoutine * heapam_storage_slot_handler(void);
 
 /*
  * SetHintBits()
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/executor/tuptable.h b/src/include/executor/tuptable.h
index 55f4cce4ee..a730f265dd 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.14.2.windows.1

0006-Tuple-Insert-API-is-added-to-Storage-AM.patchapplication/octet-stream; name=0006-Tuple-Insert-API-is-added-to-Storage-AM.patchDownload
From 3406fd50f1bd8a8093722465083fe8ed10628d7b Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Sat, 28 Oct 2017 15:36:24 +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/Makefile         |    2 +-
 src/backend/access/heap/heapam.c         | 2875 ++++--------------------------
 src/backend/access/heap/heapam_common.c  |    2 +
 src/backend/access/heap/heapam_storage.c | 2204 ++++++++++++++++++++++-
 src/backend/access/heap/rewriteheap.c    |    5 +-
 src/backend/access/heap/storageam.c      |  281 +++
 src/backend/access/heap/tuptoaster.c     |    9 +-
 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           |   49 +-
 src/backend/executor/execIndexing.c      |    2 +-
 src/backend/executor/execMain.c          |  130 +-
 src/backend/executor/execReplication.c   |   72 +-
 src/backend/executor/execTuples.c        |    1 +
 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              |   26 +-
 src/include/access/heapam_common.h       |  127 ++
 src/include/access/htup_details.h        |    1 +
 src/include/access/storageam.h           |   86 +
 src/include/access/storageamapi.h        |   35 +-
 src/include/commands/trigger.h           |    2 +-
 src/include/executor/executor.h          |   15 +-
 src/include/executor/tuptable.h          |    1 +
 src/include/nodes/execnodes.h            |    6 +-
 src/include/storage/bufmgr.h             |    1 -
 31 files changed, 3468 insertions(+), 2898 deletions(-)
 create mode 100644 src/backend/access/heap/storageam.c
 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 aabb4f0944..a66eed8d3b 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/Makefile b/src/backend/access/heap/Makefile
index e6bc18e5ea..162736ff15 100644
--- a/src/backend/access/heap/Makefile
+++ b/src/backend/access/heap/Makefile
@@ -13,7 +13,7 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = heapam.o heapam_common.o heapam_storage.o hio.o \
-	pruneheap.o rewriteheap.o storageamapi.o \
+	pruneheap.o rewriteheap.o storageam.o storageamapi.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 0ee8c53282..327ad30b2d 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -94,8 +94,6 @@ static HeapScanDesc heap_beginscan_internal(Relation relation,
 						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,
-					TransactionId xid, CommandId cid, int options);
 static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 				Buffer newbuf, HeapTuple oldtup,
 				HeapTuple newtup, HeapTuple old_key_tup,
@@ -103,108 +101,17 @@ static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 static Bitmapset *HeapDetermineModifiedColumns(Relation relation,
 							 Bitmapset *interesting_cols,
 							 HeapTuple oldtup, HeapTuple newtup);
-static bool heap_acquire_tuplock(Relation relation, ItemPointer tid,
-					 LockTupleMode mode, LockWaitPolicy wait_policy,
-					 bool *have_tuple_lock);
-static void compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
-						  uint16 old_infomask2, TransactionId add_to_xmax,
-						  LockTupleMode mode, bool is_update,
-						  TransactionId *result_xmax, uint16 *result_infomask,
-						  uint16 *result_infomask2);
-static HTSU_Result heap_lock_updated_tuple(Relation rel, HeapTuple tuple,
-						ItemPointer ctid, TransactionId xid,
-						LockTupleMode mode);
 static void GetMultiXactIdHintBits(MultiXactId multi, uint16 *new_infomask,
 					   uint16 *new_infomask2);
 static TransactionId MultiXactIdGetUpdateXid(TransactionId xmax,
 						uint16 t_infomask);
-static bool DoesMultiXactIdConflict(MultiXactId multi, uint16 infomask,
-						LockTupleMode lockmode);
-static void MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
-				Relation rel, ItemPointer ctid, XLTW_Oper oper,
-				int *remaining);
-static bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
-						   uint16 infomask, Relation rel, int *remaining);
-static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
+static bool Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
+				   uint16 infomask, bool nowait,
+				   Relation rel, ItemPointer ctid, XLTW_Oper oper,
+				   int *remaining);
 static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_modified,
 					   bool *copy);
 
-
-/*
- * Each tuple lock mode has a corresponding heavyweight lock, and one or two
- * corresponding MultiXactStatuses (one to merely lock tuples, another one to
- * update them).  This table (and the macros below) helps us determine the
- * heavyweight lock mode and MultiXactStatus values to use for any particular
- * tuple lock strength.
- *
- * Don't look at lockstatus/updstatus directly!  Use get_mxact_status_for_lock
- * instead.
- */
-static const struct
-{
-	LOCKMODE	hwlock;
-	int			lockstatus;
-	int			updstatus;
-}
-
-			tupleLockExtraInfo[MaxLockTupleMode + 1] =
-{
-	{							/* LockTupleKeyShare */
-		AccessShareLock,
-		MultiXactStatusForKeyShare,
-		-1						/* KeyShare does not allow updating tuples */
-	},
-	{							/* LockTupleShare */
-		RowShareLock,
-		MultiXactStatusForShare,
-		-1						/* Share does not allow updating tuples */
-	},
-	{							/* LockTupleNoKeyExclusive */
-		ExclusiveLock,
-		MultiXactStatusForNoKeyUpdate,
-		MultiXactStatusNoKeyUpdate
-	},
-	{							/* LockTupleExclusive */
-		AccessExclusiveLock,
-		MultiXactStatusForUpdate,
-		MultiXactStatusUpdate
-	}
-};
-
-/* Get the LOCKMODE for a given MultiXactStatus */
-#define LOCKMODE_from_mxstatus(status) \
-			(tupleLockExtraInfo[TUPLOCK_from_mxstatus((status))].hwlock)
-
-/*
- * Acquire heavyweight locks on tuples, using a LockTupleMode strength value.
- * This is more readable than having every caller translate it to lock.h's
- * LOCKMODE.
- */
-#define LockTupleTuplock(rel, tup, mode) \
-	LockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
-#define UnlockTupleTuplock(rel, tup, mode) \
-	UnlockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
-#define ConditionalLockTupleTuplock(rel, tup, mode) \
-	ConditionalLockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
-
-/*
- * This table maps tuple lock strength values for each particular
- * MultiXactStatus value.
- */
-static const int MultiXactStatusLock[MaxMultiXactStatus + 1] =
-{
-	LockTupleKeyShare,			/* ForKeyShare */
-	LockTupleShare,				/* ForShare */
-	LockTupleNoKeyExclusive,	/* ForNoKeyUpdate */
-	LockTupleExclusive,			/* ForUpdate */
-	LockTupleNoKeyExclusive,	/* NoKeyUpdate */
-	LockTupleExclusive			/* Update */
-};
-
-/* Get the LockTupleMode for a given MultiXactStatus */
-#define TUPLOCK_from_mxstatus(status) \
-			(MultiXactStatusLock[(status)])
-
 /* ----------------------------------------------------------------
  *						 heap support routines
  * ----------------------------------------------------------------
@@ -1843,158 +1750,6 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 	return heap_copytuple(&(scan->rs_ctup));
 }
 
-/*
- *	heap_fetch		- retrieve tuple with given tid
- *
- * On entry, tuple->t_self is the TID to fetch.  We pin the buffer holding
- * the tuple, fill in the remaining fields of *tuple, and check the tuple
- * against the specified snapshot.
- *
- * If successful (tuple found and passes snapshot time qual), then *userbuf
- * is set to the buffer holding the tuple and TRUE is returned.  The caller
- * must unpin the buffer when done with the tuple.
- *
- * If the tuple is not found (ie, item number references a deleted slot),
- * then tuple->t_data is set to NULL and FALSE is returned.
- *
- * If the tuple is found but fails the time qual check, then FALSE is returned
- * but tuple->t_data is left pointing to the tuple.
- *
- * keep_buf determines what is done with the buffer in the FALSE-result cases.
- * When the caller specifies keep_buf = true, we retain the pin on the buffer
- * and return it in *userbuf (so the caller must eventually unpin it); when
- * keep_buf = false, the pin is released and *userbuf is set to InvalidBuffer.
- *
- * stats_relation is the relation to charge the heap_fetch operation against
- * for statistical purposes.  (This could be the heap rel itself, an
- * associated index, or NULL to not count the fetch at all.)
- *
- * heap_fetch does not follow HOT chains: only the exact TID requested will
- * be fetched.
- *
- * It is somewhat inconsistent that we ereport() on invalid block number but
- * return false on invalid item number.  There are a couple of reasons though.
- * One is that the caller can relatively easily check the block number for
- * validity, but cannot check the item number without reading the page
- * himself.  Another is that when we are following a t_ctid link, we can be
- * reasonably confident that the page number is valid (since VACUUM shouldn't
- * truncate off the destination page without having killed the referencing
- * tuple first), but the item number might well not be good.
- */
-bool
-heap_fetch(Relation relation,
-		   Snapshot snapshot,
-		   HeapTuple tuple,
-		   Buffer *userbuf,
-		   bool keep_buf,
-		   Relation stats_relation)
-{
-	ItemPointer tid = &(tuple->t_self);
-	ItemId		lp;
-	Buffer		buffer;
-	Page		page;
-	OffsetNumber offnum;
-	bool		valid;
-
-	/*
-	 * Fetch and pin the appropriate page of the relation.
-	 */
-	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
-
-	/*
-	 * Need share lock on buffer to examine tuple commit status.
-	 */
-	LockBuffer(buffer, BUFFER_LOCK_SHARE);
-	page = BufferGetPage(buffer);
-	TestForOldSnapshot(snapshot, relation, page);
-
-	/*
-	 * We'd better check for out-of-range offnum in case of VACUUM since the
-	 * TID was obtained.
-	 */
-	offnum = ItemPointerGetOffsetNumber(tid);
-	if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
-	{
-		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-		if (keep_buf)
-			*userbuf = buffer;
-		else
-		{
-			ReleaseBuffer(buffer);
-			*userbuf = InvalidBuffer;
-		}
-		tuple->t_data = NULL;
-		return false;
-	}
-
-	/*
-	 * get the item line pointer corresponding to the requested tid
-	 */
-	lp = PageGetItemId(page, offnum);
-
-	/*
-	 * Must check for deleted tuple.
-	 */
-	if (!ItemIdIsNormal(lp))
-	{
-		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-		if (keep_buf)
-			*userbuf = buffer;
-		else
-		{
-			ReleaseBuffer(buffer);
-			*userbuf = InvalidBuffer;
-		}
-		tuple->t_data = NULL;
-		return false;
-	}
-
-	/*
-	 * fill in *tuple fields
-	 */
-	tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	tuple->t_len = ItemIdGetLength(lp);
-	tuple->t_tableOid = RelationGetRelid(relation);
-
-	/*
-	 * check time qualification of tuple, then release lock
-	 */
-	valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, tuple, snapshot, buffer);
-
-	if (valid)
-		PredicateLockTuple(relation, tuple, snapshot);
-
-	CheckForSerializableConflictOut(valid, relation, tuple, buffer, snapshot);
-
-	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-
-	if (valid)
-	{
-		/*
-		 * All checks passed, so return the tuple as valid. Caller is now
-		 * responsible for releasing the buffer.
-		 */
-		*userbuf = buffer;
-
-		/* Count the successful fetch against appropriate rel, if any */
-		if (stats_relation != NULL)
-			pgstat_count_heap_fetch(stats_relation);
-
-		return true;
-	}
-
-	/* Tuple failed time qual, but maybe caller wants to see it anyway. */
-	if (keep_buf)
-		*userbuf = buffer;
-	else
-	{
-		ReleaseBuffer(buffer);
-		*userbuf = InvalidBuffer;
-	}
-
-	return false;
-}
-
 /*
  *	heap_hot_search_buffer	- search HOT chain for tuple satisfying snapshot
  *
@@ -2177,130 +1932,6 @@ heap_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot,
 	return result;
 }
 
-/*
- *	heap_get_latest_tid -  get the latest tid of a specified tuple
- *
- * Actually, this gets the latest version that is visible according to
- * the passed snapshot.  You can pass SnapshotDirty to get the very latest,
- * possibly uncommitted version.
- *
- * *tid is both an input and an output parameter: it is updated to
- * show the latest version of the row.  Note that it will not be changed
- * if no version of the row passes the snapshot test.
- */
-void
-heap_get_latest_tid(Relation relation,
-					Snapshot snapshot,
-					ItemPointer tid)
-{
-	BlockNumber blk;
-	ItemPointerData ctid;
-	TransactionId priorXmax;
-
-	/* this is to avoid Assert failures on bad input */
-	if (!ItemPointerIsValid(tid))
-		return;
-
-	/*
-	 * Since this can be called with user-supplied TID, don't trust the input
-	 * too much.  (RelationGetNumberOfBlocks is an expensive check, so we
-	 * don't check t_ctid links again this way.  Note that it would not do to
-	 * call it just once and save the result, either.)
-	 */
-	blk = ItemPointerGetBlockNumber(tid);
-	if (blk >= RelationGetNumberOfBlocks(relation))
-		elog(ERROR, "block number %u is out of range for relation \"%s\"",
-			 blk, RelationGetRelationName(relation));
-
-	/*
-	 * Loop to chase down t_ctid links.  At top of loop, ctid is the tuple we
-	 * need to examine, and *tid is the TID we will return if ctid turns out
-	 * to be bogus.
-	 *
-	 * Note that we will loop until we reach the end of the t_ctid chain.
-	 * Depending on the snapshot passed, there might be at most one visible
-	 * version of the row, but we don't try to optimize for that.
-	 */
-	ctid = *tid;
-	priorXmax = InvalidTransactionId;	/* cannot check first XMIN */
-	for (;;)
-	{
-		Buffer		buffer;
-		Page		page;
-		OffsetNumber offnum;
-		ItemId		lp;
-		HeapTupleData tp;
-		bool		valid;
-
-		/*
-		 * Read, pin, and lock the page.
-		 */
-		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&ctid));
-		LockBuffer(buffer, BUFFER_LOCK_SHARE);
-		page = BufferGetPage(buffer);
-		TestForOldSnapshot(snapshot, relation, page);
-
-		/*
-		 * Check for bogus item number.  This is not treated as an error
-		 * condition because it can happen while following a t_ctid link. We
-		 * just assume that the prior tid is OK and return it unchanged.
-		 */
-		offnum = ItemPointerGetOffsetNumber(&ctid);
-		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-		lp = PageGetItemId(page, offnum);
-		if (!ItemIdIsNormal(lp))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-
-		/* OK to access the tuple */
-		tp.t_self = ctid;
-		tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-		tp.t_len = ItemIdGetLength(lp);
-		tp.t_tableOid = RelationGetRelid(relation);
-
-		/*
-		 * After following a t_ctid link, we might arrive at an unrelated
-		 * tuple.  Check for XMIN match.
-		 */
-		if (TransactionIdIsValid(priorXmax) &&
-			!HeapTupleUpdateXmaxMatchesXmin(priorXmax, tp.t_data))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-
-		/*
-		 * Check time qualification of tuple; if visible, set it as the new
-		 * result candidate.
-		 */
-		valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, &tp, snapshot, buffer);
-		CheckForSerializableConflictOut(valid, relation, &tp, buffer, snapshot);
-		if (valid)
-			*tid = ctid;
-
-		/*
-		 * If there's a valid t_ctid link, follow it, else we're done.
-		 */
-		if ((tp.t_data->t_infomask & HEAP_XMAX_INVALID) ||
-			HeapTupleHeaderIsOnlyLocked(tp.t_data) ||
-			ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-
-		ctid = tp.t_data->t_ctid;
-		priorXmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
-		UnlockReleaseBuffer(buffer);
-	}							/* end of loop */
-}
-
 /*
  * HeapTupleUpdateXmaxMatchesXmin - verify update chain xmax/xmin lineage
  *
@@ -2362,7 +1993,7 @@ HeapTupleUpdateXmaxMatchesXmin(TransactionId xmax, HeapTupleHeader htup)
  *
  * Note this is not allowed for tuples whose xmax is a multixact.
  */
-static void
+void
 UpdateXmaxHintBits(HeapTupleHeader tuple, Buffer buffer, TransactionId xid)
 {
 	Assert(TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple), xid));
@@ -2418,7 +2049,6 @@ ReleaseBulkInsertStatePin(BulkInsertState bistate)
 	bistate->current_buf = InvalidBuffer;
 }
 
-
 /*
  *	heap_insert		- insert tuple into a heap
  *
@@ -2645,7 +2275,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
  * tuple if not. Note that in any case, the header fields are also set in
  * the original tuple.
  */
-static HeapTuple
+HeapTuple
 heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
 					CommandId cid, int options)
 {
@@ -2715,417 +2345,115 @@ heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
 }
 
 /*
- *	heap_multi_insert	- insert multiple tuple into a heap
+ *	simple_heap_insert - insert a tuple
+ *
+ * Currently, this routine differs from heap_insert only in supplying
+ * a default command ID and not allowing access to the speedup options.
  *
- * This is like heap_insert(), but inserts multiple tuples in one operation.
- * That's faster than calling heap_insert() in a loop, because when multiple
- * tuples can be inserted on a single page, we can write just a single WAL
- * record covering all of them, and only need to lock/unlock the page once.
+ * This should be used rather than using heap_insert directly in most places
+ * where we are modifying system catalogs.
+ */
+Oid
+simple_heap_insert(Relation relation, HeapTuple tup)
+{
+	return heap_insert(relation, tup, GetCurrentCommandId(true), 0, NULL);
+}
+
+/*
+ * Given infomask/infomask2, compute the bits that must be saved in the
+ * "infobits" field of xl_heap_delete, xl_heap_update, xl_heap_lock,
+ * xl_heap_lock_updated WAL records.
  *
- * Note: this leaks memory into the current memory context. You can create a
- * temporary context before calling this, if that's a problem.
+ * See fix_infomask_from_infobits.
  */
-void
-heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
-				  CommandId cid, int options, BulkInsertState bistate)
+uint8
+compute_infobits(uint16 infomask, uint16 infomask2)
+{
+	return
+		((infomask & HEAP_XMAX_IS_MULTI) != 0 ? XLHL_XMAX_IS_MULTI : 0) |
+		((infomask & HEAP_XMAX_LOCK_ONLY) != 0 ? XLHL_XMAX_LOCK_ONLY : 0) |
+		((infomask & HEAP_XMAX_EXCL_LOCK) != 0 ? XLHL_XMAX_EXCL_LOCK : 0) |
+	/* note we ignore HEAP_XMAX_SHR_LOCK here */
+		((infomask & HEAP_XMAX_KEYSHR_LOCK) != 0 ? XLHL_XMAX_KEYSHR_LOCK : 0) |
+		((infomask2 & HEAP_KEYS_UPDATED) != 0 ?
+		 XLHL_KEYS_UPDATED : 0);
+}
+
+
+
+/*
+ *	heap_delete - delete a tuple
+ *
+ * NB: do not call this directly unless you are prepared to deal with
+ * concurrent-update conditions.  Use simple_heap_delete instead.
+ *
+ *	relation - table to be modified (caller must hold suitable lock)
+ *	tid - TID of tuple to be deleted
+ *	cid - delete command ID (used for visibility test, and stored into
+ *		cmax if successful)
+ *	crosscheck - if not InvalidSnapshot, also check tuple against this
+ *	wait - true if should wait for any conflicting update to commit/abort
+ *	hufd - output parameter, filled in failure cases (see below)
+ *
+ * Normal, successful return value is HeapTupleMayBeUpdated, which
+ * actually means we did delete it.  Failure return codes are
+ * HeapTupleSelfUpdated, HeapTupleUpdated, or HeapTupleBeingUpdated
+ * (the last only possible if wait == false).
+ *
+ * In the failure cases, 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.
+ */
+HTSU_Result
+heap_delete(Relation relation, ItemPointer tid,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			HeapUpdateFailureData *hufd)
 {
+	HTSU_Result result;
 	TransactionId xid = GetCurrentTransactionId();
-	HeapTuple  *heaptuples;
-	int			i;
-	int			ndone;
-	char	   *scratch = NULL;
+	ItemId		lp;
+	HeapTupleData tp;
 	Page		page;
-	bool		needwal;
-	Size		saveFreeSpace;
-	bool		need_tuple_data = RelationIsLogicallyLogged(relation);
-	bool		need_cids = RelationIsAccessibleInLogicalDecoding(relation);
+	BlockNumber block;
+	Buffer		buffer;
+	Buffer		vmbuffer = InvalidBuffer;
+	TransactionId new_xmax;
+	uint16		new_infomask,
+				new_infomask2;
+	bool		have_tuple_lock = false;
+	bool		iscombo;
+	bool		all_visible_cleared = false;
+	HeapTuple	old_key_tuple = NULL;	/* replica identity of the tuple */
+	bool		old_key_copied = false;
+
+	Assert(ItemPointerIsValid(tid));
 
-	needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation);
-	saveFreeSpace = RelationGetTargetPageFreeSpace(relation,
-												   HEAP_DEFAULT_FILLFACTOR);
+	/*
+	 * Forbid this during a parallel operation, lest it allocate a combocid.
+	 * Other workers might need that combocid for visibility checks, and we
+	 * have no provision for broadcasting it to them.
+	 */
+	if (IsInParallelMode())
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+				 errmsg("cannot delete tuples during a parallel operation")));
 
-	/* Toast and set header data in all the tuples */
-	heaptuples = palloc(ntuples * sizeof(HeapTuple));
-	for (i = 0; i < ntuples; i++)
-		heaptuples[i] = heap_prepare_insert(relation, tuples[i],
-											xid, cid, options);
+	block = ItemPointerGetBlockNumber(tid);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
 
 	/*
-	 * Allocate some memory to use for constructing the WAL record. Using
-	 * palloc() within a critical section is not safe, so we allocate this
-	 * beforehand.
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
 	 */
-	if (needwal)
-		scratch = palloc(BLCKSZ);
-
-	/*
-	 * We're about to do the actual inserts -- but check for conflict first,
-	 * to minimize the possibility of having to roll back work we've just
-	 * done.
-	 *
-	 * A check here does not definitively prevent a serialization anomaly;
-	 * that check MUST be done at least past the point of acquiring an
-	 * exclusive buffer content lock on every buffer that will be affected,
-	 * and MAY be done after all inserts are reflected in the buffers and
-	 * those locks are released; otherwise there race condition.  Since
-	 * multiple buffers can be locked and unlocked in the loop below, and it
-	 * would not be feasible to identify and lock all of those buffers before
-	 * the loop, we must do a final check at the end.
-	 *
-	 * The check here could be omitted with no loss of correctness; it is
-	 * present strictly as an optimization.
-	 *
-	 * For heap inserts, we only need to check for table-level SSI locks. Our
-	 * new tuples can't possibly conflict with existing tuple locks, and heap
-	 * page locks are only consolidated versions of tuple locks; they do not
-	 * lock "gaps" as index page locks do.  So we don't need to specify a
-	 * buffer when making the call, which makes for a faster check.
-	 */
-	CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
-
-	ndone = 0;
-	while (ndone < ntuples)
-	{
-		Buffer		buffer;
-		Buffer		vmbuffer = InvalidBuffer;
-		bool		all_visible_cleared = false;
-		int			nthispage;
-
-		CHECK_FOR_INTERRUPTS();
-
-		/*
-		 * Find buffer where at least the next tuple will fit.  If the page is
-		 * all-visible, this will also pin the requisite visibility map page.
-		 */
-		buffer = RelationGetBufferForTuple(relation, heaptuples[ndone]->t_len,
-										   InvalidBuffer, options, bistate,
-										   &vmbuffer, NULL);
-		page = BufferGetPage(buffer);
-
-		/* NO EREPORT(ERROR) from here till changes are logged */
-		START_CRIT_SECTION();
-
-		/*
-		 * RelationGetBufferForTuple has ensured that the first tuple fits.
-		 * Put that on the page, and then as many other tuples as fit.
-		 */
-		RelationPutHeapTuple(relation, buffer, heaptuples[ndone], false);
-		for (nthispage = 1; ndone + nthispage < ntuples; nthispage++)
-		{
-			HeapTuple	heaptup = heaptuples[ndone + nthispage];
-
-			if (PageGetHeapFreeSpace(page) < MAXALIGN(heaptup->t_len) + saveFreeSpace)
-				break;
-
-			RelationPutHeapTuple(relation, buffer, heaptup, false);
-
-			/*
-			 * We don't use heap_multi_insert for catalog tuples yet, but
-			 * better be prepared...
-			 */
-			if (needwal && need_cids)
-				log_heap_new_cid(relation, heaptup);
-		}
-
-		if (PageIsAllVisible(page))
-		{
-			all_visible_cleared = true;
-			PageClearAllVisible(page);
-			visibilitymap_clear(relation,
-								BufferGetBlockNumber(buffer),
-								vmbuffer, VISIBILITYMAP_VALID_BITS);
-		}
-
-		/*
-		 * XXX Should we set PageSetPrunable on this page ? See heap_insert()
-		 */
-
-		MarkBufferDirty(buffer);
-
-		/* XLOG stuff */
-		if (needwal)
-		{
-			XLogRecPtr	recptr;
-			xl_heap_multi_insert *xlrec;
-			uint8		info = XLOG_HEAP2_MULTI_INSERT;
-			char	   *tupledata;
-			int			totaldatalen;
-			char	   *scratchptr = scratch;
-			bool		init;
-			int			bufflags = 0;
-
-			/*
-			 * If the page was previously empty, we can reinit the page
-			 * instead of restoring the whole thing.
-			 */
-			init = (ItemPointerGetOffsetNumber(&(heaptuples[ndone]->t_self)) == FirstOffsetNumber &&
-					PageGetMaxOffsetNumber(page) == FirstOffsetNumber + nthispage - 1);
-
-			/* allocate xl_heap_multi_insert struct from the scratch area */
-			xlrec = (xl_heap_multi_insert *) scratchptr;
-			scratchptr += SizeOfHeapMultiInsert;
-
-			/*
-			 * Allocate offsets array. Unless we're reinitializing the page,
-			 * in that case the tuples are stored in order starting at
-			 * FirstOffsetNumber and we don't need to store the offsets
-			 * explicitly.
-			 */
-			if (!init)
-				scratchptr += nthispage * sizeof(OffsetNumber);
-
-			/* the rest of the scratch space is used for tuple data */
-			tupledata = scratchptr;
-
-			xlrec->flags = all_visible_cleared ? XLH_INSERT_ALL_VISIBLE_CLEARED : 0;
-			xlrec->ntuples = nthispage;
-
-			/*
-			 * Write out an xl_multi_insert_tuple and the tuple data itself
-			 * for each tuple.
-			 */
-			for (i = 0; i < nthispage; i++)
-			{
-				HeapTuple	heaptup = heaptuples[ndone + i];
-				xl_multi_insert_tuple *tuphdr;
-				int			datalen;
-
-				if (!init)
-					xlrec->offsets[i] = ItemPointerGetOffsetNumber(&heaptup->t_self);
-				/* xl_multi_insert_tuple needs two-byte alignment. */
-				tuphdr = (xl_multi_insert_tuple *) SHORTALIGN(scratchptr);
-				scratchptr = ((char *) tuphdr) + SizeOfMultiInsertTuple;
-
-				tuphdr->t_infomask2 = heaptup->t_data->t_infomask2;
-				tuphdr->t_infomask = heaptup->t_data->t_infomask;
-				tuphdr->t_hoff = heaptup->t_data->t_hoff;
-
-				/* write bitmap [+ padding] [+ oid] + data */
-				datalen = heaptup->t_len - SizeofHeapTupleHeader;
-				memcpy(scratchptr,
-					   (char *) heaptup->t_data + SizeofHeapTupleHeader,
-					   datalen);
-				tuphdr->datalen = datalen;
-				scratchptr += datalen;
-			}
-			totaldatalen = scratchptr - tupledata;
-			Assert((scratchptr - scratch) < BLCKSZ);
-
-			if (need_tuple_data)
-				xlrec->flags |= XLH_INSERT_CONTAINS_NEW_TUPLE;
-
-			/*
-			 * Signal that this is the last xl_heap_multi_insert record
-			 * emitted by this call to heap_multi_insert(). Needed for logical
-			 * decoding so it knows when to cleanup temporary data.
-			 */
-			if (ndone + nthispage == ntuples)
-				xlrec->flags |= XLH_INSERT_LAST_IN_MULTI;
-
-			if (init)
-			{
-				info |= XLOG_HEAP_INIT_PAGE;
-				bufflags |= REGBUF_WILL_INIT;
-			}
-
-			/*
-			 * If we're doing logical decoding, include the new tuple data
-			 * even if we take a full-page image of the page.
-			 */
-			if (need_tuple_data)
-				bufflags |= REGBUF_KEEP_DATA;
-
-			XLogBeginInsert();
-			XLogRegisterData((char *) xlrec, tupledata - scratch);
-			XLogRegisterBuffer(0, buffer, REGBUF_STANDARD | bufflags);
-
-			XLogRegisterBufData(0, tupledata, totaldatalen);
-
-			/* filtering by origin on a row level is much more efficient */
-			XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
-
-			recptr = XLogInsert(RM_HEAP2_ID, info);
-
-			PageSetLSN(page, recptr);
-		}
-
-		END_CRIT_SECTION();
-
-		UnlockReleaseBuffer(buffer);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
-
-		ndone += nthispage;
-	}
-
-	/*
-	 * We're done with the actual inserts.  Check for conflicts again, to
-	 * ensure that all rw-conflicts in to these inserts are detected.  Without
-	 * this final check, a sequential scan of the heap may have locked the
-	 * table after the "before" check, missing one opportunity to detect the
-	 * conflict, and then scanned the table before the new tuples were there,
-	 * missing the other chance to detect the conflict.
-	 *
-	 * For heap inserts, we only need to check for table-level SSI locks. Our
-	 * new tuples can't possibly conflict with existing tuple locks, and heap
-	 * page locks are only consolidated versions of tuple locks; they do not
-	 * lock "gaps" as index page locks do.  So we don't need to specify a
-	 * buffer when making the call.
-	 */
-	CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
-
-	/*
-	 * If tuples are cachable, mark them for invalidation from the caches in
-	 * case we abort.  Note it is OK to do this after releasing the buffer,
-	 * because the heaptuples data structure is all in local memory, not in
-	 * the shared buffer.
-	 */
-	if (IsCatalogRelation(relation))
-	{
-		for (i = 0; i < ntuples; i++)
-			CacheInvalidateHeapTuple(relation, heaptuples[i], NULL);
-	}
-
-	/*
-	 * Copy t_self fields back to the caller's original tuples. This does
-	 * nothing for untoasted tuples (tuples[i] == heaptuples[i)], but it's
-	 * probably faster to always copy than check.
-	 */
-	for (i = 0; i < ntuples; i++)
-		tuples[i]->t_self = heaptuples[i]->t_self;
-
-	pgstat_count_heap_insert(relation, ntuples);
-}
-
-/*
- *	simple_heap_insert - insert a tuple
- *
- * Currently, this routine differs from heap_insert only in supplying
- * a default command ID and not allowing access to the speedup options.
- *
- * This should be used rather than using heap_insert directly in most places
- * where we are modifying system catalogs.
- */
-Oid
-simple_heap_insert(Relation relation, HeapTuple tup)
-{
-	return heap_insert(relation, tup, GetCurrentCommandId(true), 0, NULL);
-}
-
-/*
- * Given infomask/infomask2, compute the bits that must be saved in the
- * "infobits" field of xl_heap_delete, xl_heap_update, xl_heap_lock,
- * xl_heap_lock_updated WAL records.
- *
- * See fix_infomask_from_infobits.
- */
-static uint8
-compute_infobits(uint16 infomask, uint16 infomask2)
-{
-	return
-		((infomask & HEAP_XMAX_IS_MULTI) != 0 ? XLHL_XMAX_IS_MULTI : 0) |
-		((infomask & HEAP_XMAX_LOCK_ONLY) != 0 ? XLHL_XMAX_LOCK_ONLY : 0) |
-		((infomask & HEAP_XMAX_EXCL_LOCK) != 0 ? XLHL_XMAX_EXCL_LOCK : 0) |
-	/* note we ignore HEAP_XMAX_SHR_LOCK here */
-		((infomask & HEAP_XMAX_KEYSHR_LOCK) != 0 ? XLHL_XMAX_KEYSHR_LOCK : 0) |
-		((infomask2 & HEAP_KEYS_UPDATED) != 0 ?
-		 XLHL_KEYS_UPDATED : 0);
-}
-
-/*
- * Given two versions of the same t_infomask for a tuple, compare them and
- * return whether the relevant status for a tuple Xmax has changed.  This is
- * used after a buffer lock has been released and reacquired: we want to ensure
- * that the tuple state continues to be the same it was when we previously
- * examined it.
- *
- * Note the Xmax field itself must be compared separately.
- */
-static inline bool
-xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask)
-{
-	const uint16 interesting =
-	HEAP_XMAX_IS_MULTI | HEAP_XMAX_LOCK_ONLY | HEAP_LOCK_MASK;
-
-	if ((new_infomask & interesting) != (old_infomask & interesting))
-		return true;
-
-	return false;
-}
-
-/*
- *	heap_delete - delete a tuple
- *
- * NB: do not call this directly unless you are prepared to deal with
- * concurrent-update conditions.  Use simple_heap_delete instead.
- *
- *	relation - table to be modified (caller must hold suitable lock)
- *	tid - TID of tuple to be deleted
- *	cid - delete command ID (used for visibility test, and stored into
- *		cmax if successful)
- *	crosscheck - if not InvalidSnapshot, also check tuple against this
- *	wait - true if should wait for any conflicting update to commit/abort
- *	hufd - output parameter, filled in failure cases (see below)
- *
- * Normal, successful return value is HeapTupleMayBeUpdated, which
- * actually means we did delete it.  Failure return codes are
- * HeapTupleSelfUpdated, HeapTupleUpdated, or HeapTupleBeingUpdated
- * (the last only possible if wait == false).
- *
- * In the failure cases, 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.
- */
-HTSU_Result
-heap_delete(Relation relation, ItemPointer tid,
-			CommandId cid, Snapshot crosscheck, bool wait,
-			HeapUpdateFailureData *hufd)
-{
-	HTSU_Result result;
-	TransactionId xid = GetCurrentTransactionId();
-	ItemId		lp;
-	HeapTupleData tp;
-	Page		page;
-	BlockNumber block;
-	Buffer		buffer;
-	Buffer		vmbuffer = InvalidBuffer;
-	TransactionId new_xmax;
-	uint16		new_infomask,
-				new_infomask2;
-	bool		have_tuple_lock = false;
-	bool		iscombo;
-	bool		all_visible_cleared = false;
-	HeapTuple	old_key_tuple = NULL;	/* replica identity of the tuple */
-	bool		old_key_copied = false;
-
-	Assert(ItemPointerIsValid(tid));
-
-	/*
-	 * Forbid this during a parallel operation, lest it allocate a combocid.
-	 * Other workers might need that combocid for visibility checks, and we
-	 * have no provision for broadcasting it to them.
-	 */
-	if (IsInParallelMode())
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
-				 errmsg("cannot delete tuples during a parallel operation")));
-
-	block = ItemPointerGetBlockNumber(tid);
-	buffer = ReadBuffer(relation, block);
-	page = BufferGetPage(buffer);
-
-	/*
-	 * Before locking the buffer, pin the visibility map page if it appears to
-	 * be necessary.  Since we haven't got the lock yet, someone else might be
-	 * in the middle of changing this, so we'll need to recheck after we have
-	 * the lock.
-	 */
-	if (PageIsAllVisible(page))
-		visibilitymap_pin(relation, block, &vmbuffer);
-
-	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+	if (PageIsAllVisible(page))
+		visibilitymap_pin(relation, block, &vmbuffer);
+
+	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 
 	/*
 	 * If we didn't pin the visibility map page and the page has become all
@@ -4555,7 +3883,7 @@ simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup)
 /*
  * Return the MultiXactStatus corresponding to the given tuple lock mode.
  */
-static MultiXactStatus
+MultiXactStatus
 get_mxact_status_for_lock(LockTupleMode mode, bool is_update)
 {
 	int			retval;
@@ -4573,1712 +3901,333 @@ get_mxact_status_for_lock(LockTupleMode mode, bool is_update)
 }
 
 /*
- *	heap_lock_tuple - lock a tuple in shared or exclusive mode
- *
- * Note that this acquires a buffer pin, which the caller must release.
- *
- * Input parameters:
- *	relation: relation containing tuple (caller must hold suitable lock)
- *	tuple->t_self: TID of tuple to lock (rest of struct need not be valid)
- *	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
- *	follow_updates: if true, follow the update chain to also lock descendant
- *		tuples.
- *
- * Output parameters:
- *	*tuple: all fields filled in
- *	*buffer: set to buffer holding tuple (pinned but not locked at exit)
- *	*hufd: filled in failure cases (see below)
- *
- * 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
+ * Acquire heavyweight lock on the given tuple, in preparation for acquiring
+ * its normal, Xmax-based tuple lock.
  *
- * 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.
+ * have_tuple_lock is an input and output parameter: on input, it indicates
+ * whether the lock has previously been acquired (and this function does
+ * nothing in that case).  If this function returns success, have_tuple_lock
+ * has been flipped to true.
  *
- * See README.tuplock for a thorough explanation of this mechanism.
+ * Returns false if it was unable to obtain the lock; this can only happen if
+ * wait_policy is Skip.
  */
-HTSU_Result
-heap_lock_tuple(Relation relation, HeapTuple tuple,
-				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
-				bool follow_updates,
-				Buffer *buffer, HeapUpdateFailureData *hufd)
+bool
+heap_acquire_tuplock(Relation relation, ItemPointer tid, LockTupleMode mode,
+					 LockWaitPolicy wait_policy, bool *have_tuple_lock)
 {
-	HTSU_Result result;
-	ItemPointer tid = &(tuple->t_self);
-	ItemId		lp;
-	Page		page;
-	Buffer		vmbuffer = InvalidBuffer;
-	BlockNumber block;
-	TransactionId xid,
-				xmax;
-	uint16		old_infomask,
-				new_infomask,
-				new_infomask2;
-	bool		first_time = true;
-	bool		have_tuple_lock = false;
-	bool		cleared_all_frozen = false;
+	if (*have_tuple_lock)
+		return true;
 
-	*buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
-	block = ItemPointerGetBlockNumber(tid);
+	switch (wait_policy)
+	{
+		case LockWaitBlock:
+			LockTupleTuplock(relation, tid, mode);
+			break;
 
-	/*
-	 * Before locking the buffer, pin the visibility map page if it appears to
-	 * be necessary.  Since we haven't got the lock yet, someone else might be
-	 * in the middle of changing this, so we'll need to recheck after we have
-	 * the lock.
-	 */
-	if (PageIsAllVisible(BufferGetPage(*buffer)))
-		visibilitymap_pin(relation, block, &vmbuffer);
-
-	LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-	page = BufferGetPage(*buffer);
-	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);
-
-l3:
-	result = relation->rd_stamroutine->snapshot_satisfiesUpdate(tuple, cid, *buffer);
-
-	if (result == HeapTupleInvisible)
-	{
-		/*
-		 * This is possible, but only when locking a tuple for ON CONFLICT
-		 * UPDATE.  We return this value here rather than throwing an error in
-		 * order to give that case the opportunity to throw a more specific
-		 * error.
-		 */
-		result = HeapTupleInvisible;
-		goto out_locked;
-	}
-	else if (result == HeapTupleBeingUpdated || result == HeapTupleUpdated)
-	{
-		TransactionId xwait;
-		uint16		infomask;
-		uint16		infomask2;
-		bool		require_sleep;
-		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);
-
-		LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
-
-		/*
-		 * If any subtransaction of the current top transaction already holds
-		 * a lock as strong as or stronger than what we're requesting, we
-		 * effectively hold the desired lock already.  We *must* succeed
-		 * without trying to take the tuple lock, else we will deadlock
-		 * against anyone wanting to acquire a stronger lock.
-		 *
-		 * Note we only do this the first time we loop on the HTSU result;
-		 * there is no point in testing in subsequent passes, because
-		 * evidently our own transaction cannot have acquired a new lock after
-		 * the first time we checked.
-		 */
-		if (first_time)
-		{
-			first_time = false;
-
-			if (infomask & HEAP_XMAX_IS_MULTI)
-			{
-				int			i;
-				int			nmembers;
-				MultiXactMember *members;
-
-				/*
-				 * We don't need to allow old multixacts here; if that had
-				 * been the case, HeapTupleSatisfiesUpdate would have returned
-				 * MayBeUpdated and we wouldn't be here.
-				 */
-				nmembers =
-					GetMultiXactIdMembers(xwait, &members, false,
-										  HEAP_XMAX_IS_LOCKED_ONLY(infomask));
-
-				for (i = 0; i < nmembers; i++)
-				{
-					/* only consider members of our own transaction */
-					if (!TransactionIdIsCurrentTransactionId(members[i].xid))
-						continue;
-
-					if (TUPLOCK_from_mxstatus(members[i].status) >= mode)
-					{
-						pfree(members);
-						result = HeapTupleMayBeUpdated;
-						goto out_unlocked;
-					}
-				}
-
-				if (members)
-					pfree(members);
-			}
-			else if (TransactionIdIsCurrentTransactionId(xwait))
-			{
-				switch (mode)
-				{
-					case LockTupleKeyShare:
-						Assert(HEAP_XMAX_IS_KEYSHR_LOCKED(infomask) ||
-							   HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
-							   HEAP_XMAX_IS_EXCL_LOCKED(infomask));
-						result = HeapTupleMayBeUpdated;
-						goto out_unlocked;
-					case LockTupleShare:
-						if (HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
-							HEAP_XMAX_IS_EXCL_LOCKED(infomask))
-						{
-							result = HeapTupleMayBeUpdated;
-							goto out_unlocked;
-						}
-						break;
-					case LockTupleNoKeyExclusive:
-						if (HEAP_XMAX_IS_EXCL_LOCKED(infomask))
-						{
-							result = HeapTupleMayBeUpdated;
-							goto out_unlocked;
-						}
-						break;
-					case LockTupleExclusive:
-						if (HEAP_XMAX_IS_EXCL_LOCKED(infomask) &&
-							infomask2 & HEAP_KEYS_UPDATED)
-						{
-							result = HeapTupleMayBeUpdated;
-							goto out_unlocked;
-						}
-						break;
-				}
-			}
-		}
-
-		/*
-		 * Initially assume that we will have to wait for the locking
-		 * transaction(s) to finish.  We check various cases below in which
-		 * this can be turned off.
-		 */
-		require_sleep = true;
-		if (mode == LockTupleKeyShare)
-		{
-			/*
-			 * If we're requesting KeyShare, and there's no update present, we
-			 * don't need to wait.  Even if there is an update, we can still
-			 * continue if the key hasn't been modified.
-			 *
-			 * However, if there are updates, we need to walk the update chain
-			 * to mark future versions of the row as locked, too.  That way,
-			 * if somebody deletes that future version, we're protected
-			 * against the key going away.  This locking of future versions
-			 * could block momentarily, if a concurrent transaction is
-			 * deleting a key; or it could return a value to the effect that
-			 * the transaction deleting the key has already committed.  So we
-			 * do this before re-locking the buffer; otherwise this would be
-			 * prone to deadlocks.
-			 *
-			 * Note that the TID we're locking was grabbed before we unlocked
-			 * the buffer.  For it to change while we're not looking, the
-			 * other properties we're testing for below after re-locking the
-			 * buffer would also change, in which case we would restart this
-			 * loop above.
-			 */
-			if (!(infomask2 & HEAP_KEYS_UPDATED))
-			{
-				bool		updated;
-
-				updated = !HEAP_XMAX_IS_LOCKED_ONLY(infomask);
-
-				/*
-				 * If there are updates, follow the update chain; bail out if
-				 * that cannot be done.
-				 */
-				if (follow_updates && updated)
-				{
-					HTSU_Result res;
-
-					res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
-												  GetCurrentTransactionId(),
-												  mode);
-					if (res != HeapTupleMayBeUpdated)
-					{
-						result = res;
-						/* recovery code expects to have buffer lock held */
-						LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-						goto failed;
-					}
-				}
-
-				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-				/*
-				 * Make sure it's still an appropriate lock, else start over.
-				 * Also, if it wasn't updated before we released the lock, but
-				 * is updated now, we start over too; the reason is that we
-				 * 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) ||
-					 !updated))
-					goto l3;
-
-				/* Things look okay, so we can skip sleeping */
-				require_sleep = false;
-
-				/*
-				 * Note we allow Xmax to change here; other updaters/lockers
-				 * could have modified it before we grabbed the buffer lock.
-				 * However, this is not a problem, because with the recheck we
-				 * just did we ensure that they still don't conflict with the
-				 * lock we want.
-				 */
-			}
-		}
-		else if (mode == LockTupleShare)
-		{
-			/*
-			 * If we're requesting Share, we can similarly avoid sleeping if
-			 * there's no update and no exclusive lock present.
-			 */
-			if (HEAP_XMAX_IS_LOCKED_ONLY(infomask) &&
-				!HEAP_XMAX_IS_EXCL_LOCKED(infomask))
-			{
-				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-				/*
-				 * 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))
-					goto l3;
-				require_sleep = false;
-			}
-		}
-		else if (mode == LockTupleNoKeyExclusive)
-		{
-			/*
-			 * If we're requesting NoKeyExclusive, we might also be able to
-			 * avoid sleeping; just ensure that there no conflicting lock
-			 * already acquired.
-			 */
-			if (infomask & HEAP_XMAX_IS_MULTI)
-			{
-				if (!DoesMultiXactIdConflict((MultiXactId) xwait, infomask,
-											 mode))
-				{
-					/*
-					 * No conflict, but if the xmax changed under us 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),
-											 xwait))
-						goto l3;
-
-					/* otherwise, we're good */
-					require_sleep = false;
-				}
-			}
-			else if (HEAP_XMAX_IS_KEYSHR_LOCKED(infomask))
-			{
-				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-				/* if the xmax changed in the meantime, start over */
-				if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
-					!TransactionIdEquals(
-										 HeapTupleHeaderGetRawXmax(tuple->t_data),
-										 xwait))
-					goto l3;
-				/* otherwise, we're good */
-				require_sleep = false;
-			}
-		}
-
-		/*
-		 * As a check independent from those above, we can also avoid sleeping
-		 * if the current transaction is the sole locker of the tuple.  Note
-		 * that the strength of the lock already held is irrelevant; this is
-		 * not about recording the lock in Xmax (which will be done regardless
-		 * of this optimization, below).  Also, note that the cases where we
-		 * hold a lock stronger than we are requesting are already handled
-		 * above by not doing anything.
-		 *
-		 * Note we only deal with the non-multixact case here; MultiXactIdWait
-		 * is well equipped to deal with this situation on its own.
-		 */
-		if (require_sleep && !(infomask & HEAP_XMAX_IS_MULTI) &&
-			TransactionIdIsCurrentTransactionId(xwait))
-		{
-			/* ... 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),
-									 xwait))
-				goto l3;
-			Assert(HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask));
-			require_sleep = false;
-		}
-
-		/*
-		 * Time to sleep on the other transaction/multixact, if necessary.
-		 *
-		 * If the other transaction is an update that's already committed,
-		 * then sleeping cannot possibly do any good: if we're required to
-		 * sleep, get out to raise an error instead.
-		 *
-		 * By here, we either have already acquired the buffer exclusive lock,
-		 * 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)
-		{
-			LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-			goto failed;
-		}
-		else if (require_sleep)
-		{
-			/*
-			 * Acquire tuple lock to establish our priority for the tuple, or
-			 * die trying.  LockTuple will release us when we are next-in-line
-			 * for the tuple.  We must do this even if we are share-locking.
-			 *
-			 * If we are forced to "start over" below, we keep the tuple lock;
-			 * this arranges that we stay at the head of the line while
-			 * rechecking tuple state.
-			 */
-			if (!heap_acquire_tuplock(relation, tid, mode, wait_policy,
-									  &have_tuple_lock))
-			{
-				/*
-				 * This can only happen if wait_policy is Skip and the lock
-				 * couldn't be obtained.
-				 */
-				result = HeapTupleWouldBlock;
-				/* recovery code expects to have buffer lock held */
-				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-				goto failed;
-			}
-
-			if (infomask & HEAP_XMAX_IS_MULTI)
-			{
-				MultiXactStatus status = get_mxact_status_for_lock(mode, false);
-
-				/* We only ever lock tuples, never update them */
-				if (status >= MultiXactStatusNoKeyUpdate)
-					elog(ERROR, "invalid lock mode in heap_lock_tuple");
-
-				/* wait for multixact to end, or die trying  */
-				switch (wait_policy)
-				{
-					case LockWaitBlock:
-						MultiXactIdWait((MultiXactId) xwait, status, infomask,
-										relation, &tuple->t_self, XLTW_Lock, NULL);
-						break;
-					case LockWaitSkip:
-						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
-														status, infomask, relation,
-														NULL))
-						{
-							result = HeapTupleWouldBlock;
-							/* recovery code expects to have buffer lock held */
-							LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-							goto failed;
-						}
-						break;
-					case LockWaitError:
-						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
-														status, infomask, relation,
-														NULL))
-							ereport(ERROR,
-									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-									 errmsg("could not obtain lock on row in relation \"%s\"",
-											RelationGetRelationName(relation))));
-
-						break;
-				}
-
-				/*
-				 * Of course, the multixact might not be done here: if we're
-				 * requesting a light lock mode, other transactions with light
-				 * locks could still be alive, as well as locks owned by our
-				 * own xact or other subxacts of this backend.  We need to
-				 * preserve the surviving MultiXact members.  Note that it
-				 * isn't absolutely necessary in the latter case, but doing so
-				 * is simpler.
-				 */
-			}
-			else
-			{
-				/* wait for regular transaction to end, or die trying */
-				switch (wait_policy)
-				{
-					case LockWaitBlock:
-						XactLockTableWait(xwait, relation, &tuple->t_self,
-										  XLTW_Lock);
-						break;
-					case LockWaitSkip:
-						if (!ConditionalXactLockTableWait(xwait))
-						{
-							result = HeapTupleWouldBlock;
-							/* recovery code expects to have buffer lock held */
-							LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-							goto failed;
-						}
-						break;
-					case LockWaitError:
-						if (!ConditionalXactLockTableWait(xwait))
-							ereport(ERROR,
-									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-									 errmsg("could not obtain lock on row in relation \"%s\"",
-											RelationGetRelationName(relation))));
-						break;
-				}
-			}
-
-			/* if there are updates, follow the update chain */
-			if (follow_updates && !HEAP_XMAX_IS_LOCKED_ONLY(infomask))
-			{
-				HTSU_Result res;
-
-				res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
-											  GetCurrentTransactionId(),
-											  mode);
-				if (res != HeapTupleMayBeUpdated)
-				{
-					result = res;
-					/* recovery code expects to have buffer lock held */
-					LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-					goto failed;
-				}
-			}
-
-			LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-			/*
-			 * xwait is done, but if xwait had just locked the tuple then some
-			 * 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),
-									 xwait))
-				goto l3;
-
-			if (!(infomask & HEAP_XMAX_IS_MULTI))
-			{
-				/*
-				 * Otherwise check if it committed or aborted.  Note we cannot
-				 * be here if the tuple was only locked by somebody who didn't
-				 * conflict with us; that would have been handled above.  So
-				 * that transaction must necessarily be gone by now.  But
-				 * don't check for this in the multixact case, because some
-				 * locker transactions might still be running.
-				 */
-				UpdateXmaxHintBits(tuple->t_data, *buffer, xwait);
-			}
-		}
-
-		/* By here, we're certain that we hold buffer exclusive lock again */
-
-		/*
-		 * We may lock if previous xmax aborted, or if it committed but only
-		 * locked the tuple without updating it; or if we didn't have to wait
-		 * 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))
-			result = HeapTupleMayBeUpdated;
-		else
-			result = HeapTupleUpdated;
-	}
-
-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);
-		if (result == HeapTupleSelfUpdated)
-			hufd->cmax = HeapTupleHeaderGetCmax(tuple->t_data);
-		else
-			hufd->cmax = InvalidCommandId;
-		goto out_locked;
-	}
-
-	/*
-	 * If we didn't pin the visibility map page and the page has become all
-	 * visible while we were busy locking the buffer, or during some
-	 * subsequent window during which we had it unlocked, we'll have to unlock
-	 * and re-lock, to avoid holding the buffer lock across I/O.  That's a bit
-	 * unfortunate, especially since we'll now have to recheck whether the
-	 * tuple has been locked or updated under us, but hopefully it won't
-	 * happen very often.
-	 */
-	if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
-	{
-		LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
-		visibilitymap_pin(relation, block, &vmbuffer);
-		LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-		goto l3;
-	}
-
-	xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
-	old_infomask = tuple->t_data->t_infomask;
-
-	/*
-	 * If this is the first possibly-multixact-able operation in the current
-	 * transaction, set my per-backend OldestMemberMXactId setting. We can be
-	 * certain that the transaction will never become a member of any older
-	 * MultiXactIds than that.  (We have to do this even if we end up just
-	 * using our own TransactionId below, since some other backend could
-	 * incorporate our XID into a MultiXact immediately afterwards.)
-	 */
-	MultiXactIdSetOldestMember();
-
-	/*
-	 * Compute the new xmax and infomask to store into the tuple.  Note we do
-	 * 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,
-							  GetCurrentTransactionId(), mode, false,
-							  &xid, &new_infomask, &new_infomask2);
-
-	START_CRIT_SECTION();
-
-	/*
-	 * Store transaction information of xact locking the tuple.
-	 *
-	 * Note: Cmax is meaningless in this context, so don't set it; this avoids
-	 * possibly generating a useless combo CID.  Moreover, if we're locking a
-	 * previously updated tuple, it's important to preserve the Cmax.
-	 *
-	 * 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;
-	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
-		HeapTupleHeaderClearHotUpdated(tuple->t_data);
-	HeapTupleHeaderSetXmax(tuple->t_data, xid);
-
-	/*
-	 * Make sure there is no forward chain link in t_ctid.  Note that in the
-	 * cases where the tuple has been updated, we must not overwrite t_ctid,
-	 * because it was set by the updater.  Moreover, if the tuple has been
-	 * updated, we need to follow the update chain to lock the new versions of
-	 * the tuple as well.
-	 */
-	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
-		tuple->t_data->t_ctid = *tid;
-
-	/* Clear only the all-frozen bit on visibility map if needed */
-	if (PageIsAllVisible(page) &&
-		visibilitymap_clear(relation, block, vmbuffer,
-							VISIBILITYMAP_ALL_FROZEN))
-		cleared_all_frozen = true;
-
-
-	MarkBufferDirty(*buffer);
-
-	/*
-	 * XLOG stuff.  You might think that we don't need an XLOG record because
-	 * there is no state change worth restoring after a crash.  You would be
-	 * wrong however: we have just written either a TransactionId or a
-	 * MultiXactId that may never have been seen on disk before, and we need
-	 * to make sure that there are XLOG entries covering those ID numbers.
-	 * Else the same IDs might be re-used after a crash, which would be
-	 * disastrous if this page made it to disk before the crash.  Essentially
-	 * we have to enforce the WAL log-before-data rule even in this case.
-	 * (Also, in a PITR log-shipping or 2PC environment, we have to have XLOG
-	 * entries for everything anyway.)
-	 */
-	if (RelationNeedsWAL(relation))
-	{
-		xl_heap_lock xlrec;
-		XLogRecPtr	recptr;
-
-		XLogBeginInsert();
-		XLogRegisterBuffer(0, *buffer, REGBUF_STANDARD);
-
-		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
-		xlrec.locking_xid = xid;
-		xlrec.infobits_set = compute_infobits(new_infomask,
-											  tuple->t_data->t_infomask2);
-		xlrec.flags = cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
-		XLogRegisterData((char *) &xlrec, SizeOfHeapLock);
-
-		/* we don't decode row locks atm, so no need to log the origin */
-
-		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_LOCK);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	result = HeapTupleMayBeUpdated;
-
-out_locked:
-	LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
-
-out_unlocked:
-	if (BufferIsValid(vmbuffer))
-		ReleaseBuffer(vmbuffer);
-
-	/*
-	 * Don't update the visibility map here. Locking a tuple doesn't change
-	 * visibility info.
-	 */
-
-	/*
-	 * Now that we have successfully marked the tuple as locked, we can
-	 * release the lmgr tuple lock, if we had it.
-	 */
-	if (have_tuple_lock)
-		UnlockTupleTuplock(relation, tid, mode);
-
-	return result;
-}
-
-/*
- * Acquire heavyweight lock on the given tuple, in preparation for acquiring
- * its normal, Xmax-based tuple lock.
- *
- * have_tuple_lock is an input and output parameter: on input, it indicates
- * whether the lock has previously been acquired (and this function does
- * nothing in that case).  If this function returns success, have_tuple_lock
- * has been flipped to true.
- *
- * Returns false if it was unable to obtain the lock; this can only happen if
- * wait_policy is Skip.
- */
-static bool
-heap_acquire_tuplock(Relation relation, ItemPointer tid, LockTupleMode mode,
-					 LockWaitPolicy wait_policy, bool *have_tuple_lock)
-{
-	if (*have_tuple_lock)
-		return true;
-
-	switch (wait_policy)
-	{
-		case LockWaitBlock:
-			LockTupleTuplock(relation, tid, mode);
-			break;
-
-		case LockWaitSkip:
-			if (!ConditionalLockTupleTuplock(relation, tid, mode))
-				return false;
-			break;
-
-		case LockWaitError:
-			if (!ConditionalLockTupleTuplock(relation, tid, mode))
-				ereport(ERROR,
-						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-						 errmsg("could not obtain lock on row in relation \"%s\"",
-								RelationGetRelationName(relation))));
-			break;
-	}
-	*have_tuple_lock = true;
-
-	return true;
-}
-
-/*
- * Given an original set of Xmax and infomask, and a transaction (identified by
- * add_to_xmax) acquiring a new lock of some mode, compute the new Xmax and
- * corresponding infomasks to use on the tuple.
- *
- * Note that this might have side effects such as creating a new MultiXactId.
- *
- * Most callers will have called HeapTupleSatisfiesUpdate before this function;
- * that will have set the HEAP_XMAX_INVALID bit if the xmax was a MultiXactId
- * but it was not running anymore. There is a race condition, which is that the
- * MultiXactId may have finished since then, but that uncommon case is handled
- * either here, or within MultiXactIdExpand.
- *
- * There is a similar race condition possible when the old xmax was a regular
- * TransactionId.  We test TransactionIdIsInProgress again just to narrow the
- * window, but it's still possible to end up creating an unnecessary
- * MultiXactId.  Fortunately this is harmless.
- */
-static void
-compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
-						  uint16 old_infomask2, TransactionId add_to_xmax,
-						  LockTupleMode mode, bool is_update,
-						  TransactionId *result_xmax, uint16 *result_infomask,
-						  uint16 *result_infomask2)
-{
-	TransactionId new_xmax;
-	uint16		new_infomask,
-				new_infomask2;
-
-	Assert(TransactionIdIsCurrentTransactionId(add_to_xmax));
-
-l5:
-	new_infomask = 0;
-	new_infomask2 = 0;
-	if (old_infomask & HEAP_XMAX_INVALID)
-	{
-		/*
-		 * No previous locker; we just insert our own TransactionId.
-		 *
-		 * Note that it's critical that this case be the first one checked,
-		 * because there are several blocks below that come back to this one
-		 * to implement certain optimizations; old_infomask might contain
-		 * other dirty bits in those cases, but we don't really care.
-		 */
-		if (is_update)
-		{
-			new_xmax = add_to_xmax;
-			if (mode == LockTupleExclusive)
-				new_infomask2 |= HEAP_KEYS_UPDATED;
-		}
-		else
-		{
-			new_infomask |= HEAP_XMAX_LOCK_ONLY;
-			switch (mode)
-			{
-				case LockTupleKeyShare:
-					new_xmax = add_to_xmax;
-					new_infomask |= HEAP_XMAX_KEYSHR_LOCK;
-					break;
-				case LockTupleShare:
-					new_xmax = add_to_xmax;
-					new_infomask |= HEAP_XMAX_SHR_LOCK;
-					break;
-				case LockTupleNoKeyExclusive:
-					new_xmax = add_to_xmax;
-					new_infomask |= HEAP_XMAX_EXCL_LOCK;
-					break;
-				case LockTupleExclusive:
-					new_xmax = add_to_xmax;
-					new_infomask |= HEAP_XMAX_EXCL_LOCK;
-					new_infomask2 |= HEAP_KEYS_UPDATED;
-					break;
-				default:
-					new_xmax = InvalidTransactionId;	/* silence compiler */
-					elog(ERROR, "invalid lock mode");
-			}
-		}
-	}
-	else if (old_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		MultiXactStatus new_status;
-
-		/*
-		 * Currently we don't allow XMAX_COMMITTED to be set for multis, so
-		 * cross-check.
-		 */
-		Assert(!(old_infomask & HEAP_XMAX_COMMITTED));
-
-		/*
-		 * A multixact together with LOCK_ONLY set but neither lock bit set
-		 * (i.e. a pg_upgraded share locked tuple) cannot possibly be running
-		 * anymore.  This check is critical for databases upgraded by
-		 * pg_upgrade; both MultiXactIdIsRunning and MultiXactIdExpand assume
-		 * that such multis are never passed.
-		 */
-		if (HEAP_LOCKED_UPGRADED(old_infomask))
-		{
-			old_infomask &= ~HEAP_XMAX_IS_MULTI;
-			old_infomask |= HEAP_XMAX_INVALID;
-			goto l5;
-		}
-
-		/*
-		 * If the XMAX is already a MultiXactId, then we need to expand it to
-		 * include add_to_xmax; but if all the members were lockers and are
-		 * all gone, we can do away with the IS_MULTI bit and just set
-		 * add_to_xmax as the only locker/updater.  If all lockers are gone
-		 * and we have an updater that aborted, we can also do without a
-		 * multi.
-		 *
-		 * The cost of doing GetMultiXactIdMembers would be paid by
-		 * MultiXactIdExpand if we weren't to do this, so this check is not
-		 * incurring extra work anyhow.
-		 */
-		if (!MultiXactIdIsRunning(xmax, HEAP_XMAX_IS_LOCKED_ONLY(old_infomask)))
-		{
-			if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) ||
-				!TransactionIdDidCommit(MultiXactIdGetUpdateXid(xmax,
-																old_infomask)))
-			{
-				/*
-				 * Reset these bits and restart; otherwise fall through to
-				 * create a new multi below.
-				 */
-				old_infomask &= ~HEAP_XMAX_IS_MULTI;
-				old_infomask |= HEAP_XMAX_INVALID;
-				goto l5;
-			}
-		}
-
-		new_status = get_mxact_status_for_lock(mode, is_update);
-
-		new_xmax = MultiXactIdExpand((MultiXactId) xmax, add_to_xmax,
-									 new_status);
-		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
-	}
-	else if (old_infomask & HEAP_XMAX_COMMITTED)
-	{
-		/*
-		 * It's a committed update, so we need to preserve him as updater of
-		 * the tuple.
-		 */
-		MultiXactStatus status;
-		MultiXactStatus new_status;
-
-		if (old_infomask2 & HEAP_KEYS_UPDATED)
-			status = MultiXactStatusUpdate;
-		else
-			status = MultiXactStatusNoKeyUpdate;
-
-		new_status = get_mxact_status_for_lock(mode, is_update);
-
-		/*
-		 * since it's not running, it's obviously impossible for the old
-		 * updater to be identical to the current one, so we need not check
-		 * for that case as we do in the block above.
-		 */
-		new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
-		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
-	}
-	else if (TransactionIdIsInProgress(xmax))
-	{
-		/*
-		 * If the XMAX is a valid, in-progress TransactionId, then we need to
-		 * create a new MultiXactId that includes both the old locker or
-		 * updater and our own TransactionId.
-		 */
-		MultiXactStatus new_status;
-		MultiXactStatus old_status;
-		LockTupleMode old_mode;
-
-		if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
-		{
-			if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
-				old_status = MultiXactStatusForKeyShare;
-			else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
-				old_status = MultiXactStatusForShare;
-			else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
-			{
-				if (old_infomask2 & HEAP_KEYS_UPDATED)
-					old_status = MultiXactStatusForUpdate;
-				else
-					old_status = MultiXactStatusForNoKeyUpdate;
-			}
-			else
-			{
-				/*
-				 * LOCK_ONLY can be present alone only when a page has been
-				 * upgraded by pg_upgrade.  But in that case,
-				 * TransactionIdIsInProgress() should have returned false.  We
-				 * assume it's no longer locked in this case.
-				 */
-				elog(WARNING, "LOCK_ONLY found for Xid in progress %u", xmax);
-				old_infomask |= HEAP_XMAX_INVALID;
-				old_infomask &= ~HEAP_XMAX_LOCK_ONLY;
-				goto l5;
-			}
-		}
-		else
-		{
-			/* it's an update, but which kind? */
-			if (old_infomask2 & HEAP_KEYS_UPDATED)
-				old_status = MultiXactStatusUpdate;
-			else
-				old_status = MultiXactStatusNoKeyUpdate;
-		}
-
-		old_mode = TUPLOCK_from_mxstatus(old_status);
-
-		/*
-		 * If the lock to be acquired is for the same TransactionId as the
-		 * existing lock, there's an optimization possible: consider only the
-		 * strongest of both locks as the only one present, and restart.
-		 */
-		if (xmax == add_to_xmax)
-		{
-			/*
-			 * Note that it's not possible for the original tuple to be
-			 * updated: we wouldn't be here because the tuple would have been
-			 * invisible and we wouldn't try to update it.  As a subtlety,
-			 * this code can also run when traversing an update chain to lock
-			 * future versions of a tuple.  But we wouldn't be here either,
-			 * because the add_to_xmax would be different from the original
-			 * updater.
-			 */
-			Assert(HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
-
-			/* acquire the strongest of both */
-			if (mode < old_mode)
-				mode = old_mode;
-			/* mustn't touch is_update */
-
-			old_infomask |= HEAP_XMAX_INVALID;
-			goto l5;
-		}
-
-		/* otherwise, just fall back to creating a new multixact */
-		new_status = get_mxact_status_for_lock(mode, is_update);
-		new_xmax = MultiXactIdCreate(xmax, old_status,
-									 add_to_xmax, new_status);
-		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
-	}
-	else if (!HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) &&
-			 TransactionIdDidCommit(xmax))
-	{
-		/*
-		 * It's a committed update, so we gotta preserve him as updater of the
-		 * tuple.
-		 */
-		MultiXactStatus status;
-		MultiXactStatus new_status;
-
-		if (old_infomask2 & HEAP_KEYS_UPDATED)
-			status = MultiXactStatusUpdate;
-		else
-			status = MultiXactStatusNoKeyUpdate;
-
-		new_status = get_mxact_status_for_lock(mode, is_update);
-
-		/*
-		 * since it's not running, it's obviously impossible for the old
-		 * updater to be identical to the current one, so we need not check
-		 * for that case as we do in the block above.
-		 */
-		new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
-		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
-	}
-	else
-	{
-		/*
-		 * Can get here iff the locking/updating transaction was running when
-		 * the infomask was extracted from the tuple, but finished before
-		 * TransactionIdIsInProgress got to run.  Deal with it as if there was
-		 * no locker at all in the first place.
-		 */
-		old_infomask |= HEAP_XMAX_INVALID;
-		goto l5;
-	}
-
-	*result_infomask = new_infomask;
-	*result_infomask2 = new_infomask2;
-	*result_xmax = new_xmax;
-}
-
-/*
- * Subroutine for heap_lock_updated_tuple_rec.
- *
- * Given a hypothetical multixact status held by the transaction identified
- * with the given xid, does the current transaction need to wait, fail, or can
- * it continue if it wanted to acquire a lock of the given mode?  "needwait"
- * is set to true if waiting is necessary; if it can continue, then
- * HeapTupleMayBeUpdated is returned.  If the lock is already held by the
- * current transaction, return HeapTupleSelfUpdated.  In case of a conflict
- * with another transaction, a different HeapTupleSatisfiesUpdate return code
- * is returned.
- *
- * The held status is said to be hypothetical because it might correspond to a
- * lock held by a single Xid, i.e. not a real MultiXactId; we express it this
- * way for simplicity of API.
- */
-static HTSU_Result
-test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid,
-						   LockTupleMode mode, bool *needwait)
-{
-	MultiXactStatus wantedstatus;
-
-	*needwait = false;
-	wantedstatus = get_mxact_status_for_lock(mode, false);
-
-	/*
-	 * Note: we *must* check TransactionIdIsInProgress before
-	 * TransactionIdDidAbort/Commit; see comment at top of tqual.c for an
-	 * explanation.
-	 */
-	if (TransactionIdIsCurrentTransactionId(xid))
-	{
-		/*
-		 * The tuple has already been locked by our own transaction.  This is
-		 * very rare but can happen if multiple transactions are trying to
-		 * lock an ancient version of the same tuple.
-		 */
-		return HeapTupleSelfUpdated;
-	}
-	else if (TransactionIdIsInProgress(xid))
-	{
-		/*
-		 * If the locking transaction is running, what we do depends on
-		 * whether the lock modes conflict: if they do, then we must wait for
-		 * it to finish; otherwise we can fall through to lock this tuple
-		 * version without waiting.
-		 */
-		if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
-								LOCKMODE_from_mxstatus(wantedstatus)))
-		{
-			*needwait = true;
-		}
-
-		/*
-		 * If we set needwait above, then this value doesn't matter;
-		 * otherwise, this value signals to caller that it's okay to proceed.
-		 */
-		return HeapTupleMayBeUpdated;
-	}
-	else if (TransactionIdDidAbort(xid))
-		return HeapTupleMayBeUpdated;
-	else if (TransactionIdDidCommit(xid))
-	{
-		/*
-		 * The other transaction committed.  If it was only a locker, then the
-		 * lock is completely gone now and we can return success; but if it
-		 * was an update, then what we do depends on whether the two lock
-		 * modes conflict.  If they conflict, then we must report error to
-		 * caller. But if they don't, we can fall through to allow the current
-		 * transaction to lock the tuple.
-		 *
-		 * Note: the reason we worry about ISUPDATE here is because as soon as
-		 * a transaction ends, all its locks are gone and meaningless, and
-		 * thus we can ignore them; whereas its updates persist.  In the
-		 * TransactionIdIsInProgress case, above, we don't need to check
-		 * because we know the lock is still "alive" and thus a conflict needs
-		 * always be checked.
-		 */
-		if (!ISUPDATE_from_mxstatus(status))
-			return HeapTupleMayBeUpdated;
-
-		if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
-								LOCKMODE_from_mxstatus(wantedstatus)))
-			/* bummer */
-			return HeapTupleUpdated;
-
-		return HeapTupleMayBeUpdated;
-	}
-
-	/* Not in progress, not aborted, not committed -- must have crashed */
-	return HeapTupleMayBeUpdated;
-}
-
-
-/*
- * Recursive part of heap_lock_updated_tuple
- *
- * Fetch the tuple pointed to by tid in rel, and mark it as locked by the given
- * xid with the given mode; if this tuple is updated, recurse to lock the new
- * version as well.
- */
-static HTSU_Result
-heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid,
-							LockTupleMode mode)
-{
-	HTSU_Result result;
-	ItemPointerData tupid;
-	HeapTupleData mytup;
-	Buffer		buf;
-	uint16		new_infomask,
-				new_infomask2,
-				old_infomask,
-				old_infomask2;
-	TransactionId xmax,
-				new_xmax;
-	TransactionId priorXmax = InvalidTransactionId;
-	bool		cleared_all_frozen = false;
-	Buffer		vmbuffer = InvalidBuffer;
-	BlockNumber block;
-
-	ItemPointerCopy(tid, &tupid);
-
-	for (;;)
-	{
-		new_infomask = 0;
-		new_xmax = InvalidTransactionId;
-		block = ItemPointerGetBlockNumber(&tupid);
-		ItemPointerCopy(&tupid, &(mytup.t_self));
-
-		if (!heap_fetch(rel, SnapshotAny, &mytup, &buf, false, NULL))
-		{
-			/*
-			 * if we fail to find the updated version of the tuple, it's
-			 * because it was vacuumed/pruned away after its creator
-			 * transaction aborted.  So behave as if we got to the end of the
-			 * chain, and there's no further tuple to lock: return success to
-			 * caller.
-			 */
-			return HeapTupleMayBeUpdated;
-		}
-
-l4:
-		CHECK_FOR_INTERRUPTS();
-
-		/*
-		 * Before locking the buffer, pin the visibility map page if it
-		 * appears to be necessary.  Since we haven't got the lock yet,
-		 * someone else might be in the middle of changing this, so we'll need
-		 * to recheck after we have the lock.
-		 */
-		if (PageIsAllVisible(BufferGetPage(buf)))
-			visibilitymap_pin(rel, block, &vmbuffer);
-		else
-			vmbuffer = InvalidBuffer;
-
-		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
-
-		/*
-		 * If we didn't pin the visibility map page and the page has become
-		 * all visible while we were busy locking the buffer, we'll have to
-		 * unlock and re-lock, to avoid holding the buffer lock across I/O.
-		 * That's a bit unfortunate, but hopefully shouldn't happen often.
-		 */
-		if (vmbuffer == InvalidBuffer && PageIsAllVisible(BufferGetPage(buf)))
-		{
-			LockBuffer(buf, BUFFER_LOCK_UNLOCK);
-			visibilitymap_pin(rel, block, &vmbuffer);
-			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
-		}
-
-		/*
-		 * Check the tuple XMIN against prior XMAX, if any.  If we reached the
-		 * end of the chain, we're done, so return success.
-		 */
-		if (TransactionIdIsValid(priorXmax) &&
-			!HeapTupleUpdateXmaxMatchesXmin(priorXmax, mytup.t_data))
-		{
-			result = HeapTupleMayBeUpdated;
-			goto out_locked;
-		}
-
-		/*
-		 * Also check Xmin: if this tuple was created by an aborted
-		 * (sub)transaction, then we already locked the last live one in the
-		 * chain, thus we're done, so return success.
-		 */
-		if (TransactionIdDidAbort(HeapTupleHeaderGetXmin(mytup.t_data)))
-		{
-			UnlockReleaseBuffer(buf);
-			return HeapTupleMayBeUpdated;
-		}
-
-		old_infomask = mytup.t_data->t_infomask;
-		old_infomask2 = mytup.t_data->t_infomask2;
-		xmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
-
-		/*
-		 * If this tuple version has been updated or locked by some concurrent
-		 * transaction(s), what we do depends on whether our lock mode
-		 * conflicts with what those other transactions hold, and also on the
-		 * status of them.
-		 */
-		if (!(old_infomask & HEAP_XMAX_INVALID))
-		{
-			TransactionId rawxmax;
-			bool		needwait;
-
-			rawxmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
-			if (old_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				int			nmembers;
-				int			i;
-				MultiXactMember *members;
-
-				/*
-				 * We don't need a test for pg_upgrade'd tuples: this is only
-				 * applied to tuples after the first in an update chain.  Said
-				 * first tuple in the chain may well be locked-in-9.2-and-
-				 * pg_upgraded, but that one was already locked by our caller,
-				 * not us; and any subsequent ones cannot be because our
-				 * caller must necessarily have obtained a snapshot later than
-				 * the pg_upgrade itself.
-				 */
-				Assert(!HEAP_LOCKED_UPGRADED(mytup.t_data->t_infomask));
-
-				nmembers = GetMultiXactIdMembers(rawxmax, &members, false,
-												 HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
-				for (i = 0; i < nmembers; i++)
-				{
-					result = test_lockmode_for_conflict(members[i].status,
-														members[i].xid,
-														mode, &needwait);
-
-					/*
-					 * If the tuple was already locked by ourselves in a
-					 * previous iteration of this (say heap_lock_tuple was
-					 * forced to restart the locking loop because of a change
-					 * in xmax), then we hold the lock already on this tuple
-					 * version and we don't need to do anything; and this is
-					 * not an error condition either.  We just need to skip
-					 * this tuple and continue locking the next version in the
-					 * update chain.
-					 */
-					if (result == HeapTupleSelfUpdated)
-					{
-						pfree(members);
-						goto next;
-					}
-
-					if (needwait)
-					{
-						LockBuffer(buf, BUFFER_LOCK_UNLOCK);
-						XactLockTableWait(members[i].xid, rel,
-										  &mytup.t_self,
-										  XLTW_LockUpdated);
-						pfree(members);
-						goto l4;
-					}
-					if (result != HeapTupleMayBeUpdated)
-					{
-						pfree(members);
-						goto out_locked;
-					}
-				}
-				if (members)
-					pfree(members);
-			}
-			else
-			{
-				MultiXactStatus status;
-
-				/*
-				 * For a non-multi Xmax, we first need to compute the
-				 * corresponding MultiXactStatus by using the infomask bits.
-				 */
-				if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
-				{
-					if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
-						status = MultiXactStatusForKeyShare;
-					else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
-						status = MultiXactStatusForShare;
-					else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
-					{
-						if (old_infomask2 & HEAP_KEYS_UPDATED)
-							status = MultiXactStatusForUpdate;
-						else
-							status = MultiXactStatusForNoKeyUpdate;
-					}
-					else
-					{
-						/*
-						 * LOCK_ONLY present alone (a pg_upgraded tuple marked
-						 * as share-locked in the old cluster) shouldn't be
-						 * seen in the middle of an update chain.
-						 */
-						elog(ERROR, "invalid lock status in tuple");
-					}
-				}
-				else
-				{
-					/* it's an update, but which kind? */
-					if (old_infomask2 & HEAP_KEYS_UPDATED)
-						status = MultiXactStatusUpdate;
-					else
-						status = MultiXactStatusNoKeyUpdate;
-				}
-
-				result = test_lockmode_for_conflict(status, rawxmax, mode,
-													&needwait);
-
-				/*
-				 * If the tuple was already locked by ourselves in a previous
-				 * iteration of this (say heap_lock_tuple was forced to
-				 * restart the locking loop because of a change in xmax), then
-				 * we hold the lock already on this tuple version and we don't
-				 * need to do anything; and this is not an error condition
-				 * either.  We just need to skip this tuple and continue
-				 * locking the next version in the update chain.
-				 */
-				if (result == HeapTupleSelfUpdated)
-					goto next;
-
-				if (needwait)
-				{
-					LockBuffer(buf, BUFFER_LOCK_UNLOCK);
-					XactLockTableWait(rawxmax, rel, &mytup.t_self,
-									  XLTW_LockUpdated);
-					goto l4;
-				}
-				if (result != HeapTupleMayBeUpdated)
-				{
-					goto out_locked;
-				}
-			}
-		}
-
-		/* compute the new Xmax and infomask values for the tuple ... */
-		compute_new_xmax_infomask(xmax, old_infomask, mytup.t_data->t_infomask2,
-								  xid, mode, false,
-								  &new_xmax, &new_infomask, &new_infomask2);
-
-		if (PageIsAllVisible(BufferGetPage(buf)) &&
-			visibilitymap_clear(rel, block, vmbuffer,
-								VISIBILITYMAP_ALL_FROZEN))
-			cleared_all_frozen = true;
-
-		START_CRIT_SECTION();
-
-		/* ... and set them */
-		HeapTupleHeaderSetXmax(mytup.t_data, new_xmax);
-		mytup.t_data->t_infomask &= ~HEAP_XMAX_BITS;
-		mytup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
-		mytup.t_data->t_infomask |= new_infomask;
-		mytup.t_data->t_infomask2 |= new_infomask2;
-
-		MarkBufferDirty(buf);
-
-		/* XLOG stuff */
-		if (RelationNeedsWAL(rel))
-		{
-			xl_heap_lock_updated xlrec;
-			XLogRecPtr	recptr;
-			Page		page = BufferGetPage(buf);
-
-			XLogBeginInsert();
-			XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
-
-			xlrec.offnum = ItemPointerGetOffsetNumber(&mytup.t_self);
-			xlrec.xmax = new_xmax;
-			xlrec.infobits_set = compute_infobits(new_infomask, new_infomask2);
-			xlrec.flags =
-				cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
-
-			XLogRegisterData((char *) &xlrec, SizeOfHeapLockUpdated);
-
-			recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_LOCK_UPDATED);
-
-			PageSetLSN(page, recptr);
-		}
-
-		END_CRIT_SECTION();
-
-next:
-		/* if we find the end of update chain, we're done. */
-		if (mytup.t_data->t_infomask & HEAP_XMAX_INVALID ||
-			ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid) ||
-			HeapTupleHeaderIsOnlyLocked(mytup.t_data))
-		{
-			result = HeapTupleMayBeUpdated;
-			goto out_locked;
-		}
+		case LockWaitSkip:
+			if (!ConditionalLockTupleTuplock(relation, tid, mode))
+				return false;
+			break;
 
-		/* tail recursion */
-		priorXmax = HeapTupleHeaderGetUpdateXid(mytup.t_data);
-		ItemPointerCopy(&(mytup.t_data->t_ctid), &tupid);
-		UnlockReleaseBuffer(buf);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
+		case LockWaitError:
+			if (!ConditionalLockTupleTuplock(relation, tid, mode))
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("could not obtain lock on row in relation \"%s\"",
+								RelationGetRelationName(relation))));
+			break;
 	}
+	*have_tuple_lock = true;
 
-	result = HeapTupleMayBeUpdated;
-
-out_locked:
-	UnlockReleaseBuffer(buf);
-
-	if (vmbuffer != InvalidBuffer)
-		ReleaseBuffer(vmbuffer);
-
-	return result;
-
+	return true;
 }
 
 /*
- * heap_lock_updated_tuple
- *		Follow update chain when locking an updated tuple, acquiring locks (row
- *		marks) on the updated versions.
- *
- * The initial tuple is assumed to be already locked.
- *
- * This function doesn't check visibility, it just unconditionally marks the
- * tuple(s) as locked.  If any tuple in the updated chain is being deleted
- * concurrently (or updated with the key being modified), sleep until the
- * transaction doing it is finished.
+ * Given an original set of Xmax and infomask, and a transaction (identified by
+ * add_to_xmax) acquiring a new lock of some mode, compute the new Xmax and
+ * corresponding infomasks to use on the tuple.
  *
- * Note that we don't acquire heavyweight tuple locks on the tuples we walk
- * when we have to wait for other transactions to release them, as opposed to
- * what heap_lock_tuple does.  The reason is that having more than one
- * transaction walking the chain is probably uncommon enough that risk of
- * starvation is not likely: one of the preconditions for being here is that
- * the snapshot in use predates the update that created this tuple (because we
- * started at an earlier version of the tuple), but at the same time such a
- * transaction cannot be using repeatable read or serializable isolation
- * levels, because that would lead to a serializability failure.
- */
-static HTSU_Result
-heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid,
-						TransactionId xid, LockTupleMode mode)
-{
-	if (!ItemPointerEquals(&tuple->t_self, ctid))
-	{
-		/*
-		 * If this is the first possibly-multixact-able operation in the
-		 * current transaction, set my per-backend OldestMemberMXactId
-		 * setting. We can be certain that the transaction will never become a
-		 * member of any older MultiXactIds than that.  (We have to do this
-		 * even if we end up just using our own TransactionId below, since
-		 * some other backend could incorporate our XID into a MultiXact
-		 * immediately afterwards.)
-		 */
-		MultiXactIdSetOldestMember();
-
-		return heap_lock_updated_tuple_rec(rel, ctid, xid, mode);
-	}
-
-	/* nothing to lock */
-	return HeapTupleMayBeUpdated;
-}
-
-/*
- *	heap_finish_speculative - mark speculative insertion as successful
+ * Note that this might have side effects such as creating a new MultiXactId.
  *
- * To successfully finish a speculative insertion we have to clear speculative
- * token from tuple.  To do so the t_ctid field, which will contain a
- * speculative token value, is modified in place to point to the tuple itself,
- * which is characteristic of a newly inserted ordinary tuple.
+ * Most callers will have called HeapTupleSatisfiesUpdate before this function;
+ * that will have set the HEAP_XMAX_INVALID bit if the xmax was a MultiXactId
+ * but it was not running anymore. There is a race condition, which is that the
+ * MultiXactId may have finished since then, but that uncommon case is handled
+ * either here, or within MultiXactIdExpand.
  *
- * NB: It is not ok to commit without either finishing or aborting a
- * speculative insertion.  We could treat speculative tuples of committed
- * transactions implicitly as completed, but then we would have to be prepared
- * to deal with speculative tokens on committed tuples.  That wouldn't be
- * difficult - no-one looks at the ctid field of a tuple with invalid xmax -
- * but clearing the token at completion isn't very expensive either.
- * An explicit confirmation WAL record also makes logical decoding simpler.
+ * There is a similar race condition possible when the old xmax was a regular
+ * TransactionId.  We test TransactionIdIsInProgress again just to narrow the
+ * window, but it's still possible to end up creating an unnecessary
+ * MultiXactId.  Fortunately this is harmless.
  */
 void
-heap_finish_speculative(Relation relation, HeapTuple tuple)
+compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
+						  uint16 old_infomask2, TransactionId add_to_xmax,
+						  LockTupleMode mode, bool is_update,
+						  TransactionId *result_xmax, uint16 *result_infomask,
+						  uint16 *result_infomask2)
 {
-	Buffer		buffer;
-	Page		page;
-	OffsetNumber offnum;
-	ItemId		lp = NULL;
-	HeapTupleHeader htup;
-
-	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&(tuple->t_self)));
-	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
-	page = (Page) BufferGetPage(buffer);
-
-	offnum = ItemPointerGetOffsetNumber(&(tuple->t_self));
-	if (PageGetMaxOffsetNumber(page) >= offnum)
-		lp = PageGetItemId(page, offnum);
-
-	if (PageGetMaxOffsetNumber(page) < offnum || !ItemIdIsNormal(lp))
-		elog(ERROR, "invalid lp");
-
-	htup = (HeapTupleHeader) PageGetItem(page, lp);
-
-	/* SpecTokenOffsetNumber should be distinguishable from any real offset */
-	StaticAssertStmt(MaxOffsetNumber < SpecTokenOffsetNumber,
-					 "invalid speculative token constant");
-
-	/* NO EREPORT(ERROR) from here till changes are logged */
-	START_CRIT_SECTION();
-
-	Assert(HeapTupleHeaderIsSpeculative(tuple->t_data));
-
-	MarkBufferDirty(buffer);
+	TransactionId new_xmax;
+	uint16		new_infomask,
+				new_infomask2;
 
-	/*
-	 * Replace the speculative insertion token with a real t_ctid, pointing to
-	 * itself like it does on regular tuples.
-	 */
-	htup->t_ctid = tuple->t_self;
+	Assert(TransactionIdIsCurrentTransactionId(add_to_xmax));
 
-	/* XLOG stuff */
-	if (RelationNeedsWAL(relation))
+l5:
+	new_infomask = 0;
+	new_infomask2 = 0;
+	if (old_infomask & HEAP_XMAX_INVALID)
 	{
-		xl_heap_confirm xlrec;
-		XLogRecPtr	recptr;
-
-		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
-
-		XLogBeginInsert();
-
-		/* We want the same filtering on this as on a plain insert */
-		XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
-
-		XLogRegisterData((char *) &xlrec, SizeOfHeapConfirm);
-		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
-
-		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_CONFIRM);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	UnlockReleaseBuffer(buffer);
-}
-
-/*
- *	heap_abort_speculative - kill a speculatively inserted tuple
- *
- * Marks a tuple that was speculatively inserted in the same command as dead,
- * by setting its xmin as invalid.  That makes it immediately appear as dead
- * to all transactions, including our own.  In particular, it makes
- * HeapTupleSatisfiesDirty() regard the tuple as dead, so that another backend
- * inserting a duplicate key value won't unnecessarily wait for our whole
- * transaction to finish (it'll just wait for our speculative insertion to
- * finish).
- *
- * Killing the tuple prevents "unprincipled deadlocks", which are deadlocks
- * that arise due to a mutual dependency that is not user visible.  By
- * definition, unprincipled deadlocks cannot be prevented by the user
- * reordering lock acquisition in client code, because the implementation level
- * lock acquisitions are not under the user's direct control.  If speculative
- * inserters did not take this precaution, then under high concurrency they
- * could deadlock with each other, which would not be acceptable.
- *
- * This is somewhat redundant with heap_delete, but we prefer to have a
- * dedicated routine with stripped down requirements.  Note that this is also
- * used to delete the TOAST tuples created during speculative insertion.
- *
- * This routine does not affect logical decoding as it only looks at
- * confirmation records.
- */
-void
-heap_abort_speculative(Relation relation, HeapTuple tuple)
-{
-	TransactionId xid = GetCurrentTransactionId();
-	ItemPointer tid = &(tuple->t_self);
-	ItemId		lp;
-	HeapTupleData tp;
-	Page		page;
-	BlockNumber block;
-	Buffer		buffer;
-
-	Assert(ItemPointerIsValid(tid));
-
-	block = ItemPointerGetBlockNumber(tid);
-	buffer = ReadBuffer(relation, block);
-	page = BufferGetPage(buffer);
-
-	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
-
-	/*
-	 * Page can't be all visible, we just inserted into it, and are still
-	 * running.
-	 */
-	Assert(!PageIsAllVisible(page));
-
-	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
-	Assert(ItemIdIsNormal(lp));
-
-	tp.t_tableOid = RelationGetRelid(relation);
-	tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	tp.t_len = ItemIdGetLength(lp);
-	tp.t_self = *tid;
-
-	/*
-	 * Sanity check that the tuple really is a speculatively inserted tuple,
-	 * inserted by us.
-	 */
-	if (tp.t_data->t_choice.t_heap.t_xmin != xid)
-		elog(ERROR, "attempted to kill a tuple inserted by another transaction");
-	if (!(IsToastRelation(relation) || HeapTupleHeaderIsSpeculative(tp.t_data)))
-		elog(ERROR, "attempted to kill a non-speculative tuple");
-	Assert(!HeapTupleHeaderIsHeapOnly(tp.t_data));
+		/*
+		 * No previous locker; we just insert our own TransactionId.
+		 *
+		 * Note that it's critical that this case be the first one checked,
+		 * because there are several blocks below that come back to this one
+		 * to implement certain optimizations; old_infomask might contain
+		 * other dirty bits in those cases, but we don't really care.
+		 */
+		if (is_update)
+		{
+			new_xmax = add_to_xmax;
+			if (mode == LockTupleExclusive)
+				new_infomask2 |= HEAP_KEYS_UPDATED;
+		}
+		else
+		{
+			new_infomask |= HEAP_XMAX_LOCK_ONLY;
+			switch (mode)
+			{
+				case LockTupleKeyShare:
+					new_xmax = add_to_xmax;
+					new_infomask |= HEAP_XMAX_KEYSHR_LOCK;
+					break;
+				case LockTupleShare:
+					new_xmax = add_to_xmax;
+					new_infomask |= HEAP_XMAX_SHR_LOCK;
+					break;
+				case LockTupleNoKeyExclusive:
+					new_xmax = add_to_xmax;
+					new_infomask |= HEAP_XMAX_EXCL_LOCK;
+					break;
+				case LockTupleExclusive:
+					new_xmax = add_to_xmax;
+					new_infomask |= HEAP_XMAX_EXCL_LOCK;
+					new_infomask2 |= HEAP_KEYS_UPDATED;
+					break;
+				default:
+					new_xmax = InvalidTransactionId;	/* silence compiler */
+					elog(ERROR, "invalid lock mode");
+			}
+		}
+	}
+	else if (old_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		MultiXactStatus new_status;
 
-	/*
-	 * No need to check for serializable conflicts here.  There is never a
-	 * need for a combocid, either.  No need to extract replica identity, or
-	 * do anything special with infomask bits.
-	 */
+		/*
+		 * Currently we don't allow XMAX_COMMITTED to be set for multis, so
+		 * cross-check.
+		 */
+		Assert(!(old_infomask & HEAP_XMAX_COMMITTED));
 
-	START_CRIT_SECTION();
+		/*
+		 * A multixact together with LOCK_ONLY set but neither lock bit set
+		 * (i.e. a pg_upgraded share locked tuple) cannot possibly be running
+		 * anymore.  This check is critical for databases upgraded by
+		 * pg_upgrade; both MultiXactIdIsRunning and MultiXactIdExpand assume
+		 * that such multis are never passed.
+		 */
+		if (HEAP_LOCKED_UPGRADED(old_infomask))
+		{
+			old_infomask &= ~HEAP_XMAX_IS_MULTI;
+			old_infomask |= HEAP_XMAX_INVALID;
+			goto l5;
+		}
 
-	/*
-	 * The tuple will become DEAD immediately.  Flag that this page
-	 * immediately is a candidate for pruning by setting xmin to
-	 * RecentGlobalXmin.  That's not pretty, but it doesn't seem worth
-	 * inventing a nicer API for this.
-	 */
-	Assert(TransactionIdIsValid(RecentGlobalXmin));
-	PageSetPrunable(page, RecentGlobalXmin);
+		/*
+		 * If the XMAX is already a MultiXactId, then we need to expand it to
+		 * include add_to_xmax; but if all the members were lockers and are
+		 * all gone, we can do away with the IS_MULTI bit and just set
+		 * add_to_xmax as the only locker/updater.  If all lockers are gone
+		 * and we have an updater that aborted, we can also do without a
+		 * multi.
+		 *
+		 * The cost of doing GetMultiXactIdMembers would be paid by
+		 * MultiXactIdExpand if we weren't to do this, so this check is not
+		 * incurring extra work anyhow.
+		 */
+		if (!MultiXactIdIsRunning(xmax, HEAP_XMAX_IS_LOCKED_ONLY(old_infomask)))
+		{
+			if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) ||
+				!TransactionIdDidCommit(MultiXactIdGetUpdateXid(xmax,
+																old_infomask)))
+			{
+				/*
+				 * Reset these bits and restart; otherwise fall through to
+				 * create a new multi below.
+				 */
+				old_infomask &= ~HEAP_XMAX_IS_MULTI;
+				old_infomask |= HEAP_XMAX_INVALID;
+				goto l5;
+			}
+		}
 
-	/* store transaction information of xact deleting the tuple */
-	tp.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
-	tp.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+		new_status = get_mxact_status_for_lock(mode, is_update);
 
-	/*
-	 * Set the tuple header xmin to InvalidTransactionId.  This makes the
-	 * tuple immediately invisible everyone.  (In particular, to any
-	 * transactions waiting on the speculative token, woken up later.)
-	 */
-	HeapTupleHeaderSetXmin(tp.t_data, InvalidTransactionId);
+		new_xmax = MultiXactIdExpand((MultiXactId) xmax, add_to_xmax,
+									 new_status);
+		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
+	}
+	else if (old_infomask & HEAP_XMAX_COMMITTED)
+	{
+		/*
+		 * It's a committed update, so we need to preserve him as updater of
+		 * the tuple.
+		 */
+		MultiXactStatus status;
+		MultiXactStatus new_status;
 
-	/* Clear the speculative insertion token too */
-	tp.t_data->t_ctid = tp.t_self;
+		if (old_infomask2 & HEAP_KEYS_UPDATED)
+			status = MultiXactStatusUpdate;
+		else
+			status = MultiXactStatusNoKeyUpdate;
 
-	MarkBufferDirty(buffer);
+		new_status = get_mxact_status_for_lock(mode, is_update);
 
-	/*
-	 * XLOG stuff
-	 *
-	 * The WAL records generated here match heap_delete().  The same recovery
-	 * routines are used.
-	 */
-	if (RelationNeedsWAL(relation))
+		/*
+		 * since it's not running, it's obviously impossible for the old
+		 * updater to be identical to the current one, so we need not check
+		 * for that case as we do in the block above.
+		 */
+		new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
+		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
+	}
+	else if (TransactionIdIsInProgress(xmax))
 	{
-		xl_heap_delete xlrec;
-		XLogRecPtr	recptr;
+		/*
+		 * If the XMAX is a valid, in-progress TransactionId, then we need to
+		 * create a new MultiXactId that includes both the old locker or
+		 * updater and our own TransactionId.
+		 */
+		MultiXactStatus new_status;
+		MultiXactStatus old_status;
+		LockTupleMode old_mode;
 
-		xlrec.flags = XLH_DELETE_IS_SUPER;
-		xlrec.infobits_set = compute_infobits(tp.t_data->t_infomask,
-											  tp.t_data->t_infomask2);
-		xlrec.offnum = ItemPointerGetOffsetNumber(&tp.t_self);
-		xlrec.xmax = xid;
+		if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
+		{
+			if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
+				old_status = MultiXactStatusForKeyShare;
+			else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
+				old_status = MultiXactStatusForShare;
+			else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
+			{
+				if (old_infomask2 & HEAP_KEYS_UPDATED)
+					old_status = MultiXactStatusForUpdate;
+				else
+					old_status = MultiXactStatusForNoKeyUpdate;
+			}
+			else
+			{
+				/*
+				 * LOCK_ONLY can be present alone only when a page has been
+				 * upgraded by pg_upgrade.  But in that case,
+				 * TransactionIdIsInProgress() should have returned false.  We
+				 * assume it's no longer locked in this case.
+				 */
+				elog(WARNING, "LOCK_ONLY found for Xid in progress %u", xmax);
+				old_infomask |= HEAP_XMAX_INVALID;
+				old_infomask &= ~HEAP_XMAX_LOCK_ONLY;
+				goto l5;
+			}
+		}
+		else
+		{
+			/* it's an update, but which kind? */
+			if (old_infomask2 & HEAP_KEYS_UPDATED)
+				old_status = MultiXactStatusUpdate;
+			else
+				old_status = MultiXactStatusNoKeyUpdate;
+		}
 
-		XLogBeginInsert();
-		XLogRegisterData((char *) &xlrec, SizeOfHeapDelete);
-		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
+		old_mode = TUPLOCK_from_mxstatus(old_status);
+
+		/*
+		 * If the lock to be acquired is for the same TransactionId as the
+		 * existing lock, there's an optimization possible: consider only the
+		 * strongest of both locks as the only one present, and restart.
+		 */
+		if (xmax == add_to_xmax)
+		{
+			/*
+			 * Note that it's not possible for the original tuple to be
+			 * updated: we wouldn't be here because the tuple would have been
+			 * invisible and we wouldn't try to update it.  As a subtlety,
+			 * this code can also run when traversing an update chain to lock
+			 * future versions of a tuple.  But we wouldn't be here either,
+			 * because the add_to_xmax would be different from the original
+			 * updater.
+			 */
+			Assert(HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
 
-		/* No replica identity & replication origin logged */
+			/* acquire the strongest of both */
+			if (mode < old_mode)
+				mode = old_mode;
+			/* mustn't touch is_update */
 
-		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_DELETE);
+			old_infomask |= HEAP_XMAX_INVALID;
+			goto l5;
+		}
 
-		PageSetLSN(page, recptr);
+		/* otherwise, just fall back to creating a new multixact */
+		new_status = get_mxact_status_for_lock(mode, is_update);
+		new_xmax = MultiXactIdCreate(xmax, old_status,
+									 add_to_xmax, new_status);
+		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
 	}
+	else if (!HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) &&
+			 TransactionIdDidCommit(xmax))
+	{
+		/*
+		 * It's a committed update, so we gotta preserve him as updater of the
+		 * tuple.
+		 */
+		MultiXactStatus status;
+		MultiXactStatus new_status;
 
-	END_CRIT_SECTION();
+		if (old_infomask2 & HEAP_KEYS_UPDATED)
+			status = MultiXactStatusUpdate;
+		else
+			status = MultiXactStatusNoKeyUpdate;
 
-	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+		new_status = get_mxact_status_for_lock(mode, is_update);
 
-	if (HeapTupleHasExternal(&tp))
+		/*
+		 * since it's not running, it's obviously impossible for the old
+		 * updater to be identical to the current one, so we need not check
+		 * for that case as we do in the block above.
+		 */
+		new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
+		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
+	}
+	else
 	{
-		Assert(!IsToastRelation(relation));
-		toast_delete(relation, &tp, true);
+		/*
+		 * Can get here iff the locking/updating transaction was running when
+		 * the infomask was extracted from the tuple, but finished before
+		 * TransactionIdIsInProgress got to run.  Deal with it as if there was
+		 * no locker at all in the first place.
+		 */
+		old_infomask |= HEAP_XMAX_INVALID;
+		goto l5;
 	}
 
-	/*
-	 * Never need to mark tuple for invalidation, since catalogs don't support
-	 * speculative insertion
-	 */
+	*result_infomask = new_infomask;
+	*result_infomask2 = new_infomask2;
+	*result_xmax = new_xmax;
+}
 
-	/* Now we can release the buffer */
-	ReleaseBuffer(buffer);
 
-	/* count deletion, as we counted the insertion too */
-	pgstat_count_heap_delete(relation);
-}
 
 /*
  * heap_inplace_update - update a tuple "in place" (ie, overwrite it)
@@ -7066,7 +5015,7 @@ HeapTupleGetUpdateXid(HeapTupleHeader tuple)
  *
  * The passed infomask pairs up with the given multixact in the tuple header.
  */
-static bool
+bool
 DoesMultiXactIdConflict(MultiXactId multi, uint16 infomask,
 						LockTupleMode lockmode)
 {
@@ -7233,7 +5182,7 @@ Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
  * We return (in *remaining, if not NULL) the number of members that are still
  * running, including any (non-aborted) subtransactions of our own transaction.
  */
-static void
+void
 MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
 				Relation rel, ItemPointer ctid, XLTW_Oper oper,
 				int *remaining)
@@ -7255,7 +5204,7 @@ MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
  * We return (in *remaining, if not NULL) the number of members that are still
  * running, including any (non-aborted) subtransactions of our own transaction.
  */
-static bool
+bool
 ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
 						   uint16 infomask, Relation rel, int *remaining)
 {
@@ -7817,7 +5766,7 @@ log_heap_update(Relation reln, Buffer oldbuf,
  * This is only used in wal_level >= WAL_LEVEL_LOGICAL, and only for catalog
  * tuples.
  */
-static XLogRecPtr
+XLogRecPtr
 log_heap_new_cid(Relation relation, HeapTuple tup)
 {
 	xl_heap_new_cid xlrec;
@@ -9193,46 +7142,6 @@ heap2_redo(XLogReaderState *record)
 	}
 }
 
-/*
- *	heap_sync		- sync a heap, for use when no WAL has been written
- *
- * This forces the heap contents (including TOAST heap if any) down to disk.
- * If we skipped using WAL, and WAL is otherwise needed, we must force the
- * relation down to disk before it's safe to commit the transaction.  This
- * requires writing out any dirty buffers and then doing a forced fsync.
- *
- * Indexes are not touched.  (Currently, index operations associated with
- * the commands that use this are WAL-logged and so do not need fsync.
- * That behavior might change someday, but in any case it's likely that
- * any fsync decisions required would be per-index and hence not appropriate
- * to be done here.)
- */
-void
-heap_sync(Relation rel)
-{
-	/* non-WAL-logged tables never need fsync */
-	if (!RelationNeedsWAL(rel))
-		return;
-
-	/* main heap */
-	FlushRelationBuffers(rel);
-	/* FlushRelationBuffers will have opened rd_smgr */
-	smgrimmedsync(rel->rd_smgr, MAIN_FORKNUM);
-
-	/* FSM is not critical, don't bother syncing it */
-
-	/* toast heap, if any */
-	if (OidIsValid(rel->rd_rel->reltoastrelid))
-	{
-		Relation	toastrel;
-
-		toastrel = heap_open(rel->rd_rel->reltoastrelid, AccessShareLock);
-		FlushRelationBuffers(toastrel);
-		smgrimmedsync(toastrel->rd_smgr, MAIN_FORKNUM);
-		heap_close(toastrel, AccessShareLock);
-	}
-}
-
 /*
  * Mask a heap page before performing consistency checks on it.
  */
diff --git a/src/backend/access/heap/heapam_common.c b/src/backend/access/heap/heapam_common.c
index 6ebe0b290c..342c575b8c 100644
--- a/src/backend/access/heap/heapam_common.c
+++ b/src/backend/access/heap/heapam_common.c
@@ -24,9 +24,11 @@
 #include "access/heapam.h"
 #include "access/heapam_common.h"
 #include "access/htup_details.h"
+#include "access/storageamapi.h"
 #include "access/transam.h"
 #include "access/xact.h"
 #include "access/xlog.h"
+#include "executor/tuptable.h"
 #include "storage/bufmgr.h"
 #include "storage/procarray.h"
 
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index a874e243c9..d6352df472 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -48,6 +48,18 @@
 #include "utils/rel.h"
 #include "utils/tqual.h"
 
+/* local functions */
+static bool heap_fetch(Relation relation,
+		   ItemPointer tid,
+		   Snapshot snapshot,
+		   HeapTuple tuple,
+		   Buffer *userbuf,
+		   bool keep_buf,
+		   Relation stats_relation);
+
+static void heapam_finish_speculative(Relation relation, TupleTableSlot *slot);
+static void heapam_abort_speculative(Relation relation, TupleTableSlot *slot);
+
 /*-------------------------------------------------------------------------
  *
  * POSTGRES "time qualification" code, ie, tuple visibility rules.
@@ -1688,10 +1700,2196 @@ HeapTupleSatisfies(StorageTuple stup, Snapshot snapshot, Buffer buffer)
 	}
 }
 
-Datum
-heapam_storage_handler(PG_FUNCTION_ARGS)
+
+/*
+ *	heap_fetch		- retrieve tuple with given tid
+ *
+ * On entry, tuple->t_self is the TID to fetch.  We pin the buffer holding
+ * the tuple, fill in the remaining fields of *tuple, and check the tuple
+ * against the specified snapshot.
+ *
+ * If successful (tuple found and passes snapshot time qual), then *userbuf
+ * is set to the buffer holding the tuple and TRUE is returned.  The caller
+ * must unpin the buffer when done with the tuple.
+ *
+ * If the tuple is not found (ie, item number references a deleted slot),
+ * then tuple->t_data is set to NULL and FALSE is returned.
+ *
+ * If the tuple is found but fails the time qual check, then FALSE is returned
+ * but tuple->t_data is left pointing to the tuple.
+ *
+ * keep_buf determines what is done with the buffer in the FALSE-result cases.
+ * When the caller specifies keep_buf = true, we retain the pin on the buffer
+ * and return it in *userbuf (so the caller must eventually unpin it); when
+ * keep_buf = false, the pin is released and *userbuf is set to InvalidBuffer.
+ *
+ * stats_relation is the relation to charge the heap_fetch operation against
+ * for statistical purposes.  (This could be the heap rel itself, an
+ * associated index, or NULL to not count the fetch at all.)
+ *
+ * heap_fetch does not follow HOT chains: only the exact TID requested will
+ * be fetched.
+ *
+ * It is somewhat inconsistent that we ereport() on invalid block number but
+ * return false on invalid item number.  There are a couple of reasons though.
+ * One is that the caller can relatively easily check the block number for
+ * validity, but cannot check the item number without reading the page
+ * himself.  Another is that when we are following a t_ctid link, we can be
+ * reasonably confident that the page number is valid (since VACUUM shouldn't
+ * truncate off the destination page without having killed the referencing
+ * tuple first), but the item number might well not be good.
+ */
+static bool
+heap_fetch(Relation relation,
+		   ItemPointer tid,
+		   Snapshot snapshot,
+		   HeapTuple tuple,
+		   Buffer *userbuf,
+		   bool keep_buf,
+		   Relation stats_relation)
 {
-	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
+	ItemId		lp;
+	Buffer		buffer;
+	Page		page;
+	OffsetNumber offnum;
+	bool		valid;
+
+	/*
+	 * Fetch and pin the appropriate page of the relation.
+	 */
+	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
+
+	/*
+	 * Need share lock on buffer to examine tuple commit status.
+	 */
+	LockBuffer(buffer, BUFFER_LOCK_SHARE);
+	page = BufferGetPage(buffer);
+	TestForOldSnapshot(snapshot, relation, page);
+
+	/*
+	 * We'd better check for out-of-range offnum in case of VACUUM since the
+	 * TID was obtained.
+	 */
+	offnum = ItemPointerGetOffsetNumber(tid);
+	if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
+	{
+		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+		if (keep_buf)
+			*userbuf = buffer;
+		else
+		{
+			ReleaseBuffer(buffer);
+			*userbuf = InvalidBuffer;
+		}
+		return false;
+	}
+
+	/*
+	 * get the item line pointer corresponding to the requested tid
+	 */
+	lp = PageGetItemId(page, offnum);
+
+	/*
+	 * Must check for deleted tuple.
+	 */
+	if (!ItemIdIsNormal(lp))
+	{
+		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+		if (keep_buf)
+			*userbuf = buffer;
+		else
+		{
+			ReleaseBuffer(buffer);
+			*userbuf = InvalidBuffer;
+		}
+		return false;
+	}
+
+	/*
+	 * 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);
+
+	/*
+	 * check time qualification of tuple, then release lock
+	 */
+	valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, tuple, snapshot, buffer);
+
+	if (valid)
+		PredicateLockTuple(relation, tuple, snapshot);
+
+	CheckForSerializableConflictOut(valid, relation, tuple, buffer, snapshot);
+
+	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+
+	if (valid)
+	{
+		/*
+		 * All checks passed, so return the tuple as valid. Caller is now
+		 * responsible for releasing the buffer.
+		 */
+		*userbuf = buffer;
+
+		/* Count the successful fetch against appropriate rel, if any */
+		if (stats_relation != NULL)
+			pgstat_count_heap_fetch(stats_relation);
+
+		return true;
+	}
+
+	/* Tuple failed time qual, but maybe caller wants to see it anyway. */
+	if (keep_buf)
+		*userbuf = buffer;
+	else
+	{
+		ReleaseBuffer(buffer);
+		*userbuf = InvalidBuffer;
+	}
+
+	return false;
+}
+
+
+
+
+
+/* ----------------------------------------------------------------
+ *				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)
+				heapam_finish_speculative(relation, slot);
+			else
+			{
+				heapam_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->es_result_relation_info->ri_NumIndices > 0) &&
+		(!HeapTupleIsHeapOnly(tuple)))
+		*recheckIndexes = (IndexFunc) (slot, estate, false, NULL, NIL);
+
+	return result;
+}
+
+static void
+heapam_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);
+
+	offnum = ItemPointerGetOffsetNumber(&(tuple->t_self));
+	if (PageGetMaxOffsetNumber(page) >= offnum)
+		lp = PageGetItemId(page, offnum);
+
+	if (PageGetMaxOffsetNumber(page) < offnum || !ItemIdIsNormal(lp))
+		elog(ERROR, "invalid lp");
+
+	htup = (HeapTupleHeader) PageGetItem(page, lp);
+
+	/* SpecTokenOffsetNumber should be distinguishable from any real offset */
+	StaticAssertStmt(MaxOffsetNumber < SpecTokenOffsetNumber,
+					 "invalid speculative token constant");
+
+	/* NO EREPORT(ERROR) from here till changes are logged */
+	START_CRIT_SECTION();
+
+	Assert(HeapTupleHeaderIsSpeculative(tuple->t_data));
+
+	MarkBufferDirty(buffer);
+
+	/*
+	 * Replace the speculative insertion token with a real t_ctid, pointing to
+	 * itself like it does on regular tuples.
+	 */
+	htup->t_ctid = tuple->t_self;
+
+	/* XLOG stuff */
+	if (RelationNeedsWAL(relation))
+	{
+		xl_heap_confirm xlrec;
+		XLogRecPtr	recptr;
+
+		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
+
+		XLogBeginInsert();
+
+		/* We want the same filtering on this as on a plain insert */
+		XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
+
+		XLogRegisterData((char *) &xlrec, SizeOfHeapConfirm);
+		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
+
+		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_CONFIRM);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(buffer);
+	slot->tts_speculativeToken = 0;
+}
+
+static void
+heapam_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;
+	HeapTupleData tp;
+	Page		page;
+	BlockNumber block;
+	Buffer		buffer;
+
+	/*
+	 * Assert(slot->tts_speculativeToken != 0); This needs some update in
+	 * toast
+	 */
+	Assert(ItemPointerIsValid(tid));
+
+	block = ItemPointerGetBlockNumber(tid);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
+
+	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+
+	/*
+	 * Page can't be all visible, we just inserted into it, and are still
+	 * running.
+	 */
+	Assert(!PageIsAllVisible(page));
+
+	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
+	Assert(ItemIdIsNormal(lp));
+
+	tp.t_tableOid = RelationGetRelid(relation);
+	tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	tp.t_len = ItemIdGetLength(lp);
+	tp.t_self = *tid;
+
+	/*
+	 * Sanity check that the tuple really is a speculatively inserted tuple,
+	 * inserted by us.
+	 */
+	if (tp.t_data->t_choice.t_heap.t_xmin != xid)
+		elog(ERROR, "attempted to kill a tuple inserted by another transaction");
+	if (!(IsToastRelation(relation) || HeapTupleHeaderIsSpeculative(tp.t_data)))
+		elog(ERROR, "attempted to kill a non-speculative tuple");
+	Assert(!HeapTupleHeaderIsHeapOnly(tp.t_data));
+
+	/*
+	 * No need to check for serializable conflicts here.  There is never a
+	 * need for a combocid, either.  No need to extract replica identity, or
+	 * do anything special with infomask bits.
+	 */
+
+	START_CRIT_SECTION();
+
+	/*
+	 * The tuple will become DEAD immediately.  Flag that this page
+	 * immediately is a candidate for pruning by setting xmin to
+	 * RecentGlobalXmin.  That's not pretty, but it doesn't seem worth
+	 * inventing a nicer API for this.
+	 */
+	Assert(TransactionIdIsValid(RecentGlobalXmin));
+	PageSetPrunable(page, RecentGlobalXmin);
+
+	/* store transaction information of xact deleting the tuple */
+	tp.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+	tp.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+
+	/*
+	 * Set the tuple header xmin to InvalidTransactionId.  This makes the
+	 * tuple immediately invisible everyone.  (In particular, to any
+	 * transactions waiting on the speculative token, woken up later.)
+	 */
+	HeapTupleHeaderSetXmin(tp.t_data, InvalidTransactionId);
+
+	/* Clear the speculative insertion token too */
+	tp.t_data->t_ctid = tp.t_self;
+
+	MarkBufferDirty(buffer);
+
+	/*
+	 * XLOG stuff
+	 *
+	 * The WAL records generated here match heap_delete().  The same recovery
+	 * routines are used.
+	 */
+	if (RelationNeedsWAL(relation))
+	{
+		xl_heap_delete xlrec;
+		XLogRecPtr	recptr;
+
+		xlrec.flags = XLH_DELETE_IS_SUPER;
+		xlrec.infobits_set = compute_infobits(tp.t_data->t_infomask,
+											  tp.t_data->t_infomask2);
+		xlrec.offnum = ItemPointerGetOffsetNumber(&tp.t_self);
+		xlrec.xmax = xid;
+
+		XLogBeginInsert();
+		XLogRegisterData((char *) &xlrec, SizeOfHeapDelete);
+		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
+
+		/* No replica identity & replication origin logged */
+
+		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_DELETE);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+
+	if (HeapTupleHasExternal(&tp))
+	{
+		Assert(!IsToastRelation(relation));
+		toast_delete(relation, &tp, true);
+	}
+
+	/*
+	 * Never need to mark tuple for invalidation, since catalogs don't support
+	 * speculative insertion
+	 */
+
+	/* Now we can release the buffer */
+	ReleaseBuffer(buffer);
+
+	/* count deletion, as we counted the insertion too */
+	pgstat_count_heap_delete(relation);
+	slot->tts_speculativeToken = 0;
+}
+
+/*
+ *	heapam_multi_insert	- insert multiple tuple into a heap
+ *
+ * This is like heap_insert(), but inserts multiple tuples in one operation.
+ * That's faster than calling heap_insert() in a loop, because when multiple
+ * tuples can be inserted on a single page, we can write just a single WAL
+ * record covering all of them, and only need to lock/unlock the page once.
+ *
+ * Note: this leaks memory into the current memory context. You can create a
+ * temporary context before calling this, if that's a problem.
+ */
+static void
+heapam_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
+					CommandId cid, int options, BulkInsertState bistate)
+{
+	TransactionId xid = GetCurrentTransactionId();
+	HeapTuple  *heaptuples;
+	int			i;
+	int			ndone;
+	char	   *scratch = NULL;
+	Page		page;
+	bool		needwal;
+	Size		saveFreeSpace;
+	bool		need_tuple_data = RelationIsLogicallyLogged(relation);
+	bool		need_cids = RelationIsAccessibleInLogicalDecoding(relation);
+
+	needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation);
+	saveFreeSpace = RelationGetTargetPageFreeSpace(relation,
+												   HEAP_DEFAULT_FILLFACTOR);
+
+	/* Toast and set header data in all the tuples */
+	heaptuples = palloc(ntuples * sizeof(HeapTuple));
+	for (i = 0; i < ntuples; i++)
+		heaptuples[i] = heap_prepare_insert(relation, tuples[i],
+											xid, cid, options);
+
+	/*
+	 * Allocate some memory to use for constructing the WAL record. Using
+	 * palloc() within a critical section is not safe, so we allocate this
+	 * beforehand.
+	 */
+	if (needwal)
+		scratch = palloc(BLCKSZ);
+
+	/*
+	 * We're about to do the actual inserts -- but check for conflict first,
+	 * to minimize the possibility of having to roll back work we've just
+	 * done.
+	 *
+	 * A check here does not definitively prevent a serialization anomaly;
+	 * that check MUST be done at least past the point of acquiring an
+	 * exclusive buffer content lock on every buffer that will be affected,
+	 * and MAY be done after all inserts are reflected in the buffers and
+	 * those locks are released; otherwise there race condition.  Since
+	 * multiple buffers can be locked and unlocked in the loop below, and it
+	 * would not be feasible to identify and lock all of those buffers before
+	 * the loop, we must do a final check at the end.
+	 *
+	 * The check here could be omitted with no loss of correctness; it is
+	 * present strictly as an optimization.
+	 *
+	 * For heap inserts, we only need to check for table-level SSI locks. Our
+	 * new tuples can't possibly conflict with existing tuple locks, and heap
+	 * page locks are only consolidated versions of tuple locks; they do not
+	 * lock "gaps" as index page locks do.  So we don't need to specify a
+	 * buffer when making the call, which makes for a faster check.
+	 */
+	CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
+
+	ndone = 0;
+	while (ndone < ntuples)
+	{
+		Buffer		buffer;
+		Buffer		vmbuffer = InvalidBuffer;
+		bool		all_visible_cleared = false;
+		int			nthispage;
+
+		CHECK_FOR_INTERRUPTS();
+
+		/*
+		 * Find buffer where at least the next tuple will fit.  If the page is
+		 * all-visible, this will also pin the requisite visibility map page.
+		 */
+		buffer = RelationGetBufferForTuple(relation, heaptuples[ndone]->t_len,
+										   InvalidBuffer, options, bistate,
+										   &vmbuffer, NULL);
+		page = BufferGetPage(buffer);
+
+		/* NO EREPORT(ERROR) from here till changes are logged */
+		START_CRIT_SECTION();
+
+		/*
+		 * RelationGetBufferForTuple has ensured that the first tuple fits.
+		 * Put that on the page, and then as many other tuples as fit.
+		 */
+		RelationPutHeapTuple(relation, buffer, heaptuples[ndone], false);
+		for (nthispage = 1; ndone + nthispage < ntuples; nthispage++)
+		{
+			HeapTuple	heaptup = heaptuples[ndone + nthispage];
+
+			if (PageGetHeapFreeSpace(page) < MAXALIGN(heaptup->t_len) + saveFreeSpace)
+				break;
+
+			RelationPutHeapTuple(relation, buffer, heaptup, false);
+
+			/*
+			 * We don't use heap_multi_insert for catalog tuples yet, but
+			 * better be prepared...
+			 */
+			if (needwal && need_cids)
+				log_heap_new_cid(relation, heaptup);
+		}
+
+		if (PageIsAllVisible(page))
+		{
+			all_visible_cleared = true;
+			PageClearAllVisible(page);
+			visibilitymap_clear(relation,
+								BufferGetBlockNumber(buffer),
+								vmbuffer, VISIBILITYMAP_VALID_BITS);
+		}
+
+		/*
+		 * XXX Should we set PageSetPrunable on this page ? See heap_insert()
+		 */
+
+		MarkBufferDirty(buffer);
+
+		/* XLOG stuff */
+		if (needwal)
+		{
+			XLogRecPtr	recptr;
+			xl_heap_multi_insert *xlrec;
+			uint8		info = XLOG_HEAP2_MULTI_INSERT;
+			char	   *tupledata;
+			int			totaldatalen;
+			char	   *scratchptr = scratch;
+			bool		init;
+			int			bufflags = 0;
+
+			/*
+			 * If the page was previously empty, we can reinit the page
+			 * instead of restoring the whole thing.
+			 */
+			init = (ItemPointerGetOffsetNumber(&(heaptuples[ndone]->t_self)) == FirstOffsetNumber &&
+					PageGetMaxOffsetNumber(page) == FirstOffsetNumber + nthispage - 1);
+
+			/* allocate xl_heap_multi_insert struct from the scratch area */
+			xlrec = (xl_heap_multi_insert *) scratchptr;
+			scratchptr += SizeOfHeapMultiInsert;
+
+			/*
+			 * Allocate offsets array. Unless we're reinitializing the page,
+			 * in that case the tuples are stored in order starting at
+			 * FirstOffsetNumber and we don't need to store the offsets
+			 * explicitly.
+			 */
+			if (!init)
+				scratchptr += nthispage * sizeof(OffsetNumber);
+
+			/* the rest of the scratch space is used for tuple data */
+			tupledata = scratchptr;
+
+			xlrec->flags = all_visible_cleared ? XLH_INSERT_ALL_VISIBLE_CLEARED : 0;
+			xlrec->ntuples = nthispage;
+
+			/*
+			 * Write out an xl_multi_insert_tuple and the tuple data itself
+			 * for each tuple.
+			 */
+			for (i = 0; i < nthispage; i++)
+			{
+				HeapTuple	heaptup = heaptuples[ndone + i];
+				xl_multi_insert_tuple *tuphdr;
+				int			datalen;
+
+				if (!init)
+					xlrec->offsets[i] = ItemPointerGetOffsetNumber(&heaptup->t_self);
+				/* xl_multi_insert_tuple needs two-byte alignment. */
+				tuphdr = (xl_multi_insert_tuple *) SHORTALIGN(scratchptr);
+				scratchptr = ((char *) tuphdr) + SizeOfMultiInsertTuple;
+
+				tuphdr->t_infomask2 = heaptup->t_data->t_infomask2;
+				tuphdr->t_infomask = heaptup->t_data->t_infomask;
+				tuphdr->t_hoff = heaptup->t_data->t_hoff;
+
+				/* write bitmap [+ padding] [+ oid] + data */
+				datalen = heaptup->t_len - SizeofHeapTupleHeader;
+				memcpy(scratchptr,
+					   (char *) heaptup->t_data + SizeofHeapTupleHeader,
+					   datalen);
+				tuphdr->datalen = datalen;
+				scratchptr += datalen;
+			}
+			totaldatalen = scratchptr - tupledata;
+			Assert((scratchptr - scratch) < BLCKSZ);
+
+			if (need_tuple_data)
+				xlrec->flags |= XLH_INSERT_CONTAINS_NEW_TUPLE;
+
+			/*
+			 * Signal that this is the last xl_heap_multi_insert record
+			 * emitted by this call to heap_multi_insert(). Needed for logical
+			 * decoding so it knows when to cleanup temporary data.
+			 */
+			if (ndone + nthispage == ntuples)
+				xlrec->flags |= XLH_INSERT_LAST_IN_MULTI;
+
+			if (init)
+			{
+				info |= XLOG_HEAP_INIT_PAGE;
+				bufflags |= REGBUF_WILL_INIT;
+			}
+
+			/*
+			 * If we're doing logical decoding, include the new tuple data
+			 * even if we take a full-page image of the page.
+			 */
+			if (need_tuple_data)
+				bufflags |= REGBUF_KEEP_DATA;
+
+			XLogBeginInsert();
+			XLogRegisterData((char *) xlrec, tupledata - scratch);
+			XLogRegisterBuffer(0, buffer, REGBUF_STANDARD | bufflags);
+
+			XLogRegisterBufData(0, tupledata, totaldatalen);
+
+			/* filtering by origin on a row level is much more efficient */
+			XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
+
+			recptr = XLogInsert(RM_HEAP2_ID, info);
+
+			PageSetLSN(page, recptr);
+		}
+
+		END_CRIT_SECTION();
+
+		UnlockReleaseBuffer(buffer);
+		if (vmbuffer != InvalidBuffer)
+			ReleaseBuffer(vmbuffer);
+
+		ndone += nthispage;
+	}
+
+	/*
+	 * We're done with the actual inserts.  Check for conflicts again, to
+	 * ensure that all rw-conflicts in to these inserts are detected.  Without
+	 * this final check, a sequential scan of the heap may have locked the
+	 * table after the "before" check, missing one opportunity to detect the
+	 * conflict, and then scanned the table before the new tuples were there,
+	 * missing the other chance to detect the conflict.
+	 *
+	 * For heap inserts, we only need to check for table-level SSI locks. Our
+	 * new tuples can't possibly conflict with existing tuple locks, and heap
+	 * page locks are only consolidated versions of tuple locks; they do not
+	 * lock "gaps" as index page locks do.  So we don't need to specify a
+	 * buffer when making the call.
+	 */
+	CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
+
+	/*
+	 * If tuples are cachable, mark them for invalidation from the caches in
+	 * case we abort.  Note it is OK to do this after releasing the buffer,
+	 * because the heaptuples data structure is all in local memory, not in
+	 * the shared buffer.
+	 */
+	if (IsCatalogRelation(relation))
+	{
+		for (i = 0; i < ntuples; i++)
+			CacheInvalidateHeapTuple(relation, heaptuples[i], NULL);
+	}
+
+	/*
+	 * Copy t_self fields back to the caller's original tuples. This does
+	 * nothing for untoasted tuples (tuples[i] == heaptuples[i)], but it's
+	 * probably faster to always copy than check.
+	 */
+	for (i = 0; i < ntuples; i++)
+		tuples[i]->t_self = heaptuples[i]->t_self;
+
+	pgstat_count_heap_insert(relation, ntuples);
+}
+
+/*
+ * Subroutine for heap_lock_updated_tuple_rec.
+ *
+ * Given a hypothetical multixact status held by the transaction identified
+ * with the given xid, does the current transaction need to wait, fail, or can
+ * it continue if it wanted to acquire a lock of the given mode?  "needwait"
+ * is set to true if waiting is necessary; if it can continue, then
+ * HeapTupleMayBeUpdated is returned.  If the lock is already held by the
+ * current transaction, return HeapTupleSelfUpdated.  In case of a conflict
+ * with another transaction, a different HeapTupleSatisfiesUpdate return code
+ * is returned.
+ *
+ * The held status is said to be hypothetical because it might correspond to a
+ * lock held by a single Xid, i.e. not a real MultiXactId; we express it this
+ * way for simplicity of API.
+ */
+static HTSU_Result
+test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid,
+						   LockTupleMode mode, bool *needwait)
+{
+	MultiXactStatus wantedstatus;
+
+	*needwait = false;
+	wantedstatus = get_mxact_status_for_lock(mode, false);
+
+	/*
+	 * Note: we *must* check TransactionIdIsInProgress before
+	 * TransactionIdDidAbort/Commit; see comment at top of tqual.c for an
+	 * explanation.
+	 */
+	if (TransactionIdIsCurrentTransactionId(xid))
+	{
+		/*
+		 * The tuple has already been locked by our own transaction.  This is
+		 * very rare but can happen if multiple transactions are trying to
+		 * lock an ancient version of the same tuple.
+		 */
+		return HeapTupleSelfUpdated;
+	}
+	else if (TransactionIdIsInProgress(xid))
+	{
+		/*
+		 * If the locking transaction is running, what we do depends on
+		 * whether the lock modes conflict: if they do, then we must wait for
+		 * it to finish; otherwise we can fall through to lock this tuple
+		 * version without waiting.
+		 */
+		if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
+								LOCKMODE_from_mxstatus(wantedstatus)))
+		{
+			*needwait = true;
+		}
+
+		/*
+		 * If we set needwait above, then this value doesn't matter;
+		 * otherwise, this value signals to caller that it's okay to proceed.
+		 */
+		return HeapTupleMayBeUpdated;
+	}
+	else if (TransactionIdDidAbort(xid))
+		return HeapTupleMayBeUpdated;
+	else if (TransactionIdDidCommit(xid))
+	{
+		/*
+		 * The other transaction committed.  If it was only a locker, then the
+		 * lock is completely gone now and we can return success; but if it
+		 * was an update, then what we do depends on whether the two lock
+		 * modes conflict.  If they conflict, then we must report error to
+		 * caller. But if they don't, we can fall through to allow the current
+		 * transaction to lock the tuple.
+		 *
+		 * Note: the reason we worry about ISUPDATE here is because as soon as
+		 * a transaction ends, all its locks are gone and meaningless, and
+		 * thus we can ignore them; whereas its updates persist.  In the
+		 * TransactionIdIsInProgress case, above, we don't need to check
+		 * because we know the lock is still "alive" and thus a conflict needs
+		 * always be checked.
+		 */
+		if (!ISUPDATE_from_mxstatus(status))
+			return HeapTupleMayBeUpdated;
+
+		if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
+								LOCKMODE_from_mxstatus(wantedstatus)))
+			/* bummer */
+			return HeapTupleUpdated;
+
+		return HeapTupleMayBeUpdated;
+	}
+
+	/* Not in progress, not aborted, not committed -- must have crashed */
+	return HeapTupleMayBeUpdated;
+}
+
+
+/*
+ * Recursive part of heap_lock_updated_tuple
+ *
+ * Fetch the tuple pointed to by tid in rel, and mark it as locked by the given
+ * xid with the given mode; if this tuple is updated, recurse to lock the new
+ * version as well.
+ */
+static HTSU_Result
+heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid,
+							LockTupleMode mode)
+{
+	HTSU_Result result;
+	ItemPointerData tupid;
+	HeapTupleData mytup;
+	Buffer		buf;
+	uint16		new_infomask,
+				new_infomask2,
+				old_infomask,
+				old_infomask2;
+	TransactionId xmax,
+				new_xmax;
+	TransactionId priorXmax = InvalidTransactionId;
+	bool		cleared_all_frozen = false;
+	Buffer		vmbuffer = InvalidBuffer;
+	BlockNumber block;
+
+	ItemPointerCopy(tid, &tupid);
+
+	for (;;)
+	{
+		new_infomask = 0;
+		new_xmax = InvalidTransactionId;
+		block = ItemPointerGetBlockNumber(&tupid);
+
+		if (!heap_fetch(rel, &tupid, SnapshotAny, &mytup, &buf, false, NULL))
+		{
+			/*
+			 * if we fail to find the updated version of the tuple, it's
+			 * because it was vacuumed/pruned away after its creator
+			 * transaction aborted.  So behave as if we got to the end of the
+			 * chain, and there's no further tuple to lock: return success to
+			 * caller.
+			 */
+			return HeapTupleMayBeUpdated;
+		}
+
+l4:
+		CHECK_FOR_INTERRUPTS();
+
+		/*
+		 * Before locking the buffer, pin the visibility map page if it
+		 * appears to be necessary.  Since we haven't got the lock yet,
+		 * someone else might be in the middle of changing this, so we'll need
+		 * to recheck after we have the lock.
+		 */
+		if (PageIsAllVisible(BufferGetPage(buf)))
+			visibilitymap_pin(rel, block, &vmbuffer);
+		else
+			vmbuffer = InvalidBuffer;
+
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+		/*
+		 * If we didn't pin the visibility map page and the page has become
+		 * all visible while we were busy locking the buffer, we'll have to
+		 * unlock and re-lock, to avoid holding the buffer lock across I/O.
+		 * That's a bit unfortunate, but hopefully shouldn't happen often.
+		 */
+		if (vmbuffer == InvalidBuffer && PageIsAllVisible(BufferGetPage(buf)))
+		{
+			LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+			visibilitymap_pin(rel, block, &vmbuffer);
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+		}
+
+		/*
+		 * Check the tuple XMIN against prior XMAX, if any.  If we reached the
+		 * end of the chain, we're done, so return success.
+		 */
+		if (TransactionIdIsValid(priorXmax) &&
+			!HeapTupleUpdateXmaxMatchesXmin(priorXmax, mytup.t_data))
+		{
+			result = HeapTupleMayBeUpdated;
+			goto out_locked;
+		}
+
+		/*
+		 * Also check Xmin: if this tuple was created by an aborted
+		 * (sub)transaction, then we already locked the last live one in the
+		 * chain, thus we're done, so return success.
+		 */
+		if (TransactionIdDidAbort(HeapTupleHeaderGetXmin(mytup.t_data)))
+		{
+			UnlockReleaseBuffer(buf);
+			return HeapTupleMayBeUpdated;
+		}
+
+		old_infomask = mytup.t_data->t_infomask;
+		old_infomask2 = mytup.t_data->t_infomask2;
+		xmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
+
+		/*
+		 * If this tuple version has been updated or locked by some concurrent
+		 * transaction(s), what we do depends on whether our lock mode
+		 * conflicts with what those other transactions hold, and also on the
+		 * status of them.
+		 */
+		if (!(old_infomask & HEAP_XMAX_INVALID))
+		{
+			TransactionId rawxmax;
+			bool		needwait;
+
+			rawxmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
+			if (old_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				int			nmembers;
+				int			i;
+				MultiXactMember *members;
+
+				/*
+				 * We don't need a test for pg_upgrade'd tuples: this is only
+				 * applied to tuples after the first in an update chain.  Said
+				 * first tuple in the chain may well be locked-in-9.2-and-
+				 * pg_upgraded, but that one was already locked by our caller,
+				 * not us; and any subsequent ones cannot be because our
+				 * caller must necessarily have obtained a snapshot later than
+				 * the pg_upgrade itself.
+				 */
+				Assert(!HEAP_LOCKED_UPGRADED(mytup.t_data->t_infomask));
+
+				nmembers = GetMultiXactIdMembers(rawxmax, &members, false,
+												 HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
+				for (i = 0; i < nmembers; i++)
+				{
+					result = test_lockmode_for_conflict(members[i].status,
+														members[i].xid,
+														mode, &needwait);
+
+					/*
+					 * If the tuple was already locked by ourselves in a
+					 * previous iteration of this (say heap_lock_tuple was
+					 * forced to restart the locking loop because of a change
+					 * in xmax), then we hold the lock already on this tuple
+					 * version and we don't need to do anything; and this is
+					 * not an error condition either.  We just need to skip
+					 * this tuple and continue locking the next version in the
+					 * update chain.
+					 */
+					if (result == HeapTupleSelfUpdated)
+					{
+						pfree(members);
+						goto next;
+					}
+
+					if (needwait)
+					{
+						LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+						XactLockTableWait(members[i].xid, rel,
+										  &mytup.t_self,
+										  XLTW_LockUpdated);
+						pfree(members);
+						goto l4;
+					}
+					if (result != HeapTupleMayBeUpdated)
+					{
+						pfree(members);
+						goto out_locked;
+					}
+				}
+				if (members)
+					pfree(members);
+			}
+			else
+			{
+				MultiXactStatus status;
+
+				/*
+				 * For a non-multi Xmax, we first need to compute the
+				 * corresponding MultiXactStatus by using the infomask bits.
+				 */
+				if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
+				{
+					if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
+						status = MultiXactStatusForKeyShare;
+					else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
+						status = MultiXactStatusForShare;
+					else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
+					{
+						if (old_infomask2 & HEAP_KEYS_UPDATED)
+							status = MultiXactStatusForUpdate;
+						else
+							status = MultiXactStatusForNoKeyUpdate;
+					}
+					else
+					{
+						/*
+						 * LOCK_ONLY present alone (a pg_upgraded tuple marked
+						 * as share-locked in the old cluster) shouldn't be
+						 * seen in the middle of an update chain.
+						 */
+						elog(ERROR, "invalid lock status in tuple");
+					}
+				}
+				else
+				{
+					/* it's an update, but which kind? */
+					if (old_infomask2 & HEAP_KEYS_UPDATED)
+						status = MultiXactStatusUpdate;
+					else
+						status = MultiXactStatusNoKeyUpdate;
+				}
+
+				result = test_lockmode_for_conflict(status, rawxmax, mode,
+													&needwait);
+
+				/*
+				 * If the tuple was already locked by ourselves in a previous
+				 * iteration of this (say heap_lock_tuple was forced to
+				 * restart the locking loop because of a change in xmax), then
+				 * we hold the lock already on this tuple version and we don't
+				 * need to do anything; and this is not an error condition
+				 * either.  We just need to skip this tuple and continue
+				 * locking the next version in the update chain.
+				 */
+				if (result == HeapTupleSelfUpdated)
+					goto next;
+
+				if (needwait)
+				{
+					LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+					XactLockTableWait(rawxmax, rel, &mytup.t_self,
+									  XLTW_LockUpdated);
+					goto l4;
+				}
+				if (result != HeapTupleMayBeUpdated)
+				{
+					goto out_locked;
+				}
+			}
+		}
+
+		/* compute the new Xmax and infomask values for the tuple ... */
+		compute_new_xmax_infomask(xmax, old_infomask, mytup.t_data->t_infomask2,
+								  xid, mode, false,
+								  &new_xmax, &new_infomask, &new_infomask2);
+
+		if (PageIsAllVisible(BufferGetPage(buf)) &&
+			visibilitymap_clear(rel, block, vmbuffer,
+								VISIBILITYMAP_ALL_FROZEN))
+			cleared_all_frozen = true;
+
+		START_CRIT_SECTION();
+
+		/* ... and set them */
+		HeapTupleHeaderSetXmax(mytup.t_data, new_xmax);
+		mytup.t_data->t_infomask &= ~HEAP_XMAX_BITS;
+		mytup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+		mytup.t_data->t_infomask |= new_infomask;
+		mytup.t_data->t_infomask2 |= new_infomask2;
+
+		MarkBufferDirty(buf);
+
+		/* XLOG stuff */
+		if (RelationNeedsWAL(rel))
+		{
+			xl_heap_lock_updated xlrec;
+			XLogRecPtr	recptr;
+			Page		page = BufferGetPage(buf);
+
+			XLogBeginInsert();
+			XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
+
+			xlrec.offnum = ItemPointerGetOffsetNumber(&mytup.t_self);
+			xlrec.xmax = new_xmax;
+			xlrec.infobits_set = compute_infobits(new_infomask, new_infomask2);
+			xlrec.flags =
+				cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
+
+			XLogRegisterData((char *) &xlrec, SizeOfHeapLockUpdated);
+
+			recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_LOCK_UPDATED);
+
+			PageSetLSN(page, recptr);
+		}
+
+		END_CRIT_SECTION();
+
+next:
+		/* if we find the end of update chain, we're done. */
+		if (mytup.t_data->t_infomask & HEAP_XMAX_INVALID ||
+			ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid) ||
+			HeapTupleHeaderIsOnlyLocked(mytup.t_data))
+		{
+			result = HeapTupleMayBeUpdated;
+			goto out_locked;
+		}
+
+		/* tail recursion */
+		priorXmax = HeapTupleHeaderGetUpdateXid(mytup.t_data);
+		ItemPointerCopy(&(mytup.t_data->t_ctid), &tupid);
+		UnlockReleaseBuffer(buf);
+		if (vmbuffer != InvalidBuffer)
+			ReleaseBuffer(vmbuffer);
+	}
+
+	result = HeapTupleMayBeUpdated;
+
+out_locked:
+	UnlockReleaseBuffer(buf);
+
+	if (vmbuffer != InvalidBuffer)
+		ReleaseBuffer(vmbuffer);
+
+	return result;
+
+}
+
+/*
+ * heap_lock_updated_tuple
+ *		Follow update chain when locking an updated tuple, acquiring locks (row
+ *		marks) on the updated versions.
+ *
+ * The initial tuple is assumed to be already locked.
+ *
+ * This function doesn't check visibility, it just unconditionally marks the
+ * tuple(s) as locked.  If any tuple in the updated chain is being deleted
+ * concurrently (or updated with the key being modified), sleep until the
+ * transaction doing it is finished.
+ *
+ * Note that we don't acquire heavyweight tuple locks on the tuples we walk
+ * when we have to wait for other transactions to release them, as opposed to
+ * what heap_lock_tuple does.  The reason is that having more than one
+ * transaction walking the chain is probably uncommon enough that risk of
+ * starvation is not likely: one of the preconditions for being here is that
+ * the snapshot in use predates the update that created this tuple (because we
+ * started at an earlier version of the tuple), but at the same time such a
+ * transaction cannot be using repeatable read or serializable isolation
+ * levels, because that would lead to a serializability failure.
+ */
+static HTSU_Result
+heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid,
+						TransactionId xid, LockTupleMode mode)
+{
+	if (!ItemPointerEquals(&tuple->t_self, ctid))
+	{
+		/*
+		 * If this is the first possibly-multixact-able operation in the
+		 * current transaction, set my per-backend OldestMemberMXactId
+		 * setting. We can be certain that the transaction will never become a
+		 * member of any older MultiXactIds than that.  (We have to do this
+		 * even if we end up just using our own TransactionId below, since
+		 * some other backend could incorporate our XID into a MultiXact
+		 * immediately afterwards.)
+		 */
+		MultiXactIdSetOldestMember();
+
+		return heap_lock_updated_tuple_rec(rel, ctid, xid, mode);
+	}
+
+	/* nothing to lock */
+	return HeapTupleMayBeUpdated;
+}
+
+
+/*
+ *	heapam_lock_tuple - lock a tuple in shared or exclusive mode
+ *
+ * Note that this acquires a buffer pin, which the caller must release.
+ *
+ * Input parameters:
+ *	relation: relation containing tuple (caller must hold suitable lock)
+ *	tuple->t_self: TID of tuple to lock (rest of struct need not be valid)
+ *	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
+ *	follow_updates: if true, follow the update chain to also lock descendant
+ *		tuples.
+ *
+ * Output parameters:
+ *	*tuple: all fields filled in
+ *	*buffer: set to buffer holding tuple (pinned but not locked at exit)
+ *	*hufd: filled in failure cases (see below)
+ *
+ * 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.
+ *
+ * See README.tuplock for a thorough explanation of this mechanism.
+ */
+static HTSU_Result
+heapam_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;
+	ItemId		lp;
+	Page		page;
+	Buffer		vmbuffer = InvalidBuffer;
+	BlockNumber block;
+	TransactionId xid,
+				xmax;
+	uint16		old_infomask,
+				new_infomask,
+				new_infomask2;
+	bool		first_time = true;
+	bool		have_tuple_lock = false;
+	bool		cleared_all_frozen = false;
+	HeapTupleData tuple;
+	Buffer		buf;
+
+	Assert(stuple != NULL);
+
+	buf = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
+	block = ItemPointerGetBlockNumber(tid);
+	*buffer = buf;
+
+	/*
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
+	 */
+	if (PageIsAllVisible(BufferGetPage(buf)))
+		visibilitymap_pin(relation, block, &vmbuffer);
+
+	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+	page = BufferGetPage(buf);
+	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);
+
+l3:
+	result = HeapTupleSatisfiesUpdate(&tuple, cid, buf);
+
+	if (result == HeapTupleInvisible)
+	{
+		/*
+		 * This is possible, but only when locking a tuple for ON CONFLICT
+		 * UPDATE.  We return this value here rather than throwing an error in
+		 * order to give that case the opportunity to throw a more specific
+		 * error.
+		 */
+		result = HeapTupleInvisible;
+		goto out_locked;
+	}
+	else if (result == HeapTupleBeingUpdated || result == HeapTupleUpdated)
+	{
+		TransactionId xwait;
+		uint16		infomask;
+		uint16		infomask2;
+		bool		require_sleep;
+		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);
+
+		LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+
+		/*
+		 * If any subtransaction of the current top transaction already holds
+		 * a lock as strong as or stronger than what we're requesting, we
+		 * effectively hold the desired lock already.  We *must* succeed
+		 * without trying to take the tuple lock, else we will deadlock
+		 * against anyone wanting to acquire a stronger lock.
+		 *
+		 * Note we only do this the first time we loop on the HTSU result;
+		 * there is no point in testing in subsequent passes, because
+		 * evidently our own transaction cannot have acquired a new lock after
+		 * the first time we checked.
+		 */
+		if (first_time)
+		{
+			first_time = false;
+
+			if (infomask & HEAP_XMAX_IS_MULTI)
+			{
+				int			i;
+				int			nmembers;
+				MultiXactMember *members;
+
+				/*
+				 * We don't need to allow old multixacts here; if that had
+				 * been the case, HeapTupleSatisfiesUpdate would have returned
+				 * MayBeUpdated and we wouldn't be here.
+				 */
+				nmembers =
+					GetMultiXactIdMembers(xwait, &members, false,
+										  HEAP_XMAX_IS_LOCKED_ONLY(infomask));
+
+				for (i = 0; i < nmembers; i++)
+				{
+					/* only consider members of our own transaction */
+					if (!TransactionIdIsCurrentTransactionId(members[i].xid))
+						continue;
+
+					if (TUPLOCK_from_mxstatus(members[i].status) >= mode)
+					{
+						pfree(members);
+						result = HeapTupleMayBeUpdated;
+						goto out_unlocked;
+					}
+				}
+
+				if (members)
+					pfree(members);
+			}
+			else if (TransactionIdIsCurrentTransactionId(xwait))
+			{
+				switch (mode)
+				{
+					case LockTupleKeyShare:
+						Assert(HEAP_XMAX_IS_KEYSHR_LOCKED(infomask) ||
+							   HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
+							   HEAP_XMAX_IS_EXCL_LOCKED(infomask));
+						result = HeapTupleMayBeUpdated;
+						goto out_unlocked;
+					case LockTupleShare:
+						if (HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
+							HEAP_XMAX_IS_EXCL_LOCKED(infomask))
+						{
+							result = HeapTupleMayBeUpdated;
+							goto out_unlocked;
+						}
+						break;
+					case LockTupleNoKeyExclusive:
+						if (HEAP_XMAX_IS_EXCL_LOCKED(infomask))
+						{
+							result = HeapTupleMayBeUpdated;
+							goto out_unlocked;
+						}
+						break;
+					case LockTupleExclusive:
+						if (HEAP_XMAX_IS_EXCL_LOCKED(infomask) &&
+							infomask2 & HEAP_KEYS_UPDATED)
+						{
+							result = HeapTupleMayBeUpdated;
+							goto out_unlocked;
+						}
+						break;
+				}
+			}
+		}
+
+		/*
+		 * Initially assume that we will have to wait for the locking
+		 * transaction(s) to finish.  We check various cases below in which
+		 * this can be turned off.
+		 */
+		require_sleep = true;
+		if (mode == LockTupleKeyShare)
+		{
+			/*
+			 * If we're requesting KeyShare, and there's no update present, we
+			 * don't need to wait.  Even if there is an update, we can still
+			 * continue if the key hasn't been modified.
+			 *
+			 * However, if there are updates, we need to walk the update chain
+			 * to mark future versions of the row as locked, too.  That way,
+			 * if somebody deletes that future version, we're protected
+			 * against the key going away.  This locking of future versions
+			 * could block momentarily, if a concurrent transaction is
+			 * deleting a key; or it could return a value to the effect that
+			 * the transaction deleting the key has already committed.  So we
+			 * do this before re-locking the buffer; otherwise this would be
+			 * prone to deadlocks.
+			 *
+			 * Note that the TID we're locking was grabbed before we unlocked
+			 * the buffer.  For it to change while we're not looking, the
+			 * other properties we're testing for below after re-locking the
+			 * buffer would also change, in which case we would restart this
+			 * loop above.
+			 */
+			if (!(infomask2 & HEAP_KEYS_UPDATED))
+			{
+				bool		updated;
+
+				updated = !HEAP_XMAX_IS_LOCKED_ONLY(infomask);
+
+				/*
+				 * If there are updates, follow the update chain; bail out if
+				 * that cannot be done.
+				 */
+				if (follow_updates && updated)
+				{
+					HTSU_Result res;
+
+					res = heap_lock_updated_tuple(relation, &tuple, &t_ctid,
+												  GetCurrentTransactionId(),
+												  mode);
+					if (res != HeapTupleMayBeUpdated)
+					{
+						result = res;
+						/* recovery code expects to have buffer lock held */
+						LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+						goto failed;
+					}
+				}
+
+				LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+				/*
+				 * Make sure it's still an appropriate lock, else start over.
+				 * Also, if it wasn't updated before we released the lock, but
+				 * is updated now, we start over too; the reason is that we
+				 * 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) ||
+					 !updated))
+					goto l3;
+
+				/* Things look okay, so we can skip sleeping */
+				require_sleep = false;
+
+				/*
+				 * Note we allow Xmax to change here; other updaters/lockers
+				 * could have modified it before we grabbed the buffer lock.
+				 * However, this is not a problem, because with the recheck we
+				 * just did we ensure that they still don't conflict with the
+				 * lock we want.
+				 */
+			}
+		}
+		else if (mode == LockTupleShare)
+		{
+			/*
+			 * If we're requesting Share, we can similarly avoid sleeping if
+			 * there's no update and no exclusive lock present.
+			 */
+			if (HEAP_XMAX_IS_LOCKED_ONLY(infomask) &&
+				!HEAP_XMAX_IS_EXCL_LOCKED(infomask))
+			{
+				LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+				/*
+				 * 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))
+					goto l3;
+				require_sleep = false;
+			}
+		}
+		else if (mode == LockTupleNoKeyExclusive)
+		{
+			/*
+			 * If we're requesting NoKeyExclusive, we might also be able to
+			 * avoid sleeping; just ensure that there no conflicting lock
+			 * already acquired.
+			 */
+			if (infomask & HEAP_XMAX_IS_MULTI)
+			{
+				if (!DoesMultiXactIdConflict((MultiXactId) xwait, infomask,
+											 mode))
+				{
+					/*
+					 * No conflict, but if the xmax changed under us in the
+					 * meantime, start over.
+					 */
+					LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+					if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
+						!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple.t_data),
+											 xwait))
+						goto l3;
+
+					/* otherwise, we're good */
+					require_sleep = false;
+				}
+			}
+			else if (HEAP_XMAX_IS_KEYSHR_LOCKED(infomask))
+			{
+				LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+				/* if the xmax changed in the meantime, start over */
+				if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
+					!TransactionIdEquals(
+										 HeapTupleHeaderGetRawXmax(tuple.t_data),
+										 xwait))
+					goto l3;
+				/* otherwise, we're good */
+				require_sleep = false;
+			}
+		}
+
+		/*
+		 * As a check independent from those above, we can also avoid sleeping
+		 * if the current transaction is the sole locker of the tuple.  Note
+		 * that the strength of the lock already held is irrelevant; this is
+		 * not about recording the lock in Xmax (which will be done regardless
+		 * of this optimization, below).  Also, note that the cases where we
+		 * hold a lock stronger than we are requesting are already handled
+		 * above by not doing anything.
+		 *
+		 * Note we only deal with the non-multixact case here; MultiXactIdWait
+		 * is well equipped to deal with this situation on its own.
+		 */
+		if (require_sleep && !(infomask & HEAP_XMAX_IS_MULTI) &&
+			TransactionIdIsCurrentTransactionId(xwait))
+		{
+			/* ... but if the xmax changed in the meantime, start over */
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+			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));
+			require_sleep = false;
+		}
+
+		/*
+		 * Time to sleep on the other transaction/multixact, if necessary.
+		 *
+		 * If the other transaction is an update that's already committed,
+		 * then sleeping cannot possibly do any good: if we're required to
+		 * sleep, get out to raise an error instead.
+		 *
+		 * By here, we either have already acquired the buffer exclusive lock,
+		 * 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)
+		{
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+			goto failed;
+		}
+		else if (require_sleep)
+		{
+			/*
+			 * Acquire tuple lock to establish our priority for the tuple, or
+			 * die trying.  LockTuple will release us when we are next-in-line
+			 * for the tuple.  We must do this even if we are share-locking.
+			 *
+			 * If we are forced to "start over" below, we keep the tuple lock;
+			 * this arranges that we stay at the head of the line while
+			 * rechecking tuple state.
+			 */
+			if (!heap_acquire_tuplock(relation, tid, mode, wait_policy,
+									  &have_tuple_lock))
+			{
+				/*
+				 * This can only happen if wait_policy is Skip and the lock
+				 * couldn't be obtained.
+				 */
+				result = HeapTupleWouldBlock;
+				/* recovery code expects to have buffer lock held */
+				LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+				goto failed;
+			}
+
+			if (infomask & HEAP_XMAX_IS_MULTI)
+			{
+				MultiXactStatus status = get_mxact_status_for_lock(mode, false);
+
+				/* We only ever lock tuples, never update them */
+				if (status >= MultiXactStatusNoKeyUpdate)
+					elog(ERROR, "invalid lock mode in heap_lock_tuple");
+
+				/* wait for multixact to end, or die trying  */
+				switch (wait_policy)
+				{
+					case LockWaitBlock:
+						MultiXactIdWait((MultiXactId) xwait, status, infomask,
+										relation, &tuple.t_self, XLTW_Lock, NULL);
+						break;
+					case LockWaitSkip:
+						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
+														status, infomask, relation,
+														NULL))
+						{
+							result = HeapTupleWouldBlock;
+							/* recovery code expects to have buffer lock held */
+							LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+							goto failed;
+						}
+						break;
+					case LockWaitError:
+						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
+														status, infomask, relation,
+														NULL))
+							ereport(ERROR,
+									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+									 errmsg("could not obtain lock on row in relation \"%s\"",
+											RelationGetRelationName(relation))));
+
+						break;
+				}
+
+				/*
+				 * Of course, the multixact might not be done here: if we're
+				 * requesting a light lock mode, other transactions with light
+				 * locks could still be alive, as well as locks owned by our
+				 * own xact or other subxacts of this backend.  We need to
+				 * preserve the surviving MultiXact members.  Note that it
+				 * isn't absolutely necessary in the latter case, but doing so
+				 * is simpler.
+				 */
+			}
+			else
+			{
+				/* wait for regular transaction to end, or die trying */
+				switch (wait_policy)
+				{
+					case LockWaitBlock:
+						XactLockTableWait(xwait, relation, &tuple.t_self,
+										  XLTW_Lock);
+						break;
+					case LockWaitSkip:
+						if (!ConditionalXactLockTableWait(xwait))
+						{
+							result = HeapTupleWouldBlock;
+							/* recovery code expects to have buffer lock held */
+							LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+							goto failed;
+						}
+						break;
+					case LockWaitError:
+						if (!ConditionalXactLockTableWait(xwait))
+							ereport(ERROR,
+									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+									 errmsg("could not obtain lock on row in relation \"%s\"",
+											RelationGetRelationName(relation))));
+						break;
+				}
+			}
+
+			/* if there are updates, follow the update chain */
+			if (follow_updates && !HEAP_XMAX_IS_LOCKED_ONLY(infomask))
+			{
+				HTSU_Result res;
+
+				res = heap_lock_updated_tuple(relation, &tuple, &t_ctid,
+											  GetCurrentTransactionId(),
+											  mode);
+				if (res != HeapTupleMayBeUpdated)
+				{
+					result = res;
+					/* recovery code expects to have buffer lock held */
+					LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+					goto failed;
+				}
+			}
+
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+			/*
+			 * xwait is done, but if xwait had just locked the tuple then some
+			 * 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),
+									 xwait))
+				goto l3;
+
+			if (!(infomask & HEAP_XMAX_IS_MULTI))
+			{
+				/*
+				 * Otherwise check if it committed or aborted.  Note we cannot
+				 * be here if the tuple was only locked by somebody who didn't
+				 * conflict with us; that would have been handled above.  So
+				 * that transaction must necessarily be gone by now.  But
+				 * don't check for this in the multixact case, because some
+				 * locker transactions might still be running.
+				 */
+				UpdateXmaxHintBits(tuple.t_data, buf, xwait);
+			}
+		}
+
+		/* By here, we're certain that we hold buffer exclusive lock again */
+
+		/*
+		 * We may lock if previous xmax aborted, or if it committed but only
+		 * locked the tuple without updating it; or if we didn't have to wait
+		 * 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))
+			result = HeapTupleMayBeUpdated;
+		else
+			result = HeapTupleUpdated;
+	}
+
+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);
+		if (result == HeapTupleSelfUpdated)
+			hufd->cmax = HeapTupleHeaderGetCmax(tuple.t_data);
+		else
+			hufd->cmax = InvalidCommandId;
+		goto out_locked;
+	}
+
+	/*
+	 * If we didn't pin the visibility map page and the page has become all
+	 * visible while we were busy locking the buffer, or during some
+	 * subsequent window during which we had it unlocked, we'll have to unlock
+	 * and re-lock, to avoid holding the buffer lock across I/O.  That's a bit
+	 * unfortunate, especially since we'll now have to recheck whether the
+	 * tuple has been locked or updated under us, but hopefully it won't
+	 * happen very often.
+	 */
+	if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
+	{
+		LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+		visibilitymap_pin(relation, block, &vmbuffer);
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+		goto l3;
+	}
+
+	xmax = HeapTupleHeaderGetRawXmax(tuple.t_data);
+	old_infomask = tuple.t_data->t_infomask;
+
+	/*
+	 * If this is the first possibly-multixact-able operation in the current
+	 * transaction, set my per-backend OldestMemberMXactId setting. We can be
+	 * certain that the transaction will never become a member of any older
+	 * MultiXactIds than that.  (We have to do this even if we end up just
+	 * using our own TransactionId below, since some other backend could
+	 * incorporate our XID into a MultiXact immediately afterwards.)
+	 */
+	MultiXactIdSetOldestMember();
+
+	/*
+	 * Compute the new xmax and infomask to store into the tuple.  Note we do
+	 * 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,
+							  GetCurrentTransactionId(), mode, false,
+							  &xid, &new_infomask, &new_infomask2);
+
+	START_CRIT_SECTION();
+
+	/*
+	 * Store transaction information of xact locking the tuple.
+	 *
+	 * Note: Cmax is meaningless in this context, so don't set it; this avoids
+	 * possibly generating a useless combo CID.  Moreover, if we're locking a
+	 * previously updated tuple, it's important to preserve the Cmax.
+	 *
+	 * 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;
+	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
+		HeapTupleHeaderClearHotUpdated(tuple.t_data);
+	HeapTupleHeaderSetXmax(tuple.t_data, xid);
+
+	/*
+	 * Make sure there is no forward chain link in t_ctid.  Note that in the
+	 * cases where the tuple has been updated, we must not overwrite t_ctid,
+	 * because it was set by the updater.  Moreover, if the tuple has been
+	 * updated, we need to follow the update chain to lock the new versions of
+	 * the tuple as well.
+	 */
+	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
+		tuple.t_data->t_ctid = *tid;
+
+	/* Clear only the all-frozen bit on visibility map if needed */
+	if (PageIsAllVisible(page) &&
+		visibilitymap_clear(relation, block, vmbuffer,
+							VISIBILITYMAP_ALL_FROZEN))
+		cleared_all_frozen = true;
+
+
+	MarkBufferDirty(buf);
+
+	/*
+	 * XLOG stuff.  You might think that we don't need an XLOG record because
+	 * there is no state change worth restoring after a crash.  You would be
+	 * wrong however: we have just written either a TransactionId or a
+	 * MultiXactId that may never have been seen on disk before, and we need
+	 * to make sure that there are XLOG entries covering those ID numbers.
+	 * Else the same IDs might be re-used after a crash, which would be
+	 * disastrous if this page made it to disk before the crash.  Essentially
+	 * we have to enforce the WAL log-before-data rule even in this case.
+	 * (Also, in a PITR log-shipping or 2PC environment, we have to have XLOG
+	 * entries for everything anyway.)
+	 */
+	if (RelationNeedsWAL(relation))
+	{
+		xl_heap_lock xlrec;
+		XLogRecPtr	recptr;
+
+		XLogBeginInsert();
+		XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
+
+		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple.t_self);
+		xlrec.locking_xid = xid;
+		xlrec.infobits_set = compute_infobits(new_infomask,
+											  tuple.t_data->t_infomask2);
+		xlrec.flags = cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
+		XLogRegisterData((char *) &xlrec, SizeOfHeapLock);
+
+		/* we don't decode row locks atm, so no need to log the origin */
+
+		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_LOCK);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	result = HeapTupleMayBeUpdated;
+
+out_locked:
+	LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+
+out_unlocked:
+	if (BufferIsValid(vmbuffer))
+		ReleaseBuffer(vmbuffer);
+
+	/*
+	 * Don't update the visibility map here. Locking a tuple doesn't change
+	 * visibility info.
+	 */
+
+	/*
+	 * Now that we have successfully marked the tuple as locked, we can
+	 * release the lmgr tuple lock, if we had it.
+	 */
+	if (have_tuple_lock)
+		UnlockTupleTuplock(relation, tid, mode);
+
+	*stuple = heap_copytuple(&tuple);
+	return result;
+}
+
+/*
+ *	heapam_get_latest_tid -  get the latest tid of a specified tuple
+ *
+ * Actually, this gets the latest version that is visible according to
+ * the passed snapshot.  You can pass SnapshotDirty to get the very latest,
+ * possibly uncommitted version.
+ *
+ * *tid is both an input and an output parameter: it is updated to
+ * show the latest version of the row.  Note that it will not be changed
+ * if no version of the row passes the snapshot test.
+ */
+static void
+heapam_get_latest_tid(Relation relation,
+					  Snapshot snapshot,
+					  ItemPointer tid)
+{
+	BlockNumber blk;
+	ItemPointerData ctid;
+	TransactionId priorXmax;
+
+	/* this is to avoid Assert failures on bad input */
+	if (!ItemPointerIsValid(tid))
+		return;
+
+	/*
+	 * Since this can be called with user-supplied TID, don't trust the input
+	 * too much.  (RelationGetNumberOfBlocks is an expensive check, so we
+	 * don't check t_ctid links again this way.  Note that it would not do to
+	 * call it just once and save the result, either.)
+	 */
+	blk = ItemPointerGetBlockNumber(tid);
+	if (blk >= RelationGetNumberOfBlocks(relation))
+		elog(ERROR, "block number %u is out of range for relation \"%s\"",
+			 blk, RelationGetRelationName(relation));
+
+	/*
+	 * Loop to chase down t_ctid links.  At top of loop, ctid is the tuple we
+	 * need to examine, and *tid is the TID we will return if ctid turns out
+	 * to be bogus.
+	 *
+	 * Note that we will loop until we reach the end of the t_ctid chain.
+	 * Depending on the snapshot passed, there might be at most one visible
+	 * version of the row, but we don't try to optimize for that.
+	 */
+	ctid = *tid;
+	priorXmax = InvalidTransactionId;	/* cannot check first XMIN */
+	for (;;)
+	{
+		Buffer		buffer;
+		Page		page;
+		OffsetNumber offnum;
+		ItemId		lp;
+		HeapTupleData tp;
+		bool		valid;
+
+		/*
+		 * Read, pin, and lock the page.
+		 */
+		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&ctid));
+		LockBuffer(buffer, BUFFER_LOCK_SHARE);
+		page = BufferGetPage(buffer);
+		TestForOldSnapshot(snapshot, relation, page);
+
+		/*
+		 * Check for bogus item number.  This is not treated as an error
+		 * condition because it can happen while following a t_ctid link. We
+		 * just assume that the prior tid is OK and return it unchanged.
+		 */
+		offnum = ItemPointerGetOffsetNumber(&ctid);
+		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+		lp = PageGetItemId(page, offnum);
+		if (!ItemIdIsNormal(lp))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+
+		/* OK to access the tuple */
+		tp.t_self = ctid;
+		tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+		tp.t_len = ItemIdGetLength(lp);
+		tp.t_tableOid = RelationGetRelid(relation);
+
+		/*
+		 * After following a t_ctid link, we might arrive at an unrelated
+		 * tuple.  Check for XMIN match.
+		 */
+		if (TransactionIdIsValid(priorXmax) &&
+			!HeapTupleUpdateXmaxMatchesXmin(priorXmax, tp.t_data))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+
+		/*
+		 * Check time qualification of tuple; if visible, set it as the new
+		 * result candidate.
+		 */
+		valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, &tp, snapshot, buffer);
+		CheckForSerializableConflictOut(valid, relation, &tp, buffer, snapshot);
+		if (valid)
+			*tid = ctid;
+
+		/*
+		 * If there's a valid t_ctid link, follow it, else we're done.
+		 */
+		if ((tp.t_data->t_infomask & HEAP_XMAX_INVALID) ||
+			HeapTupleHeaderIsOnlyLocked(tp.t_data) ||
+			ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+
+		ctid = tp.t_data->t_ctid;
+		priorXmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
+		UnlockReleaseBuffer(buffer);
+	}							/* end of loop */
+}
+
+
+/*
+ *	heapam_sync		- sync a heap, for use when no WAL has been written
+ *
+ * This forces the heap contents (including TOAST heap if any) down to disk.
+ * If we skipped using WAL, and WAL is otherwise needed, we must force the
+ * relation down to disk before it's safe to commit the transaction.  This
+ * requires writing out any dirty buffers and then doing a forced fsync.
+ *
+ * Indexes are not touched.  (Currently, index operations associated with
+ * the commands that use this are WAL-logged and so do not need fsync.
+ * That behavior might change someday, but in any case it's likely that
+ * any fsync decisions required would be per-index and hence not appropriate
+ * to be done here.)
+ */
+static void
+heapam_sync(Relation rel)
+{
+	/* non-WAL-logged tables never need fsync */
+	if (!RelationNeedsWAL(rel))
+		return;
+
+	/* main heap */
+	FlushRelationBuffers(rel);
+	/* FlushRelationBuffers will have opened rd_smgr */
+	smgrimmedsync(rel->rd_smgr, MAIN_FORKNUM);
+
+	/* FSM is not critical, don't bother syncing it */
+
+	/* toast heap, if any */
+	if (OidIsValid(rel->rd_rel->reltoastrelid))
+	{
+		Relation	toastrel;
+
+		toastrel = heap_open(rel->rd_rel->reltoastrelid, AccessShareLock);
+		FlushRelationBuffers(toastrel);
+		smgrimmedsync(toastrel->rd_smgr, MAIN_FORKNUM);
+		heap_close(toastrel, AccessShareLock);
+	}
+}
+
+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)
+{
+	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
+
+	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 = heapam_multi_insert;
+
+	amroutine->speculative_abort = heapam_abort_speculative;
+
+	amroutine->get_tuple_data = heapam_get_tuple_data;
+	amroutine->tuple_from_datum = heapam_form_tuple_by_datum;
+	amroutine->tuple_get_latest_tid = heapam_get_latest_tid;
+
+	amroutine->relation_sync = heapam_sync;
 
 	amroutine->snapshot_satisfies = HeapTupleSatisfies;
 	amroutine->snapshot_satisfiesUpdate = HeapTupleSatisfiesUpdate;
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 3118c2d33f..84ac4bfade 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -111,6 +111,7 @@
 #include "access/heapam_common.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"
@@ -127,13 +128,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
@@ -358,7 +359,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/storageam.c b/src/backend/access/heap/storageam.c
new file mode 100644
index 0000000000..870490d00f
--- /dev/null
+++ b/src/backend/access/heap/storageam.c
@@ -0,0 +1,281 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/heap/storageam.c
+ *
+ *
+ * NOTES
+ *	  This file contains the storage_ routines which implement
+ *	  the POSTGRES storage access method used for all POSTGRES
+ *	  relations.
+ *
+ *-------------------------------------------------------------------------
+ */
+#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 "access/relscan.h"
+#include "access/storageam.h"
+#include "access/storageamapi.h"
+#include "access/tuptoaster.h"
+#include "access/valid.h"
+#include "access/visibilitymap.h"
+#include "access/xloginsert.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/bufmgr.h"
+#include "storage/lmgr.h"
+#include "storage/predicate.h"
+#include "storage/procarray.h"
+#include "storage/smgr.h"
+#include "storage/spin.h"
+#include "utils/builtins.h"
+#include "utils/inval.h"
+#include "utils/rel.h"
+#include "utils/tqual.h"
+
+
+/*
+ *	storage_fetch		- retrieve tuple with given tid
+ *
+ * On entry, tuple->t_self is the TID to fetch.  We pin the buffer holding
+ * the tuple, fill in the remaining fields of *tuple, and check the tuple
+ * against the specified snapshot.
+ *
+ * If successful (tuple found and passes snapshot time qual), then *userbuf
+ * is set to the buffer holding the tuple and TRUE is returned.  The caller
+ * must unpin the buffer when done with the tuple.
+ *
+ * If the tuple is not found (ie, item number references a deleted slot),
+ * then tuple->t_data is set to NULL and FALSE is returned.
+ *
+ * If the tuple is found but fails the time qual check, then FALSE is returned
+ * but tuple->t_data is left pointing to the tuple.
+ *
+ * keep_buf determines what is done with the buffer in the FALSE-result cases.
+ * When the caller specifies keep_buf = true, we retain the pin on the buffer
+ * and return it in *userbuf (so the caller must eventually unpin it); when
+ * keep_buf = false, the pin is released and *userbuf is set to InvalidBuffer.
+ *
+ * stats_relation is the relation to charge the heap_fetch operation against
+ * for statistical purposes.  (This could be the heap rel itself, an
+ * associated index, or NULL to not count the fetch at all.)
+ *
+ * heap_fetch does not follow HOT chains: only the exact TID requested will
+ * be fetched.
+ *
+ * It is somewhat inconsistent that we ereport() on invalid block number but
+ * return false on invalid item number.  There are a couple of reasons though.
+ * One is that the caller can relatively easily check the block number for
+ * validity, but cannot check the item number without reading the page
+ * himself.  Another is that when we are following a t_ctid link, we can be
+ * reasonably confident that the page number is valid (since VACUUM shouldn't
+ * truncate off the destination page without having killed the referencing
+ * tuple first), but the item number might well not be good.
+ */
+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
+ *
+ * Note that this acquires a buffer pin, which the caller must release.
+ *
+ * Input parameters:
+ *	relation: relation containing tuple (caller must hold suitable lock)
+ *	tuple->t_self: TID of tuple to lock (rest of struct need not be valid)
+ *	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
+ *	follow_updates: if true, follow the update chain to also lock descendant
+ *		tuples.
+ *
+ * Output parameters:
+ *	*tuple: all fields filled in
+ *	*buffer: set to buffer holding tuple (pinned but not locked at exit)
+ *	*hufd: filled in failure cases (see below)
+ *
+ * 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.
+ *
+ * See README.tuplock for a thorough explanation of this mechanism.
+ */
+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
+ *
+ * This is like heap_insert(), but inserts multiple tuples in one operation.
+ * That's faster than calling heap_insert() in a loop, because when multiple
+ * tuples can be inserted on a single page, we can write just a single WAL
+ * record covering all of them, and only need to lock/unlock the page once.
+ *
+ * Note: this leaks memory into the current memory context. You can create a
+ * temporary context before calling this, if that's a problem.
+ */
+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
+ *
+ * Marks a tuple that was speculatively inserted in the same command as dead,
+ * by setting its xmin as invalid.  That makes it immediately appear as dead
+ * to all transactions, including our own.  In particular, it makes
+ * HeapTupleSatisfiesDirty() regard the tuple as dead, so that another backend
+ * inserting a duplicate key value won't unnecessarily wait for our whole
+ * transaction to finish (it'll just wait for our speculative insertion to
+ * finish).
+ *
+ * Killing the tuple prevents "unprincipled deadlocks", which are deadlocks
+ * that arise due to a mutual dependency that is not user visible.  By
+ * definition, unprincipled deadlocks cannot be prevented by the user
+ * reordering lock acquisition in client code, because the implementation level
+ * lock acquisitions are not under the user's direct control.  If speculative
+ * inserters did not take this precaution, then under high concurrency they
+ * could deadlock with each other, which would not be acceptable.
+ *
+ * This is somewhat redundant with heap_delete, but we prefer to have a
+ * dedicated routine with stripped down requirements.  Note that this is also
+ * used to delete the TOAST tuples created during speculative insertion.
+ *
+ * This routine does not affect logical decoding as it only looks at
+ * confirmation records.
+ */
+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
+ *
+ * This forces the heap contents (including TOAST heap if any) down to disk.
+ * If we skipped using WAL, and WAL is otherwise needed, we must force the
+ * relation down to disk before it's safe to commit the transaction.  This
+ * requires writing out any dirty buffers and then doing a forced fsync.
+ *
+ * Indexes are not touched.  (Currently, index operations associated with
+ * the commands that use this are WAL-logged and so do not need fsync.
+ * That behavior might change someday, but in any case it's likely that
+ * any fsync decisions required would be per-index and hence not appropriate
+ * to be done here.)
+ */
+void
+storage_sync(Relation rel)
+{
+	rel->rd_stamroutine->relation_sync(rel);
+}
diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index 5a8f1dab83..ba9592d055 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/commands/copy.c b/src/backend/commands/copy.c
index b56fae4e91..69af210dba 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"
@@ -2704,8 +2705,6 @@ CopyFrom(CopyState cstate)
 
 			if (slot == NULL)	/* "do nothing" */
 				skip_tuple = true;
-			else				/* trigger might have changed tuple */
-				tuple = ExecHeapifySlot(slot);
 		}
 
 		if (!skip_tuple)
@@ -2768,19 +2767,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);
@@ -2876,7 +2867,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;
 }
@@ -2909,12 +2900,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);
 
 	/*
@@ -2930,10 +2921,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);
 		}
@@ -2950,8 +2940,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 3ab808715b..e1ca60b35b 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"
@@ -4652,7 +4653,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);
 
@@ -4676,7 +4678,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 005bdbd023..680a65cad5 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2352,17 +2352,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 +3016,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 +3035,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 +3071,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 +3118,7 @@ ltrmark:;
 	{
 		Page		page;
 		ItemId		lp;
+		HeapTupleData tupledata;
 
 		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 
@@ -3130,17 +3137,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 +3953,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 +4027,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 +4043,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 638a856dc3..0467e17e70 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1894,7 +1894,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 +1974,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				 */
 				if (resultRelInfo->ri_PartitionRoot)
 				{
-					HeapTuple	tuple = ExecFetchSlotTuple(slot);
+					StorageTuple tuple = ExecFetchSlotTuple(slot);
 					TupleConversionMap *map;
 
 					rel = resultRelInfo->ri_PartitionRoot;
@@ -2021,7 +2021,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 +2480,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 +2498,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 +2531,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 +2564,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 +2579,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 +2598,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 (!HeapTupleUpdateXmaxMatchesXmin(priorXmax, tuple.t_data))
+			if (!HeapTupleUpdateXmaxMatchesXmin(priorXmax, ((HeapTuple) tuple)->t_data))
 			{
 				ReleaseBuffer(buffer);
 				return NULL;
@@ -2616,7 +2619,8 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 				{
 					case LockWaitBlock:
 						XactLockTableWait(SnapshotDirty.xmax,
-										  relation, &tuple.t_self,
+										  relation,
+										  tid,
 										  XLTW_FetchUpdated);
 						break;
 					case LockWaitSkip:
@@ -2645,20 +2649,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);
 
@@ -2694,12 +2701,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;
@@ -2721,10 +2731,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;
 		}
@@ -2733,7 +2739,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;
@@ -2742,7 +2748,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 		/*
 		 * As above, if xmin isn't what we're expecting, do nothing.
 		 */
-		if (!HeapTupleUpdateXmaxMatchesXmin(priorXmax, tuple.t_data))
+		if (!HeapTupleUpdateXmaxMatchesXmin(priorXmax, ((HeapTuple) tuple)->t_data))
 		{
 			ReleaseBuffer(buffer);
 			return NULL;
@@ -2760,7 +2766,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);
@@ -2768,17 +2776,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;
 }
 
 /*
@@ -2824,7 +2834,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;
 
@@ -2843,7 +2853,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;
@@ -2871,7 +2881,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");
@@ -2902,8 +2912,6 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 
 		if (erm->markType == ROW_MARK_REFERENCE)
 		{
-			HeapTuple	copyTuple;
-
 			Assert(erm->relation != NULL);
 
 			/* fetch the tuple's ctid */
@@ -2927,11 +2935,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");
 
 				/*
@@ -2945,23 +2953,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 */
@@ -2971,19 +2974,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);
+
 		}
 	}
 }
@@ -3149,8 +3145,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 07640a9992..24b5ff7298 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/execTuples.c b/src/backend/executor/execTuples.c
index 93aeb3eb6e..d618b3d903 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -83,6 +83,7 @@
 
 #include "access/heapam_common.h"
 #include "access/htup_details.h"
+#include "access/storageamapi.h"
 #include "access/tuptoaster.h"
 #include "funcapi.h"
 #include "catalog/pg_type.h"
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 ec5c543bdd..965c629870 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/executor.h"
 #include "executor/nodeModifyTable.h"
@@ -164,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;
 
@@ -191,7 +192,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
 static void
 ExecCheckHeapTupleVisible(EState *estate,
 						  Relation rel,
-						  HeapTuple tuple,
+						  StorageTuple tuple,
 						  Buffer buffer)
 {
 	if (!IsolationUsesXactSnapshot())
@@ -204,13 +205,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")));
@@ -226,19 +229,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);
 }
 
 /* ----------------------------------------------------------------
@@ -259,7 +263,7 @@ ExecInsert(ModifyTableState *mtstate,
 		   EState *estate,
 		   bool canSetTag)
 {
-	HeapTuple	tuple;
+	StorageTuple tuple;
 	ResultRelInfo *resultRelInfo;
 	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
@@ -267,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
 	 */
@@ -284,6 +282,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.
@@ -374,19 +374,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.
@@ -404,9 +416,6 @@ ExecInsert(ModifyTableState *mtstate,
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
-
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -418,9 +427,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)
@@ -436,14 +442,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;
 	}
@@ -463,7 +467,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
@@ -493,7 +498,6 @@ ExecInsert(ModifyTableState *mtstate,
 			/* Perform a speculative insertion. */
 			uint32		specToken;
 			ItemPointerData conflictTid;
-			bool		specConflict;
 
 			/*
 			 * Do a non-conclusive check for conflicts first.
@@ -508,7 +512,7 @@ ExecInsert(ModifyTableState *mtstate,
 			 * speculatively.
 			 */
 	vlock:
-			specConflict = false;
+			slot->tts_specConflict = false;
 			if (!ExecCheckIndexConstraints(slot, estate, &conflictTid,
 										   arbiterIndexes))
 			{
@@ -554,24 +558,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
@@ -587,7 +584,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;
@@ -599,19 +596,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);
 		}
 	}
 
@@ -619,11 +611,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);
@@ -674,7 +666,7 @@ ExecInsert(ModifyTableState *mtstate,
 static TupleTableSlot *
 ExecDelete(ModifyTableState *mtstate,
 		   ItemPointer tupleid,
-		   HeapTuple oldtuple,
+		   StorageTuple oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate,
@@ -719,8 +711,6 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
-		HeapTuple	tuple;
-
 		/*
 		 * delete from foreign table: let the FDW do it
 		 *
@@ -746,8 +736,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
 	{
@@ -761,11 +753,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:
@@ -861,7 +853,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)
@@ -875,20 +867,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);
@@ -897,7 +888,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))
@@ -934,14 +925,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;
@@ -1006,14 +997,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
 	{
@@ -1023,7 +1014,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
@@ -1059,11 +1050,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:
@@ -1134,26 +1128,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)
@@ -1211,11 +1185,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);
@@ -1226,10 +1201,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:
@@ -1254,7 +1227,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"),
@@ -1285,7 +1259,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:
@@ -1313,10 +1289,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
@@ -1331,7 +1307,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 */
 	}
@@ -1371,12 +1349,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;
 }
 
@@ -1579,7 +1559,7 @@ ExecModifyTable(PlanState *pstate)
 	ItemPointer tupleid = NULL;
 	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 9f4367d704..b1cb80c251 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -133,17 +133,13 @@ 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,
-		   HeapTuple tuple, Buffer *userbuf, bool keep_buf,
-		   Relation stats_relation);
+extern Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid,
+			int options, BulkInsertState bistate);
 extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
 					   Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
 					   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);
 
 extern bool HeapTupleUpdateXmaxMatchesXmin(TransactionId xmax,
@@ -153,23 +149,6 @@ extern BulkInsertState GetBulkInsertState(void);
 extern void FreeBulkInsertState(BulkInsertState);
 extern void ReleaseBulkInsertStatePin(BulkInsertState bistate);
 
-extern Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid,
-			int options, BulkInsertState bistate);
-extern void heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
-				  CommandId cid, int options, BulkInsertState bistate);
-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 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,
-				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,7 +161,6 @@ extern void simple_heap_delete(Relation relation, ItemPointer tid);
 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);
 
 /* in heap/pruneheap.c */
diff --git a/src/include/access/heapam_common.h b/src/include/access/heapam_common.h
index feb35c5024..14aad60546 100644
--- a/src/include/access/heapam_common.h
+++ b/src/include/access/heapam_common.h
@@ -34,6 +34,111 @@
 #include "utils/relcache.h"
 #include "utils/snapshot.h"
 
+/*
+ * Each tuple lock mode has a corresponding heavyweight lock, and one or two
+ * corresponding MultiXactStatuses (one to merely lock tuples, another one to
+ * update them).  This table (and the macros below) helps us determine the
+ * heavyweight lock mode and MultiXactStatus values to use for any particular
+ * tuple lock strength.
+ *
+ * Don't look at lockstatus/updstatus directly!  Use get_mxact_status_for_lock
+ * instead.
+ */
+static const struct
+{
+	LOCKMODE	hwlock;
+	int			lockstatus;
+	int			updstatus;
+}
+
+			tupleLockExtraInfo[MaxLockTupleMode + 1] =
+{
+	{							/* LockTupleKeyShare */
+		AccessShareLock,
+		MultiXactStatusForKeyShare,
+		-1						/* KeyShare does not allow updating tuples */
+	},
+	{							/* LockTupleShare */
+		RowShareLock,
+		MultiXactStatusForShare,
+		-1						/* Share does not allow updating tuples */
+	},
+	{							/* LockTupleNoKeyExclusive */
+		ExclusiveLock,
+		MultiXactStatusForNoKeyUpdate,
+		MultiXactStatusNoKeyUpdate
+	},
+	{							/* LockTupleExclusive */
+		AccessExclusiveLock,
+		MultiXactStatusForUpdate,
+		MultiXactStatusUpdate
+	}
+};
+
+/*
+ * This table maps tuple lock strength values for each particular
+ * MultiXactStatus value.
+ */
+static const int MultiXactStatusLock[MaxMultiXactStatus + 1] =
+{
+	LockTupleKeyShare,			/* ForKeyShare */
+	LockTupleShare,				/* ForShare */
+	LockTupleNoKeyExclusive,	/* ForNoKeyUpdate */
+	LockTupleExclusive,			/* ForUpdate */
+	LockTupleNoKeyExclusive,	/* NoKeyUpdate */
+	LockTupleExclusive			/* Update */
+};
+
+/* Get the LockTupleMode for a given MultiXactStatus */
+#define TUPLOCK_from_mxstatus(status) \
+			(MultiXactStatusLock[(status)])
+
+/*
+ * Acquire heavyweight locks on tuples, using a LockTupleMode strength value.
+ * This is more readable than having every caller translate it to lock.h's
+ * LOCKMODE.
+ */
+#define LockTupleTuplock(rel, tup, mode) \
+	LockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
+#define UnlockTupleTuplock(rel, tup, mode) \
+	UnlockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
+#define ConditionalLockTupleTuplock(rel, tup, mode) \
+	ConditionalLockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
+/* Get the LOCKMODE for a given MultiXactStatus */
+#define LOCKMODE_from_mxstatus(status) \
+			(tupleLockExtraInfo[TUPLOCK_from_mxstatus((status))].hwlock)
+extern HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
+					TransactionId xid, CommandId cid, int options);
+
+extern HTSU_Result heap_delete(Relation relation, ItemPointer tid,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			HeapUpdateFailureData *hufd);
+extern HTSU_Result heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			HeapUpdateFailureData *hufd, LockTupleMode *lockmode);
+
+extern XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
+extern uint8 compute_infobits(uint16 infomask, uint16 infomask2);
+extern void compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
+						  uint16 old_infomask2, TransactionId add_to_xmax,
+						  LockTupleMode mode, bool is_update,
+						  TransactionId *result_xmax, uint16 *result_infomask,
+						  uint16 *result_infomask2);
+extern void UpdateXmaxHintBits(HeapTupleHeader tuple, Buffer buffer, TransactionId xid);
+extern bool DoesMultiXactIdConflict(MultiXactId multi, uint16 infomask,
+						LockTupleMode lockmode);
+extern bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
+						   uint16 infomask, Relation rel, int *remaining);
+
+extern void MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
+				Relation rel, ItemPointer ctid, XLTW_Oper oper,
+				int *remaining);
+extern MultiXactStatus get_mxact_status_for_lock(LockTupleMode mode, bool is_update);
+
+extern void heap_inplace_update(Relation relation, HeapTuple tuple);
+extern bool heap_acquire_tuplock(Relation relation, ItemPointer tid,
+					 LockTupleMode mode, LockWaitPolicy wait_policy,
+					 bool *have_tuple_lock);
 
 /* in heap/heapam_common.c */
 extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
@@ -43,6 +148,28 @@ extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin);
 typedef struct StorageSlotAmRoutine StorageSlotAmRoutine;
 extern StorageSlotAmRoutine * heapam_storage_slot_handler(void);
 
+
+/*
+ * Given two versions of the same t_infomask for a tuple, compare them and
+ * return whether the relevant status for a tuple Xmax has changed.  This is
+ * used after a buffer lock has been released and reacquired: we want to ensure
+ * that the tuple state continues to be the same it was when we previously
+ * examined it.
+ *
+ * Note the Xmax field itself must be compared separately.
+ */
+static inline bool
+xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask)
+{
+	const uint16 interesting =
+	HEAP_XMAX_IS_MULTI | HEAP_XMAX_LOCK_ONLY | HEAP_LOCK_MASK;
+
+	if ((new_infomask & interesting) != (old_infomask & interesting))
+		return true;
+
+	return false;
+}
+
 /*
  * SetHintBits()
  *
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/storageam.h b/src/include/access/storageam.h
new file mode 100644
index 0000000000..447935ba10
--- /dev/null
+++ b/src/include/access/storageam.h
@@ -0,0 +1,86 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "executor/tuptable.h"
+#include "nodes/execnodes.h"
+
+/* A physical tuple coming from a storage AM scan */
+typedef void *StorageTuple;
+
+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 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 2b08c6f4ff..03a6aa425a 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -15,30 +15,11 @@
 #include "access/heapam.h"
 #include "access/sdir.h"
 #include "access/skey.h"
+#include "access/storageam.h"
 #include "executor/tuptable.h"
 #include "utils/relcache.h"
 #include "utils/snapshot.h"
 
-/* A physical tuple coming from a storage AM scan */
-typedef void *StorageTuple;
-
-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;
-
-
 typedef HeapScanDesc (*ScanBegin_function) (Relation relation,
 											Snapshot snapshot,
 											int nkeys, ScanKey key,
@@ -70,11 +51,9 @@ typedef bool (*HotSearchBuffer_function) (ItemPointer tid, Relation relation,
 										  Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
 										  bool *all_dead, bool first_call);
 
-typedef Oid (*TupleInsert_function) (Relation relation,
-									 TupleTableSlot *tupslot,
-									 CommandId cid,
-									 int options,
-									 BulkInsertState bistate);
+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,
@@ -86,11 +65,14 @@ typedef HTSU_Result (*TupleDelete_function) (Relation relation,
 typedef HTSU_Result (*TupleUpdate_function) (Relation relation,
 											 ItemPointer otid,
 											 TupleTableSlot *slot,
+											 EState *estate,
 											 CommandId cid,
 											 Snapshot crosscheck,
 											 bool wait,
 											 HeapUpdateFailureData *hufd,
-											 LockTupleMode *lockmode);
+											 LockTupleMode *lockmode,
+											 InsertIndexTuples IndexFunc,
+											 List **recheckIndexes);
 
 typedef bool (*TupleFetch_function) (Relation relation,
 									 ItemPointer tid,
@@ -209,7 +191,6 @@ typedef struct StorageAmRoutine
 	 * need for a storage AM method, but after inserting a tuple containing a
 	 * speculative token, the insertion must be completed by these routines:
 	 */
-	SpeculativeFinish_function speculative_finish;
 	SpeculativeAbort_function speculative_abort;
 
 
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 c4ecf0d50f..85b1e15093 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -196,16 +196,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);
 extern void ExecSetupPartitionTupleRouting(Relation rel,
 							   Index resultRTindex,
 							   EState *estate,
@@ -528,9 +528,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 a730f265dd..630899ea22 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 8698c8a50c..6a3a88bdd4 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -503,7 +503,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 */
 
@@ -1743,7 +1743,7 @@ typedef struct SharedSortInfo
 {
 	int			num_workers;
 	TuplesortInstrumentation sinstrument[FLEXIBLE_ARRAY_MEMBER];
-} SharedSortInfo;
+}			SharedSortInfo;
 
 /* ----------------
  *	 SortState information
@@ -2025,7 +2025,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;
 
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index b1c94de683..1a403b8e21 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -14,7 +14,6 @@
 #ifndef BUFMGR_H
 #define BUFMGR_H
 
-#include "access/storageamapi.h"
 #include "storage/block.h"
 #include "storage/buf.h"
 #include "storage/bufpage.h"
-- 
2.14.2.windows.1

#107Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Haribabu Kommi (#106)
8 attachment(s)
Re: Pluggable storage

On Tue, Oct 31, 2017 at 8:59 PM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:

Additional changes that are done in the patches compared to earlier
patches apart from rebase.

Rebased patches are attached.

Regards,
Hari Babu
Fujitsu Australia

Attachments:

0007-Scan-functions-are-added-to-storage-AM.patchapplication/octet-stream; name=0007-Scan-functions-are-added-to-storage-AM.patchDownload
From 1bc5a33a92f8d0fc30ddc06d882a4c9a038c8675 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Tue, 7 Nov 2017 14:50:57 +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           | 1523 ++--------------------------
 src/backend/access/heap/heapam_storage.c   | 1319 ++++++++++++++++++++++++
 src/backend/access/heap/rewriteheap.c      |    4 +-
 src/backend/access/heap/storageam.c        |  235 +++++
 src/backend/access/index/genam.c           |   11 +-
 src/backend/access/index/indexam.c         |   13 +-
 src/backend/access/nbtree/nbtinsert.c      |    7 +-
 src/backend/bootstrap/bootstrap.c          |   25 +-
 src/backend/catalog/aclchk.c               |   13 +-
 src/backend/catalog/index.c                |   59 +-
 src/backend/catalog/partition.c            |   24 +-
 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/trigger.c             |    3 +-
 src/backend/commands/typecmds.c            |   13 +-
 src/backend/commands/vacuum.c              |   17 +-
 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/postmaster/postmaster.c        |    4 +-
 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                |   30 +-
 src/include/access/heapam_common.h         |    8 +
 src/include/access/storageam.h             |   39 +
 src/include/executor/functions.h           |    2 +-
 src/include/executor/spi.h                 |   12 +-
 src/include/executor/tqueue.h              |    4 +-
 src/include/funcapi.h                      |    2 +-
 57 files changed, 1996 insertions(+), 1796 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 d91452963a..6c1224db42 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -81,19 +81,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 XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 				Buffer newbuf, HeapTuple oldtup,
 				HeapTuple newtup, HeapTuple old_key_tup,
@@ -112,139 +99,6 @@ static bool Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
 static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_modified,
 					   bool *copy);
 
-/* ----------------------------------------------------------------
- *						 heap support routines
- * ----------------------------------------------------------------
- */
-
-/* ----------------
- *		initscan - scan code common to heap_beginscan and heap_rescan
- * ----------------
- */
-static void
-initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
-{
-	bool		allow_strat;
-	bool		allow_sync;
-
-	/*
-	 * Determine the number of blocks we have to scan.
-	 *
-	 * It is sufficient to do this once at scan start, since any tuples added
-	 * while the scan is in progress will be invisible to my snapshot anyway.
-	 * (That is not true when using a non-MVCC snapshot.  However, we couldn't
-	 * guarantee to return tuples added after scan start anyway, since they
-	 * might go into pages we already scanned.  To guarantee consistent
-	 * results for a non-MVCC snapshot, the caller must hold some higher-level
-	 * lock that ensures the interesting tuple(s) won't change.)
-	 */
-	if (scan->rs_parallel != NULL)
-		scan->rs_nblocks = scan->rs_parallel->phs_nblocks;
-	else
-		scan->rs_nblocks = RelationGetNumberOfBlocks(scan->rs_rd);
-
-	/*
-	 * If the table is large relative to NBuffers, use a bulk-read access
-	 * strategy and enable synchronized scanning (see syncscan.c).  Although
-	 * the thresholds for these features could be different, we make them the
-	 * same so that there are only two behaviors to tune rather than four.
-	 * (However, some callers need to be able to disable one or both of these
-	 * behaviors, independently of the size of the table; also there is a GUC
-	 * variable that can disable synchronized scanning.)
-	 *
-	 * 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)
-	{
-		allow_strat = scan->rs_allow_strat;
-		allow_sync = scan->rs_allow_sync;
-	}
-	else
-		allow_strat = allow_sync = false;
-
-	if (allow_strat)
-	{
-		/* During a rescan, keep the previous strategy object. */
-		if (scan->rs_strategy == NULL)
-			scan->rs_strategy = GetAccessStrategy(BAS_BULKREAD);
-	}
-	else
-	{
-		if (scan->rs_strategy != NULL)
-			FreeAccessStrategy(scan->rs_strategy);
-		scan->rs_strategy = NULL;
-	}
-
-	if (scan->rs_parallel != NULL)
-	{
-		/* For parallel scan, believe whatever ParallelHeapScanDesc says. */
-		scan->rs_syncscan = scan->rs_parallel->phs_syncscan;
-	}
-	else if (keep_startblock)
-	{
-		/*
-		 * When rescanning, we want to keep the previous startblock setting,
-		 * so that rewinding a cursor doesn't generate surprising results.
-		 * Reset the active syncscan setting, though.
-		 */
-		scan->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);
-	}
-	else
-	{
-		scan->rs_syncscan = false;
-		scan->rs_startblock = 0;
-	}
-
-	scan->rs_numblocks = InvalidBlockNumber;
-	scan->rs_inited = false;
-	scan->rs_ctup.t_data = NULL;
-	ItemPointerSetInvalid(&scan->rs_ctup.t_self);
-	scan->rs_cbuf = InvalidBuffer;
-	scan->rs_cblock = InvalidBlockNumber;
-
-	/* page-at-a-time fields are always invalid when not rs_inited */
-
-	/*
-	 * copy the scan key, if appropriate
-	 */
-	if (key != NULL)
-		memcpy(scan->rs_key, key, scan->rs_nkeys * sizeof(ScanKeyData));
-
-	/*
-	 * Currently, we don't have a stats counter for bitmap heap scans (but the
-	 * underlying bitmap index scans will be counted) or sample scans (we only
-	 * update stats for tuple fetches there)
-	 */
-	if (!scan->rs_bitmapscan && !scan->rs_samplescan)
-		pgstat_count_heap_scan(scan->rs_rd);
-}
-
-/*
- * 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
-heap_setscanlimits(HeapScanDesc scan, BlockNumber startBlk, BlockNumber numBlks)
-{
-	Assert(!scan->rs_inited);	/* else too late to change */
-	Assert(!scan->rs_syncscan); /* else rs_startblock is significant */
-
-	/* Check startBlk is valid (but allow case of zero blocks...) */
-	Assert(startBlk == 0 || startBlk < scan->rs_nblocks);
-
-	scan->rs_startblock = startBlk;
-	scan->rs_numblocks = numBlks;
-}
-
 /*
  * heapgetpage - subroutine for heapgettup()
  *
@@ -363,621 +217,6 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
 	scan->rs_ntuples = ntup;
 }
 
-/* ----------------
- *		heapgettup - fetch next heap tuple
- *
- *		Initialize the scan if not already done; then advance to the next
- *		tuple as indicated by "dir"; return the next tuple in scan->rs_ctup,
- *		or set scan->rs_ctup.t_data = NULL if no more tuples.
- *
- * dir == NoMovementScanDirection means "re-fetch the tuple indicated
- * by scan->rs_ctup".
- *
- * Note: the reason nkeys/key are passed separately, even though they are
- * kept in the scan descriptor, is that the caller may not want us to check
- * the scankeys.
- *
- * Note: when we fall off the end of the scan in either direction, we
- * reset rs_inited.  This means that a further request with the same
- * scan direction will restart the scan, which is a bit odd, but a
- * request with the opposite scan direction will start a fresh scan
- * in the proper direction.  The latter is required behavior for cursors,
- * while the former case is generally undefined behavior in Postgres
- * so we don't care too much.
- * ----------------
- */
-static void
-heapgettup(HeapScanDesc scan,
-		   ScanDirection dir,
-		   int nkeys,
-		   ScanKey key)
-{
-	HeapTuple	tuple = &(scan->rs_ctup);
-	Snapshot	snapshot = scan->rs_snapshot;
-	bool		backward = ScanDirectionIsBackward(dir);
-	BlockNumber page;
-	bool		finished;
-	Page		dp;
-	int			lines;
-	OffsetNumber lineoff;
-	int			linesleft;
-	ItemId		lpp;
-
-	/*
-	 * calculate next starting lineoff, given scan direction
-	 */
-	if (ScanDirectionIsForward(dir))
-	{
-		if (!scan->rs_inited)
-		{
-			/*
-			 * return null immediately if relation is empty
-			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
-			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
-				tuple->t_data = NULL;
-				return;
-			}
-			if (scan->rs_parallel != NULL)
-			{
-				heap_parallelscan_startblock_init(scan);
-
-				page = heap_parallelscan_nextpage(scan);
-
-				/* Other processes might have already finished the scan. */
-				if (page == InvalidBlockNumber)
-				{
-					Assert(!BufferIsValid(scan->rs_cbuf));
-					tuple->t_data = NULL;
-					return;
-				}
-			}
-			else
-				page = scan->rs_startblock; /* first page */
-			heapgetpage(scan, page);
-			lineoff = FirstOffsetNumber;	/* first offnum */
-			scan->rs_inited = true;
-		}
-		else
-		{
-			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-			lineoff =			/* next offnum */
-				OffsetNumberNext(ItemPointerGetOffsetNumber(&(tuple->t_self)));
-		}
-
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
-		lines = PageGetMaxOffsetNumber(dp);
-		/* page and lineoff now reference the physically next tid */
-
-		linesleft = lines - lineoff + 1;
-	}
-	else if (backward)
-	{
-		/* backward parallel scan not supported */
-		Assert(scan->rs_parallel == NULL);
-
-		if (!scan->rs_inited)
-		{
-			/*
-			 * return null immediately if relation is empty
-			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
-			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
-				tuple->t_data = NULL;
-				return;
-			}
-
-			/*
-			 * Disable reporting to syncscan logic in a backwards scan; it's
-			 * not very likely anyone else is doing the same thing at the same
-			 * time, and much more likely that we'll just bollix things for
-			 * forward scanners.
-			 */
-			scan->rs_syncscan = false;
-			/* start from last page of the scan */
-			if (scan->rs_startblock > 0)
-				page = scan->rs_startblock - 1;
-			else
-				page = scan->rs_nblocks - 1;
-			heapgetpage(scan, page);
-		}
-		else
-		{
-			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-		}
-
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
-		lines = PageGetMaxOffsetNumber(dp);
-
-		if (!scan->rs_inited)
-		{
-			lineoff = lines;	/* final offnum */
-			scan->rs_inited = true;
-		}
-		else
-		{
-			lineoff =			/* previous offnum */
-				OffsetNumberPrev(ItemPointerGetOffsetNumber(&(tuple->t_self)));
-		}
-		/* page and lineoff now reference the physically previous tid */
-
-		linesleft = lineoff;
-	}
-	else
-	{
-		/*
-		 * ``no movement'' scan direction: refetch prior tuple
-		 */
-		if (!scan->rs_inited)
-		{
-			Assert(!BufferIsValid(scan->rs_cbuf));
-			tuple->t_data = NULL;
-			return;
-		}
-
-		page = ItemPointerGetBlockNumber(&(tuple->t_self));
-		if (page != scan->rs_cblock)
-			heapgetpage(scan, page);
-
-		/* Since the tuple was previously fetched, needn't lock page here */
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
-		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
-		lpp = PageGetItemId(dp, lineoff);
-		Assert(ItemIdIsNormal(lpp));
-
-		tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
-		tuple->t_len = ItemIdGetLength(lpp);
-
-		return;
-	}
-
-	/*
-	 * advance the scan until we find a qualifying tuple or run out of stuff
-	 * to scan
-	 */
-	lpp = PageGetItemId(dp, lineoff);
-	for (;;)
-	{
-		while (linesleft > 0)
-		{
-			if (ItemIdIsNormal(lpp))
-			{
-				bool		valid;
-
-				tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
-				tuple->t_len = ItemIdGetLength(lpp);
-				ItemPointerSet(&(tuple->t_self), page, lineoff);
-
-				/*
-				 * if current tuple qualifies, return it.
-				 */
-				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine,
-													 tuple,
-													 snapshot,
-													 scan->rs_cbuf);
-
-				CheckForSerializableConflictOut(valid, scan->rs_rd, tuple,
-												scan->rs_cbuf, snapshot);
-
-				if (valid && key != NULL)
-					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
-								nkeys, key, valid);
-
-				if (valid)
-				{
-					LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
-					return;
-				}
-			}
-
-			/*
-			 * otherwise move to the next item on the page
-			 */
-			--linesleft;
-			if (backward)
-			{
-				--lpp;			/* move back in this page's ItemId array */
-				--lineoff;
-			}
-			else
-			{
-				++lpp;			/* move forward in this page's ItemId array */
-				++lineoff;
-			}
-		}
-
-		/*
-		 * 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);
-
-		/*
-		 * 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);
-			if (page == 0)
-				page = scan->rs_nblocks;
-			page--;
-		}
-		else if (scan->rs_parallel != NULL)
-		{
-			page = heap_parallelscan_nextpage(scan);
-			finished = (page == InvalidBlockNumber);
-		}
-		else
-		{
-			page++;
-			if (page >= scan->rs_nblocks)
-				page = 0;
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
-
-			/*
-			 * Report our new scan position for synchronization purposes. We
-			 * don't do that when moving backwards, however. That would just
-			 * mess up any other forward-moving scanners.
-			 *
-			 * Note: we do this before checking for end of scan so that the
-			 * final state of the position hint is back at the start of the
-			 * rel.  That's not strictly necessary, but otherwise when you run
-			 * the same query multiple times the starting position would shift
-			 * 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);
-		}
-
-		/*
-		 * return NULL if we've exhausted all the pages
-		 */
-		if (finished)
-		{
-			if (BufferIsValid(scan->rs_cbuf))
-				ReleaseBuffer(scan->rs_cbuf);
-			scan->rs_cbuf = InvalidBuffer;
-			scan->rs_cblock = InvalidBlockNumber;
-			tuple->t_data = NULL;
-			scan->rs_inited = false;
-			return;
-		}
-
-		heapgetpage(scan, page);
-
-		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
-
-		dp = BufferGetPage(scan->rs_cbuf);
-		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
-		lines = PageGetMaxOffsetNumber((Page) dp);
-		linesleft = lines;
-		if (backward)
-		{
-			lineoff = lines;
-			lpp = PageGetItemId(dp, lines);
-		}
-		else
-		{
-			lineoff = FirstOffsetNumber;
-			lpp = PageGetItemId(dp, FirstOffsetNumber);
-		}
-	}
-}
-
-/* ----------------
- *		heapgettup_pagemode - fetch next heap tuple in page-at-a-time mode
- *
- *		Same API as heapgettup, but used in page-at-a-time mode
- *
- * The internal logic is much the same as heapgettup's too, but there are some
- * differences: we do not take the buffer content lock (that only needs to
- * happen inside heapgetpage), and we iterate through just the tuples listed
- * in rs_vistuples[] rather than all tuples on the page.  Notice that
- * lineindex is 0-based, where the corresponding loop variable lineoff in
- * heapgettup is 1-based.
- * ----------------
- */
-static void
-heapgettup_pagemode(HeapScanDesc scan,
-					ScanDirection dir,
-					int nkeys,
-					ScanKey key)
-{
-	HeapTuple	tuple = &(scan->rs_ctup);
-	bool		backward = ScanDirectionIsBackward(dir);
-	BlockNumber page;
-	bool		finished;
-	Page		dp;
-	int			lines;
-	int			lineindex;
-	OffsetNumber lineoff;
-	int			linesleft;
-	ItemId		lpp;
-
-	/*
-	 * calculate next starting lineindex, given scan direction
-	 */
-	if (ScanDirectionIsForward(dir))
-	{
-		if (!scan->rs_inited)
-		{
-			/*
-			 * return null immediately if relation is empty
-			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
-			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
-				tuple->t_data = NULL;
-				return;
-			}
-			if (scan->rs_parallel != NULL)
-			{
-				heap_parallelscan_startblock_init(scan);
-
-				page = heap_parallelscan_nextpage(scan);
-
-				/* Other processes might have already finished the scan. */
-				if (page == InvalidBlockNumber)
-				{
-					Assert(!BufferIsValid(scan->rs_cbuf));
-					tuple->t_data = NULL;
-					return;
-				}
-			}
-			else
-				page = scan->rs_startblock; /* first page */
-			heapgetpage(scan, page);
-			lineindex = 0;
-			scan->rs_inited = true;
-		}
-		else
-		{
-			/* continue from previously returned page/tuple */
-			page = scan->rs_cblock; /* current page */
-			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;
-		/* page and lineindex now reference the next visible tid */
-
-		linesleft = lines - lineindex;
-	}
-	else if (backward)
-	{
-		/* backward parallel scan not supported */
-		Assert(scan->rs_parallel == NULL);
-
-		if (!scan->rs_inited)
-		{
-			/*
-			 * return null immediately if relation is empty
-			 */
-			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
-			{
-				Assert(!BufferIsValid(scan->rs_cbuf));
-				tuple->t_data = NULL;
-				return;
-			}
-
-			/*
-			 * Disable reporting to syncscan logic in a backwards scan; it's
-			 * not very likely anyone else is doing the same thing at the same
-			 * time, and much more likely that we'll just bollix things for
-			 * forward scanners.
-			 */
-			scan->rs_syncscan = false;
-			/* start from last page of the scan */
-			if (scan->rs_startblock > 0)
-				page = scan->rs_startblock - 1;
-			else
-				page = scan->rs_nblocks - 1;
-			heapgetpage(scan, page);
-		}
-		else
-		{
-			/* continue from previously returned page/tuple */
-			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;
-
-		if (!scan->rs_inited)
-		{
-			lineindex = lines - 1;
-			scan->rs_inited = true;
-		}
-		else
-		{
-			lineindex = scan->rs_cindex - 1;
-		}
-		/* page and lineindex now reference the previous visible tid */
-
-		linesleft = lineindex + 1;
-	}
-	else
-	{
-		/*
-		 * ``no movement'' scan direction: refetch prior tuple
-		 */
-		if (!scan->rs_inited)
-		{
-			Assert(!BufferIsValid(scan->rs_cbuf));
-			tuple->t_data = NULL;
-			return;
-		}
-
-		page = ItemPointerGetBlockNumber(&(tuple->t_self));
-		if (page != scan->rs_cblock)
-			heapgetpage(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);
-		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
-		lpp = PageGetItemId(dp, lineoff);
-		Assert(ItemIdIsNormal(lpp));
-
-		tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
-		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]);
-
-		return;
-	}
-
-	/*
-	 * advance the scan until we find a qualifying tuple or run out of stuff
-	 * to scan
-	 */
-	for (;;)
-	{
-		while (linesleft > 0)
-		{
-			lineoff = scan->rs_vistuples[lineindex];
-			lpp = PageGetItemId(dp, lineoff);
-			Assert(ItemIdIsNormal(lpp));
-
-			tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
-			tuple->t_len = ItemIdGetLength(lpp);
-			ItemPointerSet(&(tuple->t_self), page, lineoff);
-
-			/*
-			 * if current tuple qualifies, return it.
-			 */
-			if (HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, tuple, scan->rs_snapshot, scan->rs_cbuf))
-			{
-				/*
-				 * if current tuple qualifies, return it.
-				 */
-				if (key != NULL)
-				{
-					bool		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;
-				}
-			}
-
-			/*
-			 * otherwise move to the next item on the page
-			 */
-			--linesleft;
-			if (backward)
-				--lineindex;
-			else
-				++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.
-		 */
-		if (backward)
-		{
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
-			if (page == 0)
-				page = scan->rs_nblocks;
-			page--;
-		}
-		else if (scan->rs_parallel != NULL)
-		{
-			page = heap_parallelscan_nextpage(scan);
-			finished = (page == InvalidBlockNumber);
-		}
-		else
-		{
-			page++;
-			if (page >= scan->rs_nblocks)
-				page = 0;
-			finished = (page == scan->rs_startblock) ||
-				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
-
-			/*
-			 * Report our new scan position for synchronization purposes. We
-			 * don't do that when moving backwards, however. That would just
-			 * mess up any other forward-moving scanners.
-			 *
-			 * Note: we do this before checking for end of scan so that the
-			 * final state of the position hint is back at the start of the
-			 * rel.  That's not strictly necessary, but otherwise when you run
-			 * the same query multiple times the starting position would shift
-			 * 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);
-		}
-
-		/*
-		 * return NULL if we've exhausted all the pages
-		 */
-		if (finished)
-		{
-			if (BufferIsValid(scan->rs_cbuf))
-				ReleaseBuffer(scan->rs_cbuf);
-			scan->rs_cbuf = InvalidBuffer;
-			scan->rs_cblock = InvalidBlockNumber;
-			tuple->t_data = NULL;
-			scan->rs_inited = false;
-			return;
-		}
-
-		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;
-		linesleft = lines;
-		if (backward)
-			lineindex = lines - 1;
-		else
-			lineindex = 0;
-	}
-}
-
-
 #if defined(DISABLE_COMPLEX_MACRO)
 /*
  * This is formatted so oddly so that the correspondence to the macro
@@ -1200,320 +439,99 @@ relation_close(Relation relation, LOCKMODE lockmode)
 	RelationClose(relation);
 
 	if (lockmode != NoLock)
-		UnlockRelationId(&relid, lockmode);
-}
-
-
-/* ----------------
- *		heap_open - open a heap relation by relation OID
- *
- *		This is essentially relation_open plus check that the relation
- *		is not an index nor a composite type.  (The caller should also
- *		check that it's not a view or foreign table before assuming it has
- *		storage.)
- * ----------------
- */
-Relation
-heap_open(Oid relationId, LOCKMODE lockmode)
-{
-	Relation	r;
-
-	r = relation_open(relationId, lockmode);
-
-	if (r->rd_rel->relkind == RELKIND_INDEX)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is an index",
-						RelationGetRelationName(r))));
-	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is a composite type",
-						RelationGetRelationName(r))));
-
-	return r;
-}
-
-/* ----------------
- *		heap_openrv - open a heap relation specified
- *		by a RangeVar node
- *
- *		As above, but relation is specified by a RangeVar.
- * ----------------
- */
-Relation
-heap_openrv(const RangeVar *relation, LOCKMODE lockmode)
-{
-	Relation	r;
-
-	r = relation_openrv(relation, lockmode);
-
-	if (r->rd_rel->relkind == RELKIND_INDEX)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is an index",
-						RelationGetRelationName(r))));
-	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is a composite type",
-						RelationGetRelationName(r))));
-
-	return r;
-}
-
-/* ----------------
- *		heap_openrv_extended - open a heap relation specified
- *		by a RangeVar node
- *
- *		As above, but optionally return NULL instead of failing for
- *		relation-not-found.
- * ----------------
- */
-Relation
-heap_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
-					 bool missing_ok)
-{
-	Relation	r;
-
-	r = relation_openrv_extended(relation, lockmode, missing_ok);
-
-	if (r)
-	{
-		if (r->rd_rel->relkind == RELKIND_INDEX)
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("\"%s\" is an index",
-							RelationGetRelationName(r))));
-		else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("\"%s\" is a composite type",
-							RelationGetRelationName(r))));
-	}
-
-	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)
-{
-	HeapScanDesc scan;
-
-	/*
-	 * increment relation ref count while scanning relation
-	 *
-	 * This is just to make really sure the relcache entry won't go away while
-	 * the scan has a pointer to it.  Caller should be holding the rel open
-	 * anyway, so this is redundant in all normal scenarios...
-	 */
-	RelationIncrementReferenceCount(relation);
-
-	/*
-	 * allocate and initialize scan descriptor
-	 */
-	scan = (HeapScanDesc) palloc(sizeof(HeapScanDescData));
-
-	scan->rs_rd = relation;
-	scan->rs_snapshot = snapshot;
-	scan->rs_nkeys = nkeys;
-	scan->rs_bitmapscan = is_bitmapscan;
-	scan->rs_samplescan = is_samplescan;
-	scan->rs_strategy = NULL;	/* set in initscan */
-	scan->rs_allow_strat = allow_strat;
-	scan->rs_allow_sync = allow_sync;
-	scan->rs_temp_snap = temp_snap;
-	scan->rs_parallel = parallel_scan;
-
-	/*
-	 * we can use page-at-a-time mode if it's an MVCC-safe snapshot
-	 */
-	scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(snapshot);
-
-	/*
-	 * For a seqscan in a serializable transaction, acquire a predicate lock
-	 * on the entire relation. This is required not only to lock all the
-	 * matching tuples, but also to conflict with new insertions into the
-	 * table. In an indexscan, we take page locks on the index pages covering
-	 * the range specified in the scan qual, but in a heap scan there is
-	 * nothing more fine-grained to lock. A bitmap scan is a different story,
-	 * there we have already scanned the index and locked the index pages
-	 * covering the predicate. But in that case we still have to lock any
-	 * matching heap tuples.
-	 */
-	if (!is_bitmapscan)
-		PredicateLockRelation(relation, snapshot);
-
-	/* we only need to set this up once */
-	scan->rs_ctup.t_tableOid = RelationGetRelid(relation);
-
-	/*
-	 * we do this here instead of in initscan() because heap_rescan also calls
-	 * initscan() and we don't want to allocate memory again
-	 */
-	if (nkeys > 0)
-		scan->rs_key = (ScanKey) palloc(sizeof(ScanKeyData) * nkeys);
-	else
-		scan->rs_key = NULL;
-
-	initscan(scan, key, false);
-
-	return scan;
+		UnlockRelationId(&relid, lockmode);
 }
 
+
 /* ----------------
- *		heap_rescan		- restart a relation scan
+ *		heap_open - open a heap relation by relation OID
+ *
+ *		This is essentially relation_open plus check that the relation
+ *		is not an index nor a composite type.  (The caller should also
+ *		check that it's not a view or foreign table before assuming it has
+ *		storage.)
  * ----------------
  */
-void
-heap_rescan(HeapScanDesc scan,
-			ScanKey key)
+Relation
+heap_open(Oid relationId, LOCKMODE lockmode)
 {
-	/*
-	 * unpin scan buffers
-	 */
-	if (BufferIsValid(scan->rs_cbuf))
-		ReleaseBuffer(scan->rs_cbuf);
+	Relation	r;
 
-	/*
-	 * reinitialize scan descriptor
-	 */
-	initscan(scan, key, true);
+	r = relation_open(relationId, lockmode);
+
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is an index",
+						RelationGetRelationName(r))));
+	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is a composite type",
+						RelationGetRelationName(r))));
+
+	return r;
 }
 
 /* ----------------
- *		heap_rescan_set_params	- restart a relation scan after changing params
+ *		heap_openrv - open a heap relation specified
+ *		by a RangeVar node
  *
- * 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.
+ *		As above, but relation is specified by a RangeVar.
  * ----------------
  */
-void
-heap_rescan_set_params(HeapScanDesc scan, ScanKey key,
-					   bool allow_strat, bool allow_sync, bool allow_pagemode)
+Relation
+heap_openrv(const RangeVar *relation, LOCKMODE lockmode)
 {
-	/* 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);
+	Relation	r;
+
+	r = relation_openrv(relation, lockmode);
+
+	if (r->rd_rel->relkind == RELKIND_INDEX)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is an index",
+						RelationGetRelationName(r))));
+	else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is a composite type",
+						RelationGetRelationName(r))));
+
+	return r;
 }
 
 /* ----------------
- *		heap_endscan	- end relation scan
+ *		heap_openrv_extended - open a heap relation specified
+ *		by a RangeVar node
  *
- *		See how to integrate with index scans.
- *		Check handling if reldesc caching.
+ *		As above, but optionally return NULL instead of failing for
+ *		relation-not-found.
  * ----------------
  */
-void
-heap_endscan(HeapScanDesc scan)
+Relation
+heap_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
+					 bool missing_ok)
 {
-	/* Note: no locking manipulations needed */
-
-	/*
-	 * unpin scan buffers
-	 */
-	if (BufferIsValid(scan->rs_cbuf))
-		ReleaseBuffer(scan->rs_cbuf);
-
-	/*
-	 * decrement relation reference count and free scan descriptor storage
-	 */
-	RelationDecrementReferenceCount(scan->rs_rd);
-
-	if (scan->rs_key)
-		pfree(scan->rs_key);
+	Relation	r;
 
-	if (scan->rs_strategy != NULL)
-		FreeAccessStrategy(scan->rs_strategy);
+	r = relation_openrv_extended(relation, lockmode, missing_ok);
 
-	if (scan->rs_temp_snap)
-		UnregisterSnapshot(scan->rs_snapshot);
+	if (r)
+	{
+		if (r->rd_rel->relkind == RELKIND_INDEX)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("\"%s\" is an index",
+							RelationGetRelationName(r))));
+		else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("\"%s\" is a composite type",
+							RelationGetRelationName(r))));
+	}
 
-	pfree(scan);
+	return r;
 }
 
 /* ----------------
@@ -1567,383 +585,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
- *
- *		Determine where the parallel seq scan should start.  This function may
- *		be called many times, once by each parallel worker.  We must be careful
- *		only to set the startblock once.
- * ----------------
- */
-static void
-heap_parallelscan_startblock_init(HeapScanDesc scan)
-{
-	BlockNumber sync_startpage = InvalidBlockNumber;
-	ParallelHeapScanDesc parallel_scan;
-
-	Assert(scan->rs_parallel);
-	parallel_scan = scan->rs_parallel;
-
-retry:
-	/* Grab the spinlock. */
-	SpinLockAcquire(&parallel_scan->phs_mutex);
-
-	/*
-	 * If the scan's startblock has not yet been initialized, we must do so
-	 * now.  If this is not a synchronized scan, we just start at block 0, but
-	 * if it is a synchronized scan, we must get the starting position from
-	 * the synchronized scan machinery.  We can't hold the spinlock while
-	 * doing that, though, so release the spinlock, get the information we
-	 * need, and retry.  If nobody else has initialized the scan in the
-	 * meantime, we'll fill in the value we fetched on the second time
-	 * through.
-	 */
-	if (parallel_scan->phs_startblock == InvalidBlockNumber)
-	{
-		if (!parallel_scan->phs_syncscan)
-			parallel_scan->phs_startblock = 0;
-		else if (sync_startpage != InvalidBlockNumber)
-			parallel_scan->phs_startblock = sync_startpage;
-		else
-		{
-			SpinLockRelease(&parallel_scan->phs_mutex);
-			sync_startpage = ss_get_location(scan->rs_rd, scan->rs_nblocks);
-			goto retry;
-		}
-	}
-	SpinLockRelease(&parallel_scan->phs_mutex);
-}
-
-/* ----------------
- *		heap_parallelscan_nextpage - get the next page to scan
- *
- *		Get the next page to scan.  Even if there are no pages left to scan,
- *		another backend could have grabbed a page to scan and not yet finished
- *		looking at it, so it doesn't follow that the scan is done when the
- *		first backend gets an InvalidBlockNumber return.
- * ----------------
- */
-static BlockNumber
-heap_parallelscan_nextpage(HeapScanDesc scan)
-{
-	BlockNumber page;
-	ParallelHeapScanDesc parallel_scan;
-	uint64		nallocated;
-
-	Assert(scan->rs_parallel);
-	parallel_scan = scan->rs_parallel;
-
-	/*
-	 * phs_nallocated tracks how many pages have been allocated to workers
-	 * already.  When phs_nallocated >= rs_nblocks, all blocks have been
-	 * allocated.
-	 *
-	 * Because we use an atomic fetch-and-add to fetch the current value, the
-	 * phs_nallocated counter will exceed rs_nblocks, because workers will
-	 * still increment the value, when they try to allocate the next block but
-	 * all blocks have been allocated already. The counter must be 64 bits
-	 * wide because of that, to avoid wrapping around when rs_nblocks is close
-	 * to 2^32.
-	 *
-	 * The actual page to return is calculated by adding the counter to the
-	 * starting block number, modulo nblocks.
-	 */
-	nallocated = pg_atomic_fetch_add_u64(&parallel_scan->phs_nallocated, 1);
-	if (nallocated >= scan->rs_nblocks)
-		page = InvalidBlockNumber;	/* all blocks have been allocated */
-	else
-		page = (nallocated + parallel_scan->phs_startblock) % scan->rs_nblocks;
-
-	/*
-	 * Report scan location.  Normally, we report the current page number.
-	 * When we reach the end of the scan, though, we report the starting page,
-	 * not the ending page, just so the starting positions for later scans
-	 * 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 (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);
-	}
-
-	return page;
-}
-
-/* ----------------
- *		heap_update_snapshot
- *
- *		Update snapshot info in heap scan descriptor.
- * ----------------
- */
-void
-heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
-{
-	Assert(IsMVCCSnapshot(snapshot));
-
-	RegisterSnapshot(snapshot);
-	scan->rs_snapshot = snapshot;
-	scan->rs_temp_snap = true;
-}
-
-/* ----------------
- *		heap_getnext	- retrieve next tuple in scan
- *
- *		Fix to work with index relations.
- *		We don't return the buffer anymore, but you can get it from the
- *		returned HeapTuple.
- * ----------------
- */
-
-#ifdef HEAPDEBUGALL
-#define HEAPDEBUG_1 \
-	elog(DEBUG2, "heap_getnext([%s,nkeys=%d],dir=%d) called", \
-		 RelationGetRelationName(scan->rs_rd), scan->rs_nkeys, (int) direction)
-#define HEAPDEBUG_2 \
-	elog(DEBUG2, "heap_getnext returning EOS")
-#define HEAPDEBUG_3 \
-	elog(DEBUG2, "heap_getnext returning tuple")
-#else
-#define HEAPDEBUG_1
-#define HEAPDEBUG_2
-#define HEAPDEBUG_3
-#endif							/* !defined(HEAPDEBUGALL) */
-
-
-HeapTuple
-heap_getnext(HeapScanDesc scan, ScanDirection direction)
-{
-	/* Note: no locking manipulations needed */
-
-	HEAPDEBUG_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)
-	{
-		HEAPDEBUG_2;			/* heap_getnext returning EOS */
-		return NULL;
-	}
-
-	/*
-	 * if we get here it means we have a new current scan tuple, so point to
-	 * the proper return buffer and return the tuple.
-	 */
-	HEAPDEBUG_3;				/* heap_getnext returning tuple */
-
-	pgstat_count_heap_getnext(scan->rs_rd);
-
-	return heap_copytuple(&(scan->rs_ctup));
-}
-
-/*
- *	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
-heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
-					   Snapshot snapshot, HeapTuple heapTuple,
-					   bool *all_dead, bool first_call)
-{
-	Page		dp = (Page) BufferGetPage(buffer);
-	TransactionId prev_xmax = InvalidTransactionId;
-	OffsetNumber offnum;
-	bool		at_chain_start;
-	bool		valid;
-	bool		skip;
-
-	/* If this is not the first call, previous call returned a (live!) tuple */
-	if (all_dead)
-		*all_dead = first_call;
-
-	Assert(TransactionIdIsValid(RecentGlobalXmin));
-
-	Assert(ItemPointerGetBlockNumber(tid) == BufferGetBlockNumber(buffer));
-	offnum = ItemPointerGetOffsetNumber(tid);
-	at_chain_start = first_call;
-	skip = !first_call;
-
-	heapTuple->t_self = *tid;
-
-	/* Scan through possible multiple members of HOT-chain */
-	for (;;)
-	{
-		ItemId		lp;
-
-		/* check for bogus TID */
-		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(dp))
-			break;
-
-		lp = PageGetItemId(dp, offnum);
-
-		/* check for unused, dead, or redirected items */
-		if (!ItemIdIsNormal(lp))
-		{
-			/* We should only see a redirect at start of chain */
-			if (ItemIdIsRedirected(lp) && at_chain_start)
-			{
-				/* Follow the redirect */
-				offnum = ItemIdGetRedirect(lp);
-				at_chain_start = false;
-				continue;
-			}
-			/* else must be end of chain */
-			break;
-		}
-
-		heapTuple->t_data = (HeapTupleHeader) PageGetItem(dp, lp);
-		heapTuple->t_len = ItemIdGetLength(lp);
-		heapTuple->t_tableOid = RelationGetRelid(relation);
-		ItemPointerSetOffsetNumber(&heapTuple->t_self, offnum);
-
-		/*
-		 * Shouldn't see a HEAP_ONLY tuple at chain start.
-		 */
-		if (at_chain_start && HeapTupleIsHeapOnly(heapTuple))
-			break;
-
-		/*
-		 * The xmin should match the previous xmax value, else chain is
-		 * broken.
-		 */
-		if (TransactionIdIsValid(prev_xmax) &&
-			!TransactionIdEquals(prev_xmax,
-								 HeapTupleHeaderGetXmin(heapTuple->t_data)))
-			break;
-
-		/*
-		 * When first_call is true (and thus, skip is initially false) we'll
-		 * return the first tuple we find.  But on later passes, heapTuple
-		 * will initially be pointing to the tuple we returned last time.
-		 * Returning it again would be incorrect (and would loop forever), so
-		 * we skip it and return the next match we find.
-		 */
-		if (!skip)
-		{
-			/*
-			 * For the benefit of logical decoding, have t_self point at the
-			 * element of the HOT chain we're currently investigating instead
-			 * of the root tuple of the HOT chain. This is important because
-			 * the *Satisfies routine for historical mvcc snapshots needs the
-			 * correct tid to decide about the visibility in some cases.
-			 */
-			ItemPointerSet(&(heapTuple->t_self), BufferGetBlockNumber(buffer), offnum);
-
-			/* If it's visible per the snapshot, we must return it */
-			valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, heapTuple, snapshot, buffer);
-			CheckForSerializableConflictOut(valid, relation, heapTuple,
-											buffer, snapshot);
-			/* reset to original, non-redirected, tid */
-			heapTuple->t_self = *tid;
-
-			if (valid)
-			{
-				ItemPointerSetOffsetNumber(tid, offnum);
-				PredicateLockTuple(relation, heapTuple, snapshot);
-				if (all_dead)
-					*all_dead = false;
-				return true;
-			}
-		}
-		skip = false;
-
-		/*
-		 * If we can't see it, maybe no one else can either.  At caller
-		 * request, check whether all chain members are dead to all
-		 * transactions.
-		 *
-		 * Note: if you change the criterion here for what is "dead", fix the
-		 * planner's get_actual_variable_range() function to match.
-		 */
-		if (all_dead && *all_dead &&
-			!HeapTupleIsSurelyDead(heapTuple, RecentGlobalXmin))
-			*all_dead = false;
-
-		/*
-		 * Check to see if HOT chain continues past this tuple; if so fetch
-		 * the next offnum and loop around.
-		 */
-		if (HeapTupleIsHotUpdated(heapTuple))
-		{
-			Assert(ItemPointerGetBlockNumber(&heapTuple->t_data->t_ctid) ==
-				   ItemPointerGetBlockNumber(tid));
-			offnum = ItemPointerGetOffsetNumber(&heapTuple->t_data->t_ctid);
-			at_chain_start = false;
-			prev_xmax = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
-		}
-		else
-			break;				/* end of chain */
-	}
-
-	return false;
-}
-
-/*
- *	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
-heap_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 = heap_hot_search_buffer(tid, relation, buffer, snapshot,
-									&heapTuple, all_dead, true);
-	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-	ReleaseBuffer(buffer);
-	return result;
-}
-
 /*
  * UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends
  *
@@ -4777,32 +3418,6 @@ heap_execute_freeze_tuple(HeapTupleHeader tuple, xl_heap_freeze_tuple *frz)
 	tuple->t_infomask2 = frz->t_infomask2;
 }
 
-/*
- * heap_freeze_tuple
- *		Freeze tuple in place, without WAL logging.
- *
- * Useful for callers like CLUSTER that perform their own WAL logging.
- */
-bool
-heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
-				  TransactionId cutoff_multi)
-{
-	xl_heap_freeze_tuple frz;
-	bool		do_freeze;
-	bool		tuple_totally_frozen;
-
-	do_freeze = heap_prepare_freeze_tuple(tuple, cutoff_xid, cutoff_multi,
-										  &frz, &tuple_totally_frozen);
-
-	/*
-	 * Note that because this is not a WAL-logged operation, we don't need to
-	 * fill in the offset in the freeze record.
-	 */
-
-	if (do_freeze)
-		heap_execute_freeze_tuple(tuple, &frz);
-	return do_freeze;
-}
 
 /*
  * For a given MultiXactId, return the hint bits that should be set in the
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 567e80de8c..4fa5ebc4e3 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -1700,6 +1700,1111 @@ HeapTupleSatisfies(StorageTuple stup, Snapshot snapshot, Buffer buffer)
 	}
 }
 
+/* ----------------------------------------------------------------
+ *						 heap support routines
+ * ----------------------------------------------------------------
+ */
+
+/* ----------------
+ *		heap_parallelscan_startblock_init - find and set the scan's startblock
+ *
+ *		Determine where the parallel seq scan should start.  This function may
+ *		be called many times, once by each parallel worker.  We must be careful
+ *		only to set the startblock once.
+ * ----------------
+ */
+static void
+heap_parallelscan_startblock_init(HeapScanDesc scan)
+{
+	BlockNumber sync_startpage = InvalidBlockNumber;
+	ParallelHeapScanDesc parallel_scan;
+
+	Assert(scan->rs_parallel);
+	parallel_scan = scan->rs_parallel;
+
+retry:
+	/* Grab the spinlock. */
+	SpinLockAcquire(&parallel_scan->phs_mutex);
+
+	/*
+	 * If the scan's startblock has not yet been initialized, we must do so
+	 * now.  If this is not a synchronized scan, we just start at block 0, but
+	 * if it is a synchronized scan, we must get the starting position from
+	 * the synchronized scan machinery.  We can't hold the spinlock while
+	 * doing that, though, so release the spinlock, get the information we
+	 * need, and retry.  If nobody else has initialized the scan in the
+	 * meantime, we'll fill in the value we fetched on the second time
+	 * through.
+	 */
+	if (parallel_scan->phs_startblock == InvalidBlockNumber)
+	{
+		if (!parallel_scan->phs_syncscan)
+			parallel_scan->phs_startblock = 0;
+		else if (sync_startpage != InvalidBlockNumber)
+			parallel_scan->phs_startblock = sync_startpage;
+		else
+		{
+			SpinLockRelease(&parallel_scan->phs_mutex);
+			sync_startpage = ss_get_location(scan->rs_rd, scan->rs_nblocks);
+			goto retry;
+		}
+	}
+	SpinLockRelease(&parallel_scan->phs_mutex);
+}
+
+/* ----------------
+ *		heap_parallelscan_nextpage - get the next page to scan
+ *
+ *		Get the next page to scan.  Even if there are no pages left to scan,
+ *		another backend could have grabbed a page to scan and not yet finished
+ *		looking at it, so it doesn't follow that the scan is done when the
+ *		first backend gets an InvalidBlockNumber return.
+ * ----------------
+ */
+static BlockNumber
+heap_parallelscan_nextpage(HeapScanDesc scan)
+{
+	BlockNumber page;
+	ParallelHeapScanDesc parallel_scan;
+	uint64		nallocated;
+
+	Assert(scan->rs_parallel);
+	parallel_scan = scan->rs_parallel;
+
+	/*
+	 * phs_nallocated tracks how many pages have been allocated to workers
+	 * already.  When phs_nallocated >= rs_nblocks, all blocks have been
+	 * allocated.
+	 *
+	 * Because we use an atomic fetch-and-add to fetch the current value, the
+	 * phs_nallocated counter will exceed rs_nblocks, because workers will
+	 * still increment the value, when they try to allocate the next block but
+	 * all blocks have been allocated already. The counter must be 64 bits
+	 * wide because of that, to avoid wrapping around when rs_nblocks is close
+	 * to 2^32.
+	 *
+	 * The actual page to return is calculated by adding the counter to the
+	 * starting block number, modulo nblocks.
+	 */
+	nallocated = pg_atomic_fetch_add_u64(&parallel_scan->phs_nallocated, 1);
+	if (nallocated >= scan->rs_nblocks)
+		page = InvalidBlockNumber;	/* all blocks have been allocated */
+	else
+		page = (nallocated + parallel_scan->phs_startblock) % scan->rs_nblocks;
+
+	/*
+	 * Report scan location.  Normally, we report the current page number.
+	 * When we reach the end of the scan, though, we report the starting page,
+	 * not the ending page, just so the starting positions for later scans
+	 * 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 (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);
+	}
+
+	return page;
+}
+
+
+/* ----------------
+ *		initscan - scan code common to heap_beginscan and heap_rescan
+ * ----------------
+ */
+static void
+initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
+{
+	bool		allow_strat;
+	bool		allow_sync;
+
+	/*
+	 * Determine the number of blocks we have to scan.
+	 *
+	 * It is sufficient to do this once at scan start, since any tuples added
+	 * while the scan is in progress will be invisible to my snapshot anyway.
+	 * (That is not true when using a non-MVCC snapshot.  However, we couldn't
+	 * guarantee to return tuples added after scan start anyway, since they
+	 * might go into pages we already scanned.  To guarantee consistent
+	 * results for a non-MVCC snapshot, the caller must hold some higher-level
+	 * lock that ensures the interesting tuple(s) won't change.)
+	 */
+	if (scan->rs_parallel != NULL)
+		scan->rs_nblocks = scan->rs_parallel->phs_nblocks;
+	else
+		scan->rs_nblocks = RelationGetNumberOfBlocks(scan->rs_rd);
+
+	/*
+	 * If the table is large relative to NBuffers, use a bulk-read access
+	 * strategy and enable synchronized scanning (see syncscan.c).  Although
+	 * the thresholds for these features could be different, we make them the
+	 * same so that there are only two behaviors to tune rather than four.
+	 * (However, some callers need to be able to disable one or both of these
+	 * behaviors, independently of the size of the table; also there is a GUC
+	 * variable that can disable synchronized scanning.)
+	 *
+	 * 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)
+	{
+		allow_strat = scan->rs_allow_strat;
+		allow_sync = scan->rs_allow_sync;
+	}
+	else
+		allow_strat = allow_sync = false;
+
+	if (allow_strat)
+	{
+		/* During a rescan, keep the previous strategy object. */
+		if (scan->rs_strategy == NULL)
+			scan->rs_strategy = GetAccessStrategy(BAS_BULKREAD);
+	}
+	else
+	{
+		if (scan->rs_strategy != NULL)
+			FreeAccessStrategy(scan->rs_strategy);
+		scan->rs_strategy = NULL;
+	}
+
+	if (scan->rs_parallel != NULL)
+	{
+		/* For parallel scan, believe whatever ParallelHeapScanDesc says. */
+		scan->rs_syncscan = scan->rs_parallel->phs_syncscan;
+	}
+	else if (keep_startblock)
+	{
+		/*
+		 * When rescanning, we want to keep the previous startblock setting,
+		 * so that rewinding a cursor doesn't generate surprising results.
+		 * Reset the active syncscan setting, though.
+		 */
+		scan->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);
+	}
+	else
+	{
+		scan->rs_syncscan = false;
+		scan->rs_startblock = 0;
+	}
+
+	scan->rs_numblocks = InvalidBlockNumber;
+	scan->rs_inited = false;
+	scan->rs_ctup.t_data = NULL;
+	ItemPointerSetInvalid(&scan->rs_ctup.t_self);
+	scan->rs_cbuf = InvalidBuffer;
+	scan->rs_cblock = InvalidBlockNumber;
+
+	/* page-at-a-time fields are always invalid when not rs_inited */
+
+	/*
+	 * copy the scan key, if appropriate
+	 */
+	if (key != NULL)
+		memcpy(scan->rs_key, key, scan->rs_nkeys * sizeof(ScanKeyData));
+
+	/*
+	 * Currently, we don't have a stats counter for bitmap heap scans (but the
+	 * underlying bitmap index scans will be counted) or sample scans (we only
+	 * update stats for tuple fetches there)
+	 */
+	if (!scan->rs_bitmapscan && !scan->rs_samplescan)
+		pgstat_count_heap_scan(scan->rs_rd);
+}
+
+
+/* ----------------
+ *		heapgettup - fetch next heap tuple
+ *
+ *		Initialize the scan if not already done; then advance to the next
+ *		tuple as indicated by "dir"; return the next tuple in scan->rs_ctup,
+ *		or set scan->rs_ctup.t_data = NULL if no more tuples.
+ *
+ * dir == NoMovementScanDirection means "re-fetch the tuple indicated
+ * by scan->rs_ctup".
+ *
+ * Note: the reason nkeys/key are passed separately, even though they are
+ * kept in the scan descriptor, is that the caller may not want us to check
+ * the scankeys.
+ *
+ * Note: when we fall off the end of the scan in either direction, we
+ * reset rs_inited.  This means that a further request with the same
+ * scan direction will restart the scan, which is a bit odd, but a
+ * request with the opposite scan direction will start a fresh scan
+ * in the proper direction.  The latter is required behavior for cursors,
+ * while the former case is generally undefined behavior in Postgres
+ * so we don't care too much.
+ * ----------------
+ */
+static void
+heapgettup(HeapScanDesc scan,
+		   ScanDirection dir,
+		   int nkeys,
+		   ScanKey key)
+{
+	HeapTuple	tuple = &(scan->rs_ctup);
+	Snapshot	snapshot = scan->rs_snapshot;
+	bool		backward = ScanDirectionIsBackward(dir);
+	BlockNumber page;
+	bool		finished;
+	Page		dp;
+	int			lines;
+	OffsetNumber lineoff;
+	int			linesleft;
+	ItemId		lpp;
+
+	/*
+	 * calculate next starting lineoff, given scan direction
+	 */
+	if (ScanDirectionIsForward(dir))
+	{
+		if (!scan->rs_inited)
+		{
+			/*
+			 * return null immediately if relation is empty
+			 */
+			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			{
+				Assert(!BufferIsValid(scan->rs_cbuf));
+				tuple->t_data = NULL;
+				return;
+			}
+			if (scan->rs_parallel != NULL)
+			{
+				heap_parallelscan_startblock_init(scan);
+
+				page = heap_parallelscan_nextpage(scan);
+
+				/* Other processes might have already finished the scan. */
+				if (page == InvalidBlockNumber)
+				{
+					Assert(!BufferIsValid(scan->rs_cbuf));
+					tuple->t_data = NULL;
+					return;
+				}
+			}
+			else
+				page = scan->rs_startblock; /* first page */
+			heapgetpage(scan, page);
+			lineoff = FirstOffsetNumber;	/* first offnum */
+			scan->rs_inited = true;
+		}
+		else
+		{
+			/* continue from previously returned page/tuple */
+			page = scan->rs_cblock; /* current page */
+			lineoff =			/* next offnum */
+				OffsetNumberNext(ItemPointerGetOffsetNumber(&(tuple->t_self)));
+		}
+
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		lines = PageGetMaxOffsetNumber(dp);
+		/* page and lineoff now reference the physically next tid */
+
+		linesleft = lines - lineoff + 1;
+	}
+	else if (backward)
+	{
+		/* backward parallel scan not supported */
+		Assert(scan->rs_parallel == NULL);
+
+		if (!scan->rs_inited)
+		{
+			/*
+			 * return null immediately if relation is empty
+			 */
+			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			{
+				Assert(!BufferIsValid(scan->rs_cbuf));
+				tuple->t_data = NULL;
+				return;
+			}
+
+			/*
+			 * Disable reporting to syncscan logic in a backwards scan; it's
+			 * not very likely anyone else is doing the same thing at the same
+			 * time, and much more likely that we'll just bollix things for
+			 * forward scanners.
+			 */
+			scan->rs_syncscan = false;
+			/* start from last page of the scan */
+			if (scan->rs_startblock > 0)
+				page = scan->rs_startblock - 1;
+			else
+				page = scan->rs_nblocks - 1;
+			heapgetpage(scan, page);
+		}
+		else
+		{
+			/* continue from previously returned page/tuple */
+			page = scan->rs_cblock; /* current page */
+		}
+
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		lines = PageGetMaxOffsetNumber(dp);
+
+		if (!scan->rs_inited)
+		{
+			lineoff = lines;	/* final offnum */
+			scan->rs_inited = true;
+		}
+		else
+		{
+			lineoff =			/* previous offnum */
+				OffsetNumberPrev(ItemPointerGetOffsetNumber(&(tuple->t_self)));
+		}
+		/* page and lineoff now reference the physically previous tid */
+
+		linesleft = lineoff;
+	}
+	else
+	{
+		/*
+		 * ``no movement'' scan direction: refetch prior tuple
+		 */
+		if (!scan->rs_inited)
+		{
+			Assert(!BufferIsValid(scan->rs_cbuf));
+			tuple->t_data = NULL;
+			return;
+		}
+
+		page = ItemPointerGetBlockNumber(&(tuple->t_self));
+		if (page != scan->rs_cblock)
+			heapgetpage(scan, page);
+
+		/* Since the tuple was previously fetched, needn't lock page here */
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
+		lpp = PageGetItemId(dp, lineoff);
+		Assert(ItemIdIsNormal(lpp));
+
+		tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+		tuple->t_len = ItemIdGetLength(lpp);
+
+		return;
+	}
+
+	/*
+	 * advance the scan until we find a qualifying tuple or run out of stuff
+	 * to scan
+	 */
+	lpp = PageGetItemId(dp, lineoff);
+	for (;;)
+	{
+		while (linesleft > 0)
+		{
+			if (ItemIdIsNormal(lpp))
+			{
+				bool		valid;
+
+				tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+				tuple->t_len = ItemIdGetLength(lpp);
+				ItemPointerSet(&(tuple->t_self), page, lineoff);
+
+				/*
+				 * if current tuple qualifies, return it.
+				 */
+				valid = HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine,
+													 tuple,
+													 snapshot,
+													 scan->rs_cbuf);
+
+				CheckForSerializableConflictOut(valid, scan->rs_rd, tuple,
+												scan->rs_cbuf, snapshot);
+
+				if (valid && key != NULL)
+					HeapKeyTest(tuple, RelationGetDescr(scan->rs_rd),
+								nkeys, key, valid);
+
+				if (valid)
+				{
+					LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+					return;
+				}
+			}
+
+			/*
+			 * otherwise move to the next item on the page
+			 */
+			--linesleft;
+			if (backward)
+			{
+				--lpp;			/* move back in this page's ItemId array */
+				--lineoff;
+			}
+			else
+			{
+				++lpp;			/* move forward in this page's ItemId array */
+				++lineoff;
+			}
+		}
+
+		/*
+		 * 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);
+
+		/*
+		 * 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);
+			if (page == 0)
+				page = scan->rs_nblocks;
+			page--;
+		}
+		else if (scan->rs_parallel != NULL)
+		{
+			page = heap_parallelscan_nextpage(scan);
+			finished = (page == InvalidBlockNumber);
+		}
+		else
+		{
+			page++;
+			if (page >= scan->rs_nblocks)
+				page = 0;
+			finished = (page == scan->rs_startblock) ||
+				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+
+			/*
+			 * Report our new scan position for synchronization purposes. We
+			 * don't do that when moving backwards, however. That would just
+			 * mess up any other forward-moving scanners.
+			 *
+			 * Note: we do this before checking for end of scan so that the
+			 * final state of the position hint is back at the start of the
+			 * rel.  That's not strictly necessary, but otherwise when you run
+			 * the same query multiple times the starting position would shift
+			 * 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);
+		}
+
+		/*
+		 * return NULL if we've exhausted all the pages
+		 */
+		if (finished)
+		{
+			if (BufferIsValid(scan->rs_cbuf))
+				ReleaseBuffer(scan->rs_cbuf);
+			scan->rs_cbuf = InvalidBuffer;
+			scan->rs_cblock = InvalidBlockNumber;
+			tuple->t_data = NULL;
+			scan->rs_inited = false;
+			return;
+		}
+
+		heapgetpage(scan, page);
+
+		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+
+		dp = BufferGetPage(scan->rs_cbuf);
+		TestForOldSnapshot(snapshot, scan->rs_rd, dp);
+		lines = PageGetMaxOffsetNumber((Page) dp);
+		linesleft = lines;
+		if (backward)
+		{
+			lineoff = lines;
+			lpp = PageGetItemId(dp, lines);
+		}
+		else
+		{
+			lineoff = FirstOffsetNumber;
+			lpp = PageGetItemId(dp, FirstOffsetNumber);
+		}
+	}
+}
+
+/* ----------------
+ *		heapgettup_pagemode - fetch next heap tuple in page-at-a-time mode
+ *
+ *		Same API as heapgettup, but used in page-at-a-time mode
+ *
+ * The internal logic is much the same as heapgettup's too, but there are some
+ * differences: we do not take the buffer content lock (that only needs to
+ * happen inside heapgetpage), and we iterate through just the tuples listed
+ * in rs_vistuples[] rather than all tuples on the page.  Notice that
+ * lineindex is 0-based, where the corresponding loop variable lineoff in
+ * heapgettup is 1-based.
+ * ----------------
+ */
+static void
+heapgettup_pagemode(HeapScanDesc scan,
+					ScanDirection dir,
+					int nkeys,
+					ScanKey key)
+{
+	HeapTuple	tuple = &(scan->rs_ctup);
+	bool		backward = ScanDirectionIsBackward(dir);
+	BlockNumber page;
+	bool		finished;
+	Page		dp;
+	int			lines;
+	int			lineindex;
+	OffsetNumber lineoff;
+	int			linesleft;
+	ItemId		lpp;
+
+	/*
+	 * calculate next starting lineindex, given scan direction
+	 */
+	if (ScanDirectionIsForward(dir))
+	{
+		if (!scan->rs_inited)
+		{
+			/*
+			 * return null immediately if relation is empty
+			 */
+			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			{
+				Assert(!BufferIsValid(scan->rs_cbuf));
+				tuple->t_data = NULL;
+				return;
+			}
+			if (scan->rs_parallel != NULL)
+			{
+				heap_parallelscan_startblock_init(scan);
+
+				page = heap_parallelscan_nextpage(scan);
+
+				/* Other processes might have already finished the scan. */
+				if (page == InvalidBlockNumber)
+				{
+					Assert(!BufferIsValid(scan->rs_cbuf));
+					tuple->t_data = NULL;
+					return;
+				}
+			}
+			else
+				page = scan->rs_startblock; /* first page */
+			heapgetpage(scan, page);
+			lineindex = 0;
+			scan->rs_inited = true;
+		}
+		else
+		{
+			/* continue from previously returned page/tuple */
+			page = scan->rs_cblock; /* current page */
+			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;
+		/* page and lineindex now reference the next visible tid */
+
+		linesleft = lines - lineindex;
+	}
+	else if (backward)
+	{
+		/* backward parallel scan not supported */
+		Assert(scan->rs_parallel == NULL);
+
+		if (!scan->rs_inited)
+		{
+			/*
+			 * return null immediately if relation is empty
+			 */
+			if (scan->rs_nblocks == 0 || scan->rs_numblocks == 0)
+			{
+				Assert(!BufferIsValid(scan->rs_cbuf));
+				tuple->t_data = NULL;
+				return;
+			}
+
+			/*
+			 * Disable reporting to syncscan logic in a backwards scan; it's
+			 * not very likely anyone else is doing the same thing at the same
+			 * time, and much more likely that we'll just bollix things for
+			 * forward scanners.
+			 */
+			scan->rs_syncscan = false;
+			/* start from last page of the scan */
+			if (scan->rs_startblock > 0)
+				page = scan->rs_startblock - 1;
+			else
+				page = scan->rs_nblocks - 1;
+			heapgetpage(scan, page);
+		}
+		else
+		{
+			/* continue from previously returned page/tuple */
+			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;
+
+		if (!scan->rs_inited)
+		{
+			lineindex = lines - 1;
+			scan->rs_inited = true;
+		}
+		else
+		{
+			lineindex = scan->rs_cindex - 1;
+		}
+		/* page and lineindex now reference the previous visible tid */
+
+		linesleft = lineindex + 1;
+	}
+	else
+	{
+		/*
+		 * ``no movement'' scan direction: refetch prior tuple
+		 */
+		if (!scan->rs_inited)
+		{
+			Assert(!BufferIsValid(scan->rs_cbuf));
+			tuple->t_data = NULL;
+			return;
+		}
+
+		page = ItemPointerGetBlockNumber(&(tuple->t_self));
+		if (page != scan->rs_cblock)
+			heapgetpage(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);
+		lineoff = ItemPointerGetOffsetNumber(&(tuple->t_self));
+		lpp = PageGetItemId(dp, lineoff);
+		Assert(ItemIdIsNormal(lpp));
+
+		tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+		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]);
+
+		return;
+	}
+
+	/*
+	 * advance the scan until we find a qualifying tuple or run out of stuff
+	 * to scan
+	 */
+	for (;;)
+	{
+		while (linesleft > 0)
+		{
+			lineoff = scan->rs_vistuples[lineindex];
+			lpp = PageGetItemId(dp, lineoff);
+			Assert(ItemIdIsNormal(lpp));
+
+			tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp);
+			tuple->t_len = ItemIdGetLength(lpp);
+			ItemPointerSet(&(tuple->t_self), page, lineoff);
+
+			/*
+			 * if current tuple qualifies, return it.
+			 */
+			if (HeapTupleSatisfiesVisibility(scan->rs_rd->rd_stamroutine, tuple, scan->rs_snapshot, scan->rs_cbuf))
+			{
+				/*
+				 * if current tuple qualifies, return it.
+				 */
+				if (key != NULL)
+				{
+					bool		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;
+				}
+			}
+
+			/*
+			 * otherwise move to the next item on the page
+			 */
+			--linesleft;
+			if (backward)
+				--lineindex;
+			else
+				++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.
+		 */
+		if (backward)
+		{
+			finished = (page == scan->rs_startblock) ||
+				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+			if (page == 0)
+				page = scan->rs_nblocks;
+			page--;
+		}
+		else if (scan->rs_parallel != NULL)
+		{
+			page = heap_parallelscan_nextpage(scan);
+			finished = (page == InvalidBlockNumber);
+		}
+		else
+		{
+			page++;
+			if (page >= scan->rs_nblocks)
+				page = 0;
+			finished = (page == scan->rs_startblock) ||
+				(scan->rs_numblocks != InvalidBlockNumber ? --scan->rs_numblocks == 0 : false);
+
+			/*
+			 * Report our new scan position for synchronization purposes. We
+			 * don't do that when moving backwards, however. That would just
+			 * mess up any other forward-moving scanners.
+			 *
+			 * Note: we do this before checking for end of scan so that the
+			 * final state of the position hint is back at the start of the
+			 * rel.  That's not strictly necessary, but otherwise when you run
+			 * the same query multiple times the starting position would shift
+			 * 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);
+		}
+
+		/*
+		 * return NULL if we've exhausted all the pages
+		 */
+		if (finished)
+		{
+			if (BufferIsValid(scan->rs_cbuf))
+				ReleaseBuffer(scan->rs_cbuf);
+			scan->rs_cbuf = InvalidBuffer;
+			scan->rs_cblock = InvalidBlockNumber;
+			tuple->t_data = NULL;
+			scan->rs_inited = false;
+			return;
+		}
+
+		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;
+		linesleft = lines;
+		if (backward)
+			lineindex = lines - 1;
+		else
+			lineindex = 0;
+	}
+}
+
+
+static HeapScanDesc
+heapam_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)
+{
+	HeapScanDesc scan;
+
+	/*
+	 * increment relation ref count while scanning relation
+	 *
+	 * This is just to make really sure the relcache entry won't go away while
+	 * the scan has a pointer to it.  Caller should be holding the rel open
+	 * anyway, so this is redundant in all normal scenarios...
+	 */
+	RelationIncrementReferenceCount(relation);
+
+	/*
+	 * allocate and initialize scan descriptor
+	 */
+	scan = (HeapScanDesc) palloc(sizeof(HeapScanDescData));
+
+	scan->rs_rd = relation;
+	scan->rs_snapshot = snapshot;
+	scan->rs_nkeys = nkeys;
+	scan->rs_bitmapscan = is_bitmapscan;
+	scan->rs_samplescan = is_samplescan;
+	scan->rs_strategy = NULL;	/* set in initscan */
+	scan->rs_allow_strat = allow_strat;
+	scan->rs_allow_sync = allow_sync;
+	scan->rs_temp_snap = temp_snap;
+	scan->rs_parallel = parallel_scan;
+
+	/*
+	 * we can use page-at-a-time mode if it's an MVCC-safe snapshot
+	 */
+	scan->rs_pageatatime = allow_pagemode && IsMVCCSnapshot(snapshot);
+
+	/*
+	 * For a seqscan in a serializable transaction, acquire a predicate lock
+	 * on the entire relation. This is required not only to lock all the
+	 * matching tuples, but also to conflict with new insertions into the
+	 * table. In an indexscan, we take page locks on the index pages covering
+	 * the range specified in the scan qual, but in a heap scan there is
+	 * nothing more fine-grained to lock. A bitmap scan is a different story,
+	 * there we have already scanned the index and locked the index pages
+	 * covering the predicate. But in that case we still have to lock any
+	 * matching heap tuples.
+	 */
+	if (!is_bitmapscan)
+		PredicateLockRelation(relation, snapshot);
+
+	/* we only need to set this up once */
+	scan->rs_ctup.t_tableOid = RelationGetRelid(relation);
+
+	/*
+	 * we do this here instead of in initscan() because heap_rescan also calls
+	 * initscan() and we don't want to allocate memory again
+	 */
+	if (nkeys > 0)
+		scan->rs_key = (ScanKey) palloc(sizeof(ScanKeyData) * nkeys);
+	else
+		scan->rs_key = NULL;
+
+	initscan(scan, key, false);
+
+	return scan;
+}
+
+/* ----------------
+ *		heapam_rescan		- restart a relation scan
+ * ----------------
+ */
+static void
+heapam_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
+	 */
+	if (BufferIsValid(scan->rs_cbuf))
+		ReleaseBuffer(scan->rs_cbuf);
+
+	/*
+	 * reinitialize scan descriptor
+	 */
+	initscan(scan, key, true);
+
+	/*
+	 * 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);
+	}
+}
+
+/* ----------------
+ *		heapam_endscan	- end relation scan
+ *
+ *		See how to integrate with index scans.
+ *		Check handling if reldesc caching.
+ * ----------------
+ */
+static void
+heapam_endscan(HeapScanDesc scan)
+{
+	/* Note: no locking manipulations needed */
+
+	/*
+	 * unpin scan buffers
+	 */
+	if (BufferIsValid(scan->rs_cbuf))
+		ReleaseBuffer(scan->rs_cbuf);
+
+	/*
+	 * decrement relation reference count and free scan descriptor storage
+	 */
+	RelationDecrementReferenceCount(scan->rs_rd);
+
+	if (scan->rs_key)
+		pfree(scan->rs_key);
+
+	if (scan->rs_strategy != NULL)
+		FreeAccessStrategy(scan->rs_strategy);
+
+	if (scan->rs_temp_snap)
+		UnregisterSnapshot(scan->rs_snapshot);
+
+	pfree(scan);
+}
+
+/* ----------------
+ *		heapam_scan_update_snapshot
+ *
+ *		Update snapshot info in heap scan descriptor.
+ * ----------------
+ */
+static void
+heapam_scan_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
+{
+	Assert(IsMVCCSnapshot(snapshot));
+
+	RegisterSnapshot(snapshot);
+	scan->rs_snapshot = snapshot;
+	scan->rs_temp_snap = true;
+}
+
+/* ----------------
+ *		heapam_getnext	- retrieve next tuple in scan
+ *
+ *		Fix to work with index relations.
+ *		We don't return the buffer anymore, but you can get it from the
+ *		returned HeapTuple.
+ * ----------------
+ */
+
+#ifdef HEAPAMDEBUGALL
+#define HEAPAMDEBUG_1 \
+	elog(DEBUG2, "heapam_getnext([%s,nkeys=%d],dir=%d) called", \
+		 RelationGetRelationName(scan->rs_rd), scan->rs_nkeys, (int) direction)
+#define HEAPAMDEBUG_2 \
+	elog(DEBUG2, "heapam_getnext returning EOS")
+#define HEAPAMDEBUG_3 \
+	elog(DEBUG2, "heapam_getnext returning tuple")
+#else
+#define HEAPAMDEBUG_1
+#define HEAPAMDEBUG_2
+#define HEAPAMDEBUG_3
+#endif							/* !defined(HEAPDEBUGALL) */
+
+
+static StorageTuple
+heapam_getnext(HeapScanDesc sscan, ScanDirection direction)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	/* Note: no locking manipulations needed */
+
+	HEAPAMDEBUG_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)
+	{
+		HEAPAMDEBUG_2;			/* heap_getnext returning EOS */
+		return NULL;
+	}
+
+	/*
+	 * if we get here it means we have a new current scan tuple, so point to
+	 * the proper return buffer and return the tuple.
+	 */
+	HEAPAMDEBUG_3;				/* heap_getnext returning tuple */
+
+	pgstat_count_heap_getnext(scan->rs_rd);
+
+	return &(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
+
+static TupleTableSlot *
+heapam_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
@@ -1852,9 +2957,213 @@ heap_fetch(Relation relation,
 	return false;
 }
 
+/*
+ *	heapam_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.
+ */
+static bool
+heapam_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
+						 Snapshot snapshot, HeapTuple heapTuple,
+						 bool *all_dead, bool first_call)
+{
+	Page		dp = (Page) BufferGetPage(buffer);
+	TransactionId prev_xmax = InvalidTransactionId;
+	OffsetNumber offnum;
+	bool		at_chain_start;
+	bool		valid;
+	bool		skip;
+
+	/* If this is not the first call, previous call returned a (live!) tuple */
+	if (all_dead)
+		*all_dead = first_call;
+
+	Assert(TransactionIdIsValid(RecentGlobalXmin));
+
+	Assert(ItemPointerGetBlockNumber(tid) == BufferGetBlockNumber(buffer));
+	offnum = ItemPointerGetOffsetNumber(tid);
+	at_chain_start = first_call;
+	skip = !first_call;
+
+	heapTuple->t_self = *tid;
+
+	/* Scan through possible multiple members of HOT-chain */
+	for (;;)
+	{
+		ItemId		lp;
+
+		/* check for bogus TID */
+		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(dp))
+			break;
+
+		lp = PageGetItemId(dp, offnum);
+
+		/* check for unused, dead, or redirected items */
+		if (!ItemIdIsNormal(lp))
+		{
+			/* We should only see a redirect at start of chain */
+			if (ItemIdIsRedirected(lp) && at_chain_start)
+			{
+				/* Follow the redirect */
+				offnum = ItemIdGetRedirect(lp);
+				at_chain_start = false;
+				continue;
+			}
+			/* else must be end of chain */
+			break;
+		}
+
+		heapTuple->t_data = (HeapTupleHeader) PageGetItem(dp, lp);
+		heapTuple->t_len = ItemIdGetLength(lp);
+		heapTuple->t_tableOid = RelationGetRelid(relation);
+		ItemPointerSetOffsetNumber(&heapTuple->t_self, offnum);
+
+		/*
+		 * Shouldn't see a HEAP_ONLY tuple at chain start.
+		 */
+		if (at_chain_start && HeapTupleIsHeapOnly(heapTuple))
+			break;
+
+		/*
+		 * The xmin should match the previous xmax value, else chain is
+		 * broken.
+		 */
+		if (TransactionIdIsValid(prev_xmax) &&
+			!TransactionIdEquals(prev_xmax,
+								 HeapTupleHeaderGetXmin(heapTuple->t_data)))
+			break;
+
+		/*
+		 * When first_call is true (and thus, skip is initially false) we'll
+		 * return the first tuple we find.  But on later passes, heapTuple
+		 * will initially be pointing to the tuple we returned last time.
+		 * Returning it again would be incorrect (and would loop forever), so
+		 * we skip it and return the next match we find.
+		 */
+		if (!skip)
+		{
+			/*
+			 * For the benefit of logical decoding, have t_self point at the
+			 * element of the HOT chain we're currently investigating instead
+			 * of the root tuple of the HOT chain. This is important because
+			 * the *Satisfies routine for historical mvcc snapshots needs the
+			 * correct tid to decide about the visibility in some cases.
+			 */
+			ItemPointerSet(&(heapTuple->t_self), BufferGetBlockNumber(buffer), offnum);
+
+			/* If it's visible per the snapshot, we must return it */
+			valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, heapTuple, snapshot, buffer);
+			CheckForSerializableConflictOut(valid, relation, heapTuple,
+											buffer, snapshot);
+			/* reset to original, non-redirected, tid */
+			heapTuple->t_self = *tid;
+
+			if (valid)
+			{
+				ItemPointerSetOffsetNumber(tid, offnum);
+				PredicateLockTuple(relation, heapTuple, snapshot);
+				if (all_dead)
+					*all_dead = false;
+				return true;
+			}
+		}
+		skip = false;
+
+		/*
+		 * If we can't see it, maybe no one else can either.  At caller
+		 * request, check whether all chain members are dead to all
+		 * transactions.
+		 *
+		 * Note: if you change the criterion here for what is "dead", fix the
+		 * planner's get_actual_variable_range() function to match.
+		 */
+		if (all_dead && *all_dead &&
+			!HeapTupleIsSurelyDead(heapTuple, RecentGlobalXmin))
+			*all_dead = false;
+
+		/*
+		 * Check to see if HOT chain continues past this tuple; if so fetch
+		 * the next offnum and loop around.
+		 */
+		if (HeapTupleIsHotUpdated(heapTuple))
+		{
+			Assert(ItemPointerGetBlockNumber(&heapTuple->t_data->t_ctid) ==
+				   ItemPointerGetBlockNumber(tid));
+			offnum = ItemPointerGetOffsetNumber(&heapTuple->t_data->t_ctid);
+			at_chain_start = false;
+			prev_xmax = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
+		}
+		else
+			break;				/* end of chain */
+	}
+
+	return false;
+}
+
+/*
+ * heapam_setscanlimits - restrict range of a heapscan
+ *
+ * startBlk is the page to start at
+ * numBlks is number of pages to scan (InvalidBlockNumber means "all")
+ */
+static void
+heapam_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
+{
+	HeapScanDesc scan = (HeapScanDesc) sscan;
+
+	Assert(!scan->rs_inited);	/* else too late to change */
+	Assert(!scan->rs_syncscan); /* else rs_startblock is significant */
+
+	/* Check startBlk is valid (but allow case of zero blocks...) */
+	Assert(startBlk == 0 || startBlk < scan->rs_nblocks);
+
+	scan->rs_startblock = startBlk;
+	scan->rs_numblocks = numBlks;
+}
+
+
+/*
+ * heapam_freeze_tuple
+ *		Freeze tuple in place, without WAL logging.
+ *
+ * Useful for callers like CLUSTER that perform their own WAL logging.
+ */
+static bool
+heapam_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
+					TransactionId cutoff_multi)
+{
+	xl_heap_freeze_tuple frz;
+	bool		do_freeze;
+	bool		tuple_totally_frozen;
 
+	do_freeze = heap_prepare_freeze_tuple(tuple, cutoff_xid, cutoff_multi,
+										  &frz, &tuple_totally_frozen);
 
+	/*
+	 * Note that because this is not a WAL-logged operation, we don't need to
+	 * fill in the offset in the freeze record.
+	 */
 
+	if (do_freeze)
+		heap_execute_freeze_tuple(tuple, &frz);
+	return do_freeze;
+}
 
 /* ----------------------------------------------------------------
  *				storage AM support routines for heapam
@@ -3875,6 +5184,16 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 {
 	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
 
+	amroutine->scan_begin = heapam_beginscan;
+	amroutine->scansetlimits = heapam_setscanlimits;
+	amroutine->scan_getnext = heapam_getnext;
+	amroutine->scan_getnextslot = heapam_getnextslot;
+	amroutine->scan_end = heapam_endscan;
+	amroutine->scan_rescan = heapam_rescan;
+	amroutine->scan_update_snapshot = heapam_scan_update_snapshot;
+	amroutine->tuple_freeze = heapam_freeze_tuple;
+	amroutine->hot_search_buffer = heapam_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/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 84ac4bfade..4691e44ecc 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -409,8 +409,8 @@ rewrite_heap_tuple(RewriteState state,
 	 * While we have our hands on the tuple, we may as well freeze any
 	 * eligible xmin or xmax, so that future VACUUM effort can be saved.
 	 */
-	heap_freeze_tuple(new_tuple->t_data, state->rs_freeze_xid,
-					  state->rs_cutoff_multi);
+	storage_freeze_tuple(state->rs_new_rel, new_tuple->t_data, state->rs_freeze_xid,
+						 state->rs_cutoff_multi);
 
 	/*
 	 * Invalid ctid means that ctid should point to the tuple itself. We'll
diff --git a/src/backend/access/heap/storageam.c b/src/backend/access/heap/storageam.c
index 870490d00f..0b83ecf1bc 100644
--- a/src/backend/access/heap/storageam.c
+++ b/src/backend/access/heap/storageam.c
@@ -48,6 +48,174 @@
 #include "utils/tqual.h"
 
 
+/* ----------------
+ *		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);
+}
+
 /*
  *	storage_fetch		- retrieve tuple with given tid
  *
@@ -99,6 +267,73 @@ storage_fetch(Relation relation,
 												 userbuf, keep_buf, stats_relation);
 }
 
+/*
+ *	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;
+}
+
+/*
+ * heap_freeze_tuple
+ *		Freeze tuple in place, without WAL logging.
+ *
+ * Useful for callers like CLUSTER that perform their own WAL logging.
+ */
+bool
+storage_freeze_tuple(Relation rel, HeapTupleHeader tuple, TransactionId cutoff_xid,
+					 TransactionId cutoff_multi)
+{
+	return rel->rd_stamroutine->tuple_freeze(tuple, cutoff_xid, cutoff_multi);
+}
+
 
 /*
  *	storage_lock_tuple - lock a tuple in shared or exclusive mode
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 bef4255369..407e381a91 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 5cbaba1b7d..309223ec4b 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/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 ccde66a7dd..d2a8a06097 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"
@@ -797,14 +798,14 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 								ObjectIdGetDatum(namespaceId));
 
 					rel = heap_open(ProcedureRelationId, AccessShareLock);
-					scan = heap_beginscan_catalog(rel, 1, key);
+					scan = storage_beginscan_catalog(rel, 1, 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;
@@ -842,14 +843,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 17be9a4042..a201b00cae 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"
@@ -1904,10 +1905,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
 	{
@@ -2279,16 +2280,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 */
@@ -2301,7 +2302,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;
 
@@ -2613,7 +2614,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		}
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 
 	/* we can now forget our snapshot, if set */
 	if (IsBootstrapProcessingMode() || indexInfo->ii_Concurrent)
@@ -2684,14 +2685,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();
 
@@ -2727,7 +2728,7 @@ IndexCheckExclusion(Relation heapRelation,
 								   estate, true);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 
 	ExecDropSingleTupleTableSlot(slot);
@@ -3004,17 +3005,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;
@@ -3171,7 +3172,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 5daa8a1c19..a5a5268d12 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -18,6 +18,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"
@@ -709,11 +710,11 @@ extern PartitionBoundInfo
 partition_bounds_copy(PartitionBoundInfo src,
 					  PartitionKey key)
 {
-	PartitionBoundInfo	dest;
-	int		i;
-	int		ndatums;
-	int		partnatts;
-	int		num_indexes;
+	PartitionBoundInfo dest;
+	int			i;
+	int			ndatums;
+	int			partnatts;
+	int			num_indexes;
 
 	dest = (PartitionBoundInfo) palloc(sizeof(PartitionBoundInfoData));
 
@@ -732,11 +733,11 @@ partition_bounds_copy(PartitionBoundInfo src,
 	if (src->kind != NULL)
 	{
 		dest->kind = (PartitionRangeDatumKind **) palloc(ndatums *
-												sizeof(PartitionRangeDatumKind *));
+														 sizeof(PartitionRangeDatumKind *));
 		for (i = 0; i < ndatums; i++)
 		{
 			dest->kind[i] = (PartitionRangeDatumKind *) palloc(partnatts *
-												sizeof(PartitionRangeDatumKind));
+															   sizeof(PartitionRangeDatumKind));
 
 			memcpy(dest->kind[i], src->kind[i],
 				   sizeof(PartitionRangeDatumKind) * key->partnatts);
@@ -747,7 +748,8 @@ partition_bounds_copy(PartitionBoundInfo src,
 
 	for (i = 0; i < ndatums; i++)
 	{
-		int		j;
+		int			j;
+
 		dest->datums[i] = (Datum *) palloc(sizeof(Datum) * partnatts);
 
 		for (j = 0; j < partnatts; j++)
@@ -1074,7 +1076,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);
 
 		/*
@@ -1083,7 +1085,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;
@@ -1099,7 +1101,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 ad28b3af3e..cad0eed1a4 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2036,10 +2036,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();
 
@@ -2051,7 +2051,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 e138539035..39850b1b37 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 3f615b6260..5209083a72 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"
@@ -1947,8 +1948,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);
@@ -1988,7 +1989,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 458c897741..3bab05079e 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;
@@ -8257,7 +8257,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
@@ -8265,7 +8265,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);
 
@@ -8280,7 +8280,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	}
 
 	MemoryContextSwitchTo(oldcxt);
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 	ExecDropSingleTupleTableSlot(slot);
 	FreeExecutorState(estate);
@@ -8335,9 +8335,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;
@@ -8366,7 +8366,7 @@ validateForeignKeyConstraint(char *conname,
 		RI_FKey_check_ins(&fcinfo);
 	}
 
-	heap_endscan(scan);
+	storage_endscan(scan);
 	UnregisterSnapshot(snapshot);
 }
 
@@ -10871,8 +10871,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;
@@ -10931,7 +10931,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 8559c3b6b3..cdfa8ffb3f 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/trigger.c b/src/backend/commands/trigger.c
index 680a65cad5..a0f3db5bdb 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -15,8 +15,9 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
-#include "access/sysattr.h"
 #include "access/htup_details.h"
+#include "access/storageam.h"
+#include "access/sysattr.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 7df942b18b..b95e3fc5ab 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 cbd6e9b161..bafc15eac6 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"
@@ -68,7 +69,7 @@ static BufferAccessStrategy vac_strategy;
 
 
 /* non-export function prototypes */
-static List *expand_vacuum_rel(VacuumRelation *vrel);
+static List *expand_vacuum_rel(VacuumRelation * vrel);
 static List *get_all_vacuum_rels(void);
 static void vac_truncate_clog(TransactionId frozenXID,
 				  MultiXactId minMulti,
@@ -423,7 +424,7 @@ vacuum(int options, List *relations, VacuumParams *params,
  * are made in vac_context.
  */
 static List *
-expand_vacuum_rel(VacuumRelation *vrel)
+expand_vacuum_rel(VacuumRelation * vrel)
 {
 	List	   *vacrels = NIL;
 	MemoryContext oldcontext;
@@ -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 24b5ff7298..ed2c0ab80c 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 an index 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 d618b3d903..742f73176c 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -683,7 +683,7 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 Datum
 ExecFetchSlotTupleDatum(TupleTableSlot *slot)
 {
-	HeapTuple	tup;
+	StorageTuple tup;
 	TupleDesc	tupdesc;
 
 	/* Fetch slot's contents in regular-physical-tuple form */
@@ -767,7 +767,7 @@ ExecHeapifySlot(TupleTableSlot *slot)
 TupleTableSlot *
 ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
 {
-	HeapTuple	newTuple;
+	StorageTuple newTuple;
 	MemoryContext oldContext;
 
 	/*
@@ -1087,7 +1087,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;
@@ -1095,7 +1095,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 98eb777421..8ce51e5bd5 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)
 {
@@ -597,7 +597,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 2b118359b5..d8c4119972 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -3119,7 +3119,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,
@@ -3244,7 +3244,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 3e22c6baf6..a661323fda 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.
@@ -1111,5 +1112,5 @@ ExecBitmapHeapInitializeWorker(BitmapHeapScanState *node, shm_toc *toc)
 	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 02f6c816aa..d3de6c5824 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 639f4f5af8..edb8046180 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -45,7 +45,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);
 
 
@@ -251,7 +251,7 @@ gather_getnext(GatherState *gatherstate)
 	TupleTableSlot *outerTupleSlot;
 	TupleTableSlot *fslot = gatherstate->funnel_slot;
 	MemoryContext tupleContext = gatherstate->ps.ps_ExprContext->ecxt_per_tuple_memory;
-	HeapTuple	tup;
+	StorageTuple tup;
 
 	while (gatherstate->nreaders > 0 || gatherstate->need_to_scan_locally)
 	{
@@ -294,7 +294,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;
@@ -302,7 +302,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 5625b12521..3381813455 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -44,7 +44,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 */
@@ -53,8 +53,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);
@@ -399,7 +399,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);
@@ -617,7 +617,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
@@ -689,12 +689,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;
 	MemoryContext oldContext;
 	MemoryContext tupleContext;
 
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index 9368ca04f8..07346435cd 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 262008240d..2726933d9d 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..64f3dd8be0 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 76bec780a8..0521df1fa9 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);
 }
 
 /* ----------------------------------------------------------------
@@ -354,5 +335,5 @@ ExecSeqScanInitializeWorker(SeqScanState *node, shm_toc *toc)
 
 	pscan = shm_toc_lookup(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 40292b86c1..557cb2175c 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 9a47276274..3d61d96775 100644
--- a/src/backend/executor/tqueue.c
+++ b/src/backend/executor/tqueue.c
@@ -166,7 +166,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 3a0b49c7c4..36808c4067 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/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 2b2b993e2c..88086f3d48 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -4268,8 +4268,8 @@ BackendInitialize(Port *port)
 	 *
 	 * postgres: walsender <user> <host> <activity>
 	 *
-	 * To achieve that, we pass "walsender" as username and username
-	 * as dbname to init_ps_display(). XXX: should add a new variant of
+	 * To achieve that, we pass "walsender" as username and username as dbname
+	 * to init_ps_display(). XXX: should add a new variant of
 	 * init_ps_display() to avoid abusing the parameters like this.
 	 */
 	if (am_walsender)
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 071b3a9ec9..4924daca76 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(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 cdd45ef313..4cddd73355 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -106,40 +106,16 @@ 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);
-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_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 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 Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 			int options, BulkInsertState bistate);
-extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
-					   Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
-					   bool *all_dead, bool first_call);
-extern bool heap_hot_search(ItemPointer tid, Relation relation,
-				Snapshot snapshot, bool *all_dead);
+
 extern void setLastTid(const ItemPointer tid);
 
 extern BulkInsertState GetBulkInsertState(void);
@@ -147,8 +123,6 @@ extern void FreeBulkInsertState(BulkInsertState);
 extern void ReleaseBulkInsertStatePin(BulkInsertState bistate);
 
 extern void heap_inplace_update(Relation relation, HeapTuple tuple);
-extern bool heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
-				  TransactionId cutoff_multi);
 extern bool heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
 						MultiXactId cutoff_multi, Buffer buf);
 extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
@@ -158,8 +132,6 @@ extern void simple_heap_delete(Relation relation, ItemPointer tid);
 extern void simple_heap_update(Relation relation, ItemPointer otid,
 				   HeapTuple tup);
 
-extern void heap_update_snapshot(HeapScanDesc scan, Snapshot snapshot);
-
 /* 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/heapam_common.h b/src/include/access/heapam_common.h
index 14aad60546..597f2bd2f4 100644
--- a/src/include/access/heapam_common.h
+++ b/src/include/access/heapam_common.h
@@ -107,6 +107,9 @@ static const int MultiXactStatusLock[MaxMultiXactStatus + 1] =
 /* Get the LOCKMODE for a given MultiXactStatus */
 #define LOCKMODE_from_mxstatus(status) \
 			(tupleLockExtraInfo[TUPLOCK_from_mxstatus((status))].hwlock)
+
+extern bool synchronize_seqscans;
+
 extern HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
 					TransactionId xid, CommandId cid, int options);
 
@@ -136,6 +139,11 @@ extern void MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 in
 extern MultiXactStatus get_mxact_status_for_lock(LockTupleMode mode, bool is_update);
 
 extern void heap_inplace_update(Relation relation, HeapTuple tuple);
+
+extern bool heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
+						MultiXactId cutoff_multi, Buffer buf);
+extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
+
 extern bool heap_acquire_tuplock(Relation relation, ItemPointer tid,
 					 LockTupleMode mode, LockWaitPolicy wait_policy,
 					 bool *have_tuple_lock);
diff --git a/src/include/access/storageam.h b/src/include/access/storageam.h
index 447935ba10..ec5dc89a26 100644
--- a/src/include/access/storageam.h
+++ b/src/include/access/storageam.h
@@ -20,6 +20,7 @@
 
 /* A physical tuple coming from a storage AM scan */
 typedef void *StorageTuple;
+typedef void *StorageScanDesc;
 
 typedef union tuple_data
 {
@@ -42,6 +43,34 @@ typedef enum tuple_data_flags
 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,
@@ -50,6 +79,16 @@ 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 bool storage_freeze_tuple(Relation rel, HeapTupleHeader tuple, TransactionId cutoff_xid,
+					 TransactionId cutoff_multi);
+
 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/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.14.2.windows.1

0008-Remove-HeapScanDesc-usage-outside-heap.patchapplication/octet-stream; name=0008-Remove-HeapScanDesc-usage-outside-heap.patchDownload
From 5028a8b93e74cc28b5e545ff60e33b1b0da5d0f7 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Wed, 1 Nov 2017 14:11:58 +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           |  37 +--
 src/backend/access/heap/heapam_storage.c   | 436 ++++++++++++++++-------------
 src/backend/access/heap/storageam.c        |  51 +++-
 src/backend/access/index/genam.c           |   4 +-
 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       |  10 +-
 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                |   4 +-
 src/include/access/relscan.h               |  47 ++--
 src/include/access/storageam.h             |  44 +--
 src/include/access/storageamapi.h          |  42 +--
 src/include/nodes/execnodes.h              |   4 +-
 39 files changed, 519 insertions(+), 406 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 6c1224db42..b8c8bf5594 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -107,8 +107,9 @@ static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_mo
  * 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;
@@ -118,13 +119,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;
 	}
 
 	/*
@@ -135,20 +136,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
@@ -158,7 +159,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;
 
@@ -193,7 +194,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);
@@ -201,20 +202,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;
 }
 
 #if defined(DISABLE_COMPLEX_MACRO)
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 4fa5ebc4e3..2a3369791c 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -1745,7 +1745,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;
 		}
 	}
@@ -1787,10 +1787,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.
@@ -1799,12 +1799,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;
@@ -1833,9 +1833,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
@@ -1849,8 +1849,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;
@@ -1861,20 +1861,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)
 	{
@@ -1883,25 +1883,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 */
 
@@ -1909,7 +1909,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
@@ -1917,7 +1917,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);
 }
 
 
@@ -1951,7 +1951,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;
@@ -1966,14 +1966,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;
 			}
@@ -1986,29 +1986,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 */
 
@@ -2019,14 +2019,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;
 			}
@@ -2037,30 +2037,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
 		{
@@ -2076,20 +2076,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));
@@ -2120,21 +2120,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;
 				}
 			}
@@ -2159,17 +2159,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)
@@ -2180,10 +2180,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
@@ -2197,8 +2197,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);
 		}
 
 		/*
@@ -2206,21 +2206,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)
@@ -2271,14 +2271,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;
 			}
@@ -2291,28 +2291,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;
@@ -2322,14 +2322,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;
 			}
@@ -2340,33 +2340,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 */
 
@@ -2377,20 +2377,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));
@@ -2399,8 +2399,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;
 	}
@@ -2413,7 +2413,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));
 
@@ -2424,7 +2424,9 @@ 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.
@@ -2433,19 +2435,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;
 				}
 			}
@@ -2464,7 +2466,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
@@ -2472,10 +2474,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)
@@ -2486,10 +2488,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
@@ -2503,8 +2505,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);
 		}
 
 		/*
@@ -2512,21 +2514,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;
@@ -2536,7 +2538,7 @@ heapgettup_pagemode(HeapScanDesc scan,
 }
 
 
-static HeapScanDesc
+static StorageScanDesc
 heapam_beginscan(Relation relation, Snapshot snapshot,
 				 int nkeys, ScanKey key,
 				 ParallelHeapScanDesc parallel_scan,
@@ -2563,12 +2565,12 @@ heapam_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;
@@ -2577,7 +2579,7 @@ heapam_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
@@ -2601,13 +2603,29 @@ heapam_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;
+}
+
+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;
 }
 
 /* ----------------
@@ -2615,21 +2633,23 @@ heapam_beginscan(Relation relation, Snapshot snapshot,
  * ----------------
  */
 static void
-heapam_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
+heapam_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
@@ -2660,29 +2680,31 @@ heapam_rescan(HeapScanDesc scan, ScanKey key, bool set_params,
  * ----------------
  */
 static void
-heapam_endscan(HeapScanDesc scan)
+heapam_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);
 }
@@ -2694,12 +2716,14 @@ heapam_endscan(HeapScanDesc scan)
  * ----------------
  */
 static void
-heapam_scan_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
+heapam_scan_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;
 }
 
@@ -2728,7 +2752,7 @@ heapam_scan_update_snapshot(HeapScanDesc scan, Snapshot snapshot)
 
 
 static StorageTuple
-heapam_getnext(HeapScanDesc sscan, ScanDirection direction)
+heapam_getnext(StorageScanDesc sscan, ScanDirection direction)
 {
 	HeapScanDesc scan = (HeapScanDesc) sscan;
 
@@ -2736,11 +2760,11 @@ heapam_getnext(HeapScanDesc sscan, ScanDirection direction)
 
 	HEAPAMDEBUG_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)
 	{
@@ -2754,7 +2778,7 @@ heapam_getnext(HeapScanDesc sscan, ScanDirection direction)
 	 */
 	HEAPAMDEBUG_3;				/* heap_getnext returning tuple */
 
-	pgstat_count_heap_getnext(scan->rs_rd);
+	pgstat_count_heap_getnext(scan->rs_scan.rs_rd);
 
 	return &(scan->rs_ctup);
 }
@@ -2774,7 +2798,7 @@ heapam_getnext(HeapScanDesc sscan, ScanDirection direction)
 #endif
 
 static TupleTableSlot *
-heapam_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
+heapam_getnextslot(StorageScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
 {
 	HeapScanDesc scan = (HeapScanDesc) sscan;
 
@@ -2782,11 +2806,11 @@ heapam_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *
 
 	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)
 	{
@@ -2801,11 +2825,34 @@ heapam_getnextslot(HeapScanDesc sscan, ScanDirection direction, TupleTableSlot *
 	 */
 	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);
 }
 
+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);
+}
+
+
+
 /*
  *	heap_fetch		- retrieve tuple with given tid
  *
@@ -3123,18 +3170,19 @@ heapam_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
  * numBlks is number of pages to scan (InvalidBlockNumber means "all")
  */
 static void
-heapam_setscanlimits(HeapScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
+heapam_setscanlimits(StorageScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks)
 {
 	HeapScanDesc scan = (HeapScanDesc) sscan;
 
-	Assert(!scan->rs_inited);	/* else too late to change */
-	Assert(!scan->rs_syncscan); /* else rs_startblock is significant */
+	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;
 }
 
 
@@ -5188,12 +5236,26 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 	amroutine->scansetlimits = heapam_setscanlimits;
 	amroutine->scan_getnext = heapam_getnext;
 	amroutine->scan_getnextslot = heapam_getnextslot;
+	amroutine->scan_fetch_tuple_from_offset = heapam_fetch_tuple_from_offset;
 	amroutine->scan_end = heapam_endscan;
 	amroutine->scan_rescan = heapam_rescan;
 	amroutine->scan_update_snapshot = heapam_scan_update_snapshot;
 	amroutine->tuple_freeze = heapam_freeze_tuple;
 	amroutine->hot_search_buffer = heapam_hot_search_buffer;
 
+	/*
+	 * 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;
 	amroutine->tuple_delete = heapam_heap_delete;
diff --git a/src/backend/access/heap/storageam.c b/src/backend/access/heap/storageam.c
index 0b83ecf1bc..9d6e9e9ea1 100644
--- a/src/backend/access/heap/storageam.c
+++ b/src/backend/access/heap/storageam.c
@@ -54,7 +54,7 @@
  *		Caller must hold a suitable lock on the correct relation.
  * ----------------
  */
-HeapScanDesc
+StorageScanDesc
 storage_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan)
 {
 	Snapshot	snapshot;
@@ -67,6 +67,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
  *
@@ -74,7 +93,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);
 }
@@ -104,7 +123,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)
 {
@@ -112,7 +131,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);
@@ -122,7 +141,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)
@@ -132,7 +151,7 @@ storage_beginscan_strat(Relation relation, Snapshot snapshot,
 												false, false, false);
 }
 
-HeapScanDesc
+StorageScanDesc
 storage_beginscan_bm(Relation relation, Snapshot snapshot,
 					 int nkeys, ScanKey key)
 {
@@ -140,7 +159,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)
@@ -155,7 +174,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);
@@ -171,7 +190,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,
@@ -186,7 +205,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);
 }
@@ -199,23 +218,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);
+}
+
 /*
  *	storage_fetch		- retrieve tuple with given tid
  *
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/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 d2a8a06097..281e3a8e2a 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -789,7 +789,7 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
 				{
 					ScanKeyData key[1];
 					Relation	rel;
-					HeapScanDesc scan;
+					StorageScanDesc scan;
 					HeapTuple	tuple;
 
 					ScanKeyInit(&key[0],
@@ -830,7 +830,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 a201b00cae..49b75478bc 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1897,7 +1897,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],
@@ -2210,7 +2210,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];
@@ -2649,7 +2649,7 @@ IndexCheckExclusion(Relation heapRelation,
 					Relation indexRelation,
 					IndexInfo *indexInfo)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
@@ -2963,7 +2963,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 a5a5268d12..259129d456 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1017,7 +1017,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 cad0eed1a4..7e050b0a96 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2030,7 +2030,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 39850b1b37..09135774c8 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 5209083a72..b8440f8e73 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1874,7 +1874,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 3bab05079e..d7ea804370 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;
 
@@ -8216,7 +8216,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	Expr	   *origexpr;
 	ExprState  *exprstate;
 	TupleDesc	tupdesc;
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	ExprContext *econtext;
 	MemoryContext oldcxt;
@@ -8299,7 +8299,7 @@ validateForeignKeyConstraint(char *conname,
 							 Oid pkindOid,
 							 Oid constraintOid)
 {
-	HeapScanDesc scan;
+	StorageScanDesc scan;
 	HeapTuple	tuple;
 	Trigger		trig;
 	Snapshot	snapshot;
@@ -10806,7 +10806,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 cdfa8ffb3f..be3c0db9e2 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 b95e3fc5ab..273bca0c2b 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 bafc15eac6..16622f3bee 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 a661323fda..07d493b0f8 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 64f3dd8be0..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 0521df1fa9..ac3754d91d 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 9d35a41e22..aa7808001d 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);
 
@@ -1825,7 +1827,7 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel,
 							Relation relation)
 {
 	PartitionDesc partdesc;
-	PartitionKey  partkey;
+	PartitionKey partkey;
 
 	Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 
@@ -1890,8 +1892,8 @@ find_partition_scheme(PlannerInfo *root, Relation relation)
 
 	/*
 	 * Did not find matching partition scheme. Create one copying relevant
-	 * information from the relcache. We need to copy the contents of the array
-	 * since the relcache entry may not survive after we have closed the
+	 * information from the relcache. We need to copy the contents of the
+	 * array since the relcache entry may not survive after we have closed the
 	 * relation.
 	 */
 	part_scheme = (PartitionScheme) palloc0(sizeof(PartitionSchemeData));
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 36808c4067..db4b091471 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 4924daca76..7122987ad7 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -420,7 +420,7 @@ DefineQueryRewrite(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 4cddd73355..42c26861de 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -97,6 +97,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;
 
@@ -106,7 +108,7 @@ typedef struct ParallelHeapScanDescData *ParallelHeapScanDesc;
  */
 #define HeapScanIsValid(scan) PointerIsValid(scan)
 
-extern void heapgetpage(HeapScanDesc scan, BlockNumber page);
+extern void heapgetpage(StorageScanDesc sscan, BlockNumber page);
 
 extern Size heap_parallelscan_estimate(Snapshot snapshot);
 extern void heap_parallelscan_initialize(ParallelHeapScanDesc target,
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/storageam.h b/src/include/access/storageam.h
index ec5dc89a26..4c1ff4cea5 100644
--- a/src/include/access/storageam.h
+++ b/src/include/access/storageam.h
@@ -20,7 +20,7 @@
 
 /* A physical tuple coming from a storage AM scan */
 typedef void *StorageTuple;
-typedef void *StorageScanDesc;
+typedef struct StorageScanDescData *StorageScanDesc;
 
 typedef union tuple_data
 {
@@ -43,29 +43,31 @@ typedef enum tuple_data_flags
 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,
+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 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_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 6e790bb21a..346f0ce0da 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -20,32 +20,37 @@
 #include "utils/relcache.h"
 #include "utils/snapshot.h"
 
-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,
@@ -158,9 +163,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 9d1549115d..a40cfdd71e 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1102,7 +1102,7 @@ typedef struct ScanState
 {
 	PlanState	ps;				/* its first field is NodeTag */
 	Relation	ss_currentRelation;
-	HeapScanDesc ss_currentScanDesc;
+	StorageScanDesc ss_currentScanDesc;
 	TupleTableSlot *ss_ScanTupleSlot;
 } ScanState;
 
@@ -1123,6 +1123,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 */
@@ -1351,6 +1352,7 @@ typedef struct ParallelBitmapHeapState
 typedef struct BitmapHeapScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
+	HeapPageScanDesc pagescan;
 	ExprState  *bitmapqualorig;
 	TIDBitmap  *tbm;
 	TBMIterator *tbmiterator;
-- 
2.14.2.windows.1

0001-Change-Create-Access-method-to-include-storage-handl.patchapplication/octet-stream; name=0001-Change-Create-Access-method-to-include-storage-handl.patchDownload
From 534a5d11912224bcd37e183e25f4340e60481e65 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
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 4c83a63f7d..278a6e8e1c 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
@@ -5174,16 +5176,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 93c031aad7..4a8ab151d7 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 ffdb452b02..ea352fa247 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.14.2.windows.1

0002-Storage-AM-API-hooks-and-related-functions.patchapplication/octet-stream; name=0002-Storage-AM-API-hooks-and-related-functions.patchDownload
From 6ced949e8d427880eeb974ae3b7aa3ca9d83a3dd Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Wed, 30 Aug 2017 12:41:15 +1000
Subject: [PATCH 2/8] Storage AM API hooks and related functions

Basic storage API functions are added, these
will change in the further patches
---
 src/backend/access/heap/Makefile         |   3 +-
 src/backend/access/heap/heapam_storage.c |  58 ++++++++
 src/backend/access/heap/storageamapi.c   | 103 ++++++++++++++
 src/include/access/htup.h                |  21 +++
 src/include/access/storageamapi.h        | 223 +++++++++++++++++++++++++++++++
 src/include/catalog/pg_am.h              |   3 +
 src/include/catalog/pg_proc.h            |   5 +
 src/include/nodes/nodes.h                |   1 +
 src/include/utils/tqual.h                |   9 --
 9 files changed, 416 insertions(+), 10 deletions(-)
 create mode 100644 src/backend/access/heap/heapam_storage.c
 create mode 100644 src/backend/access/heap/storageamapi.c
 create mode 100644 src/include/access/storageamapi.h

diff --git a/src/backend/access/heap/Makefile b/src/backend/access/heap/Makefile
index b83d496bcd..02a3909e7c 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 storageamapi.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..88827e7957
--- /dev/null
+++ b/src/backend/access/heap/heapam_storage.c
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/heapam.h"
+#include "access/heapam_xlog.h"
+#include "access/hio.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/relscan.h"
+#include "access/storageamapi.h"
+#include "access/subtrans.h"
+#include "access/tuptoaster.h"
+#include "access/valid.h"
+#include "access/visibilitymap.h"
+#include "access/xloginsert.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/bufmgr.h"
+#include "storage/lmgr.h"
+#include "storage/predicate.h"
+#include "storage/procarray.h"
+#include "storage/smgr.h"
+#include "storage/spin.h"
+#include "utils/builtins.h"
+#include "utils/inval.h"
+#include "utils/rel.h"
+#include "utils/tqual.h"
+
+
+Datum
+heapam_storage_handler(PG_FUNCTION_ARGS)
+{
+	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
+
+
+	PG_RETURN_POINTER(amroutine);
+}
diff --git a/src/backend/access/heap/storageamapi.c b/src/backend/access/heap/storageamapi.c
new file mode 100644
index 0000000000..bcbe14588b
--- /dev/null
+++ b/src/backend/access/heap/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/htup.h b/src/include/access/htup.h
index 61b3e68639..6459435c78 100644
--- a/src/include/access/htup.h
+++ b/src/include/access/htup.h
@@ -26,6 +26,27 @@ typedef struct MinimalTupleData MinimalTupleData;
 
 typedef MinimalTupleData *MinimalTuple;
 
+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 */
+
+	END_OF_VISIBILITY
+} tuple_visibility_type;
+
+/* 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;
 
 /*
  * HeapTupleData is an in-memory data structure that points to a tuple.
diff --git a/src/include/access/storageamapi.h b/src/include/access/storageamapi.h
new file mode 100644
index 0000000000..90b16ccd0a
--- /dev/null
+++ b/src/include/access/storageamapi.h
@@ -0,0 +1,223 @@
+/*---------------------------------------------------------------------
+ *
+ * storageamapi.h
+ *		API for Postgres storage access methods
+ *
+ * Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * src/include/access/storageamapi.h
+ *---------------------------------------------------------------------
+ */
+#ifndef STORAGEAMAPI_H
+#define STORAGEAMAPI_H
+
+#include "access/htup.h"
+#include "access/heapam.h"
+#include "access/sdir.h"
+#include "access/skey.h"
+#include "executor/tuptable.h"
+#include "utils/relcache.h"
+#include "utils/snapshot.h"
+
+/* A physical tuple coming from a storage AM scan */
+typedef void *StorageTuple;
+
+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;
+
+
+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);
+
+typedef Oid (*TupleInsert_function) (Relation relation,
+									 TupleTableSlot *tupslot,
+									 CommandId cid,
+									 int options,
+									 BulkInsertState bistate);
+
+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,
+											 CommandId cid,
+											 Snapshot crosscheck,
+											 bool wait,
+											 HeapUpdateFailureData *hufd,
+											 LockTupleMode *lockmode);
+
+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 bool (*TupleFreeze_function) (HeapTupleHeader tuple, TransactionId cutoff_xid,
+									  TransactionId cutoff_multi);
+
+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 (*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 void (*RelationSync_function) (Relation relation);
+
+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);
+
+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);
+
+/*
+ * 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;
+
+	/* 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 */
+
+	/* 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 */
+	TupleFreeze_function tuple_freeze;	/* heap_freeze_tuple */
+	TupleGetLatestTid_function tuple_get_latest_tid;	/* heap_get_latest_tid */
+
+	GetTupleData_function get_tuple_data;
+	TupleFromDatum_function tuple_from_datum;
+
+	slot_storageam_hook slot_storageam;
+
+	/*
+	 * 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 */
+
+	SnapshotSatisfies_function snapshot_satisfies;
+	SnapshotSatisfiesUpdate_function snapshot_satisfiesUpdate;	/* HeapTupleSatisfiesUpdate */
+	SnapshotSatisfiesVacuum_function snapshot_satisfiesVacuum;	/* HeapTupleSatisfiesVacuum */
+}			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 4a8ab151d7..f1d0198848 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 ffeeb4919b..805d623cf0 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -497,6 +497,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;
diff --git a/src/include/utils/tqual.h b/src/include/utils/tqual.h
index 96eaf01ca0..4fda7f8384 100644
--- a/src/include/utils/tqual.h
+++ b/src/include/utils/tqual.h
@@ -45,15 +45,6 @@ extern PGDLLIMPORT SnapshotData CatalogSnapshotData;
 #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,
-- 
2.14.2.windows.1

0003-Adding-storageam-hanlder-to-relation-structure.patchapplication/octet-stream; name=0003-Adding-storageam-hanlder-to-relation-structure.patchDownload
From 0fab36983accfec69748a4b47639c4b8771bdbaa Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
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 b8e37809b0..179906a16f 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -34,6 +34,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"
@@ -1368,10 +1369,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);
@@ -1868,6 +1886,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
@@ -2026,6 +2109,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
 	 */
@@ -2354,6 +2442,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)
@@ -3368,6 +3458,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.
 	 *
@@ -3889,6 +3987,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);
 
@@ -5607,6 +5717,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 4bc61e5380..82d8a534b1 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)
 	 *
@@ -427,6 +433,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 3c53cefe4b..03d996f5a1 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -71,6 +71,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.14.2.windows.1

0004-Adding-tuple-visibility-function-to-storage-AM.patchapplication/octet-stream; name=0004-Adding-tuple-visibility-function-to-storage-AM.patchDownload
From 86a61d036c6fa456a18534204227e133e89953ea Mon Sep 17 00:00:00 2001
From: Hari Babu Kommi <kommi.haribabu@gmail.com>
Date: Fri, 8 Sep 2017 15:38:28 +1000
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 heapam storage is moved into heapam_common.c
and heapam_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            |    3 +-
 src/backend/access/heap/heapam.c            |   62 +-
 src/backend/access/heap/heapam_common.c     |  160 +++
 src/backend/access/heap/heapam_storage.c    | 1644 ++++++++++++++++++++++++
 src/backend/access/heap/pruneheap.c         |    4 +-
 src/backend/access/heap/rewriteheap.c       |    1 +
 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           |    6 +-
 src/backend/executor/nodeBitmapHeapscan.c   |    2 +-
 src/backend/executor/nodeModifyTable.c      |   11 +-
 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/backend/utils/time/tqual.c              | 1807 ---------------------------
 src/include/access/heapam_common.h          |   96 ++
 src/include/access/htup.h                   |    7 +-
 src/include/storage/bufmgr.h                |    6 +-
 src/include/utils/snapshot.h                |    2 +-
 src/include/utils/tqual.h                   |   65 +-
 29 files changed, 2023 insertions(+), 1925 deletions(-)
 create mode 100644 src/backend/access/heap/heapam_common.c
 delete mode 100644 src/backend/utils/time/tqual.c
 create mode 100644 src/include/access/heapam_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 02a3909e7c..e6bc18e5ea 100644
--- a/src/backend/access/heap/Makefile
+++ b/src/backend/access/heap/Makefile
@@ -12,7 +12,8 @@ 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 storageamapi.o \
+OBJS = heapam.o heapam_common.o heapam_storage.o hio.o \
+	pruneheap.o rewriteheap.o storageamapi.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 765750b874..f62f04e185 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -40,11 +40,13 @@
 
 #include "access/bufmask.h"
 #include "access/heapam.h"
+#include "access/heapam_common.h"
 #include "access/heapam_xlog.h"
 #include "access/hio.h"
 #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 +440,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 +655,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 +844,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 +889,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 +959,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 +995,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 +1058,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 +1851,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 +1970,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 +2117,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 +2291,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 +3117,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 +3228,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 +3688,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 +3869,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 +4620,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_common.c b/src/backend/access/heap/heapam_common.c
new file mode 100644
index 0000000000..4cc3eeff0c
--- /dev/null
+++ b/src/backend/access/heap/heapam_common.c
@@ -0,0 +1,160 @@
+/*-------------------------------------------------------------------------
+ *
+ * heapam_common.c
+ *	  heapam 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/heap/heapam_common.c
+ *
+ *
+ * NOTES
+ *	  This file contains the storage_ routines which implement
+ *	  the POSTGRES storage access method used for all POSTGRES
+ *	  relations.
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/heapam_common.h"
+#include "access/htup_details.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);
+}
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index 88827e7957..f7e11d4946 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -21,6 +21,7 @@
 #include "postgres.h"
 
 #include "access/heapam.h"
+#include "access/heapam_common.h"
 #include "access/heapam_xlog.h"
 #include "access/hio.h"
 #include "access/htup_details.h"
@@ -47,12 +48,1655 @@
 #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
+ * has now committed or aborted (and it is safe to set the hint bits).
+ * If the hint bits are changed, MarkBufferDirtyHint is called on
+ * the passed-in buffer.  The caller must hold not only a pin, but at least
+ * shared buffer content lock on the buffer containing the tuple.
+ *
+ * NOTE: When using a non-MVCC snapshot, we must check
+ * TransactionIdIsInProgress (which looks in the PGXACT array)
+ * before TransactionIdDidCommit/TransactionIdDidAbort (which look in
+ * pg_xact).  Otherwise we have a race condition: we might decide that a
+ * just-committed transaction crashed, because none of the tests succeed.
+ * xact.c is careful to record commit/abort in pg_xact before it unsets
+ * MyPgXact->xid in the PGXACT array.  That fixes that problem, but it
+ * also means there is a window where TransactionIdIsInProgress and
+ * TransactionIdDidCommit will both return true.  If we check only
+ * TransactionIdDidCommit, we could consider a tuple committed when a
+ * later GetSnapshotData call will still think the originating transaction
+ * is in progress, which leads to application-level inconsistency.  The
+ * upshot is that we gotta check TransactionIdIsInProgress first in all
+ * code paths, except for a few cases where we are looking at
+ * subtransactions of our own main transaction and so there can't be any
+ * race condition.
+ *
+ * When using an MVCC snapshot, we rely on XidInMVCCSnapshot rather than
+ * TransactionIdIsInProgress, but the logic is otherwise the same: do not
+ * check pg_xact until after deciding that the xact is no longer in progress.
+ *
+ *
+ * Summary of visibility functions:
+ *
+ *	 HeapTupleSatisfiesMVCC()
+ *		  visible to supplied snapshot, excludes current command
+ *	 HeapTupleSatisfiesUpdate()
+ *		  visible to instant snapshot, with user-supplied command
+ *		  counter and more complex result
+ *	 HeapTupleSatisfiesSelf()
+ *		  visible to instant snapshot and current command
+ *	 HeapTupleSatisfiesDirty()
+ *		  like HeapTupleSatisfiesSelf(), but includes open transactions
+ *	 HeapTupleSatisfiesVacuum()
+ *		  visible to any running transaction, used by VACUUM
+ *   HeapTupleSatisfiesNonVacuumable()
+ *        Snapshot-style API for HeapTupleSatisfiesVacuum
+ *	 HeapTupleSatisfiesToast()
+ *		  visible unless part of interrupted vacuum, used for TOAST
+ *	 HeapTupleSatisfiesAny()
+ *		  all tuples are visible
+ *
+ * -------------------------------------------------------------------------
+ */
+
+/*
+ * HeapTupleSatisfiesSelf
+ *		True iff heap tuple is valid "for itself".
+ *
+ *	Here, we consider the effects of:
+ *		all committed transactions (as of the current instant)
+ *		previous commands of this transaction
+ *		changes made by the current command
+ *
+ * Note:
+ *		Assumes heap tuple is valid.
+ *
+ * The satisfaction of "itself" requires the following:
+ *
+ * ((Xmin == my-transaction &&				the row was updated by the current transaction, and
+ *		(Xmax is null						it was not deleted
+ *		 [|| Xmax != my-transaction)])			[or it was deleted by another transaction]
+ * ||
+ *
+ * (Xmin is committed &&					the row was modified by a committed transaction, and
+ *		(Xmax is null ||					the row has not been deleted, or
+ *			(Xmax != my-transaction &&			the row was deleted by another transaction
+ *			 Xmax is not committed)))			that has not been committed
+ */
+static bool
+HeapTupleSatisfiesSelf(StorageTuple stup, Snapshot snapshot, Buffer buffer)
+{
+	HeapTuple	htup = (HeapTuple) stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return false;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return false;
+			if (!TransactionIdIsInProgress(xvac))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (TransactionIdIsInProgress(xvac))
+					return false;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return true;
+
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
+				return true;
+
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleGetUpdateXid(tuple);
+
+				/* not LOCKED_ONLY, so it has to have an xmax */
+				Assert(TransactionIdIsValid(xmax));
+
+				/* updating subtransaction must have aborted */
+				if (!TransactionIdIsCurrentTransactionId(xmax))
+					return true;
+				else
+					return false;
+			}
+
+			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+			{
+				/* deleting subtransaction must have aborted */
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+				return true;
+			}
+
+			return false;
+		}
+		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+			return false;
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return false;
+		}
+	}
+
+	/* by here, the inserting transaction has committed */
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
+		return true;
+
+	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+		return false;			/* updated by other */
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+
+		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 true;
+		if (TransactionIdDidCommit(xmax))
+			return false;
+		/* it must have aborted or crashed */
+		return true;
+	}
+
+	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+		return false;
+	}
+
+	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+		return true;
+
+	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		/* it must have aborted or crashed */
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return true;
+	}
+
+	/* xmax transaction committed */
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	{
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return true;
+	}
+
+	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+				HeapTupleHeaderGetRawXmax(tuple));
+	return false;
+}
+
+/*
+ * HeapTupleSatisfiesAny
+ *		Dummy "satisfies" routine: any tuple satisfies SnapshotAny.
+ */
+static bool
+HeapTupleSatisfiesAny(StorageTuple stup, Snapshot snapshot, Buffer buffer)
+{
+	return true;
+}
+
+/*
+ * HeapTupleSatisfiesToast
+ *		True iff heap tuple is valid as a TOAST row.
+ *
+ * This is a simplified version that only checks for VACUUM moving conditions.
+ * It's appropriate for TOAST usage because TOAST really doesn't want to do
+ * its own time qual checks; if you can see the main table row that contains
+ * a TOAST reference, you should be able to see the TOASTed value.  However,
+ * vacuuming a TOAST table is independent of the main table, and in case such
+ * a vacuum fails partway through, we'd better do this much checking.
+ *
+ * Among other things, this means you can't do UPDATEs of rows in a TOAST
+ * table.
+ */
+static bool
+HeapTupleSatisfiesToast(StorageTuple stup, Snapshot snapshot,
+						Buffer buffer)
+{
+	HeapTuple	htup = (HeapTuple) stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return false;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return false;
+			if (!TransactionIdIsInProgress(xvac))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (TransactionIdIsInProgress(xvac))
+					return false;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+			}
+		}
+
+		/*
+		 * An invalid Xmin can be left behind by a speculative insertion that
+		 * is canceled by super-deleting the tuple.  This also applies to
+		 * TOAST tuples created during speculative insertion.
+		 */
+		else if (!TransactionIdIsValid(HeapTupleHeaderGetXmin(tuple)))
+			return false;
+	}
+
+	/* otherwise assume the tuple is valid for TOAST. */
+	return true;
+}
+
+/*
+ * HeapTupleSatisfiesUpdate
+ *
+ *	This function returns a more detailed result code than most of the
+ *	functions in this file, since UPDATE needs to know more than "is it
+ *	visible?".  It also allows for user-supplied CommandId rather than
+ *	relying on CurrentCommandId.
+ *
+ *	The possible return codes are:
+ *
+ *	HeapTupleInvisible: the tuple didn't exist at all when the scan started,
+ *	e.g. it was created by a later CommandId.
+ *
+ *	HeapTupleMayBeUpdated: The tuple is valid and visible, so it may be
+ *	updated.
+ *
+ *	HeapTupleSelfUpdated: The tuple was updated by the current transaction,
+ *	after the current scan started.
+ *
+ *	HeapTupleUpdated: The tuple was updated by a committed transaction.
+ *
+ *	HeapTupleBeingUpdated: The tuple is being updated by an in-progress
+ *	transaction other than the current transaction.  (Note: this includes
+ *	the case where the tuple is share-locked by a MultiXact, even if the
+ *	MultiXact includes the current transaction.  Callers that want to
+ *	distinguish that case must test for it themselves.)
+ */
+static HTSU_Result
+HeapTupleSatisfiesUpdate(StorageTuple stup, CommandId curcid,
+						 Buffer buffer)
+{
+	HeapTuple	htup = (HeapTuple) stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return HeapTupleInvisible;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return HeapTupleInvisible;
+			if (!TransactionIdIsInProgress(xvac))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return HeapTupleInvisible;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (TransactionIdIsInProgress(xvac))
+					return HeapTupleInvisible;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return HeapTupleInvisible;
+				}
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (HeapTupleHeaderGetCmin(tuple) >= curcid)
+				return HeapTupleInvisible;	/* inserted after scan started */
+
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return HeapTupleMayBeUpdated;
+
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleHeaderGetRawXmax(tuple);
+
+				/*
+				 * Careful here: even though this tuple was created by our own
+				 * transaction, it might be locked by other transactions, if
+				 * the original version was key-share locked when we updated
+				 * it.
+				 */
+
+				if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+				{
+					if (MultiXactIdIsRunning(xmax, true))
+						return HeapTupleBeingUpdated;
+					else
+						return HeapTupleMayBeUpdated;
+				}
+
+				/*
+				 * If the locker is gone, then there is nothing of interest
+				 * left in this Xmax; otherwise, report the tuple as
+				 * locked/updated.
+				 */
+				if (!TransactionIdIsInProgress(xmax))
+					return HeapTupleMayBeUpdated;
+				return HeapTupleBeingUpdated;
+			}
+
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleGetUpdateXid(tuple);
+
+				/* not LOCKED_ONLY, so it has to have an xmax */
+				Assert(TransactionIdIsValid(xmax));
+
+				/* deleting subtransaction must have aborted */
+				if (!TransactionIdIsCurrentTransactionId(xmax))
+				{
+					if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
+											 false))
+						return HeapTupleBeingUpdated;
+					return HeapTupleMayBeUpdated;
+				}
+				else
+				{
+					if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+						return HeapTupleSelfUpdated;	/* updated after scan
+														 * started */
+					else
+						return HeapTupleInvisible;	/* updated before scan
+													 * started */
+				}
+			}
+
+			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+			{
+				/* deleting subtransaction must have aborted */
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+				return HeapTupleMayBeUpdated;
+			}
+
+			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+				return HeapTupleSelfUpdated;	/* updated after scan started */
+			else
+				return HeapTupleInvisible;	/* updated before scan started */
+		}
+		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+			return HeapTupleInvisible;
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return HeapTupleInvisible;
+		}
+	}
+
+	/* by here, the inserting transaction has committed */
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
+		return HeapTupleMayBeUpdated;
+
+	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return HeapTupleMayBeUpdated;
+		return HeapTupleUpdated;	/* updated by other */
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		if (HEAP_LOCKED_UPGRADED(tuple->t_infomask))
+			return HeapTupleMayBeUpdated;
+
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+		{
+			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), true))
+				return HeapTupleBeingUpdated;
+
+			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
+			return HeapTupleMayBeUpdated;
+		}
+
+		xmax = HeapTupleGetUpdateXid(tuple);
+		if (!TransactionIdIsValid(xmax))
+		{
+			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+				return HeapTupleBeingUpdated;
+		}
+
+		/* not LOCKED_ONLY, so it has to have an xmax */
+		Assert(TransactionIdIsValid(xmax));
+
+		if (TransactionIdIsCurrentTransactionId(xmax))
+		{
+			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+				return HeapTupleSelfUpdated;	/* updated after scan started */
+			else
+				return HeapTupleInvisible;	/* updated before scan started */
+		}
+
+		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+			return HeapTupleBeingUpdated;
+
+		if (TransactionIdDidCommit(xmax))
+			return HeapTupleUpdated;
+
+		/*
+		 * By here, the update in the Xmax is either aborted or crashed, but
+		 * what about the other members?
+		 */
+
+		if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+		{
+			/*
+			 * There's no member, even just a locker, alive anymore, so we can
+			 * mark the Xmax as invalid.
+			 */
+			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+						InvalidTransactionId);
+			return HeapTupleMayBeUpdated;
+		}
+		else
+		{
+			/* There are lockers running */
+			return HeapTupleBeingUpdated;
+		}
+	}
+
+	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return HeapTupleBeingUpdated;
+		if (HeapTupleHeaderGetCmax(tuple) >= curcid)
+			return HeapTupleSelfUpdated;	/* updated after scan started */
+		else
+			return HeapTupleInvisible;	/* updated before scan started */
+	}
+
+	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+		return HeapTupleBeingUpdated;
+
+	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		/* it must have aborted or crashed */
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return HeapTupleMayBeUpdated;
+	}
+
+	/* xmax transaction committed */
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	{
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return HeapTupleMayBeUpdated;
+	}
+
+	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+				HeapTupleHeaderGetRawXmax(tuple));
+	return HeapTupleUpdated;	/* updated by other */
+}
+
+/*
+ * HeapTupleSatisfiesDirty
+ *		True iff heap tuple is valid including effects of open transactions.
+ *
+ *	Here, we consider the effects of:
+ *		all committed and in-progress transactions (as of the current instant)
+ *		previous commands of this transaction
+ *		changes made by the current command
+ *
+ * This is essentially like HeapTupleSatisfiesSelf as far as effects of
+ * the current transaction and committed/aborted xacts are concerned.
+ * However, we also include the effects of other xacts still in progress.
+ *
+ * A special hack is that the passed-in snapshot struct is used as an
+ * output argument to return the xids of concurrent xacts that affected the
+ * tuple.  snapshot->xmin is set to the tuple's xmin if that is another
+ * transaction that's still in progress; or to InvalidTransactionId if the
+ * tuple's xmin is committed good, committed dead, or my own xact.
+ * Similarly for snapshot->xmax and the tuple's xmax.  If the tuple was
+ * inserted speculatively, meaning that the inserter might still back down
+ * on the insertion without aborting the whole transaction, the associated
+ * token is also returned in snapshot->speculativeToken.
+ */
+static bool
+HeapTupleSatisfiesDirty(StorageTuple stup, Snapshot snapshot,
+						Buffer buffer)
+{
+	HeapTuple	htup = (HeapTuple) stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	snapshot->xmin = snapshot->xmax = InvalidTransactionId;
+	snapshot->speculativeToken = 0;
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return false;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return false;
+			if (!TransactionIdIsInProgress(xvac))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (TransactionIdIsInProgress(xvac))
+					return false;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return true;
+
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
+				return true;
+
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleGetUpdateXid(tuple);
+
+				/* not LOCKED_ONLY, so it has to have an xmax */
+				Assert(TransactionIdIsValid(xmax));
+
+				/* updating subtransaction must have aborted */
+				if (!TransactionIdIsCurrentTransactionId(xmax))
+					return true;
+				else
+					return false;
+			}
+
+			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+			{
+				/* deleting subtransaction must have aborted */
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+				return true;
+			}
+
+			return false;
+		}
+		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			/*
+			 * Return the speculative token to caller.  Caller can worry about
+			 * xmax, since it requires a conclusively locked row version, and
+			 * a concurrent update to this tuple is a conflict of its
+			 * purposes.
+			 */
+			if (HeapTupleHeaderIsSpeculative(tuple))
+			{
+				snapshot->speculativeToken =
+					HeapTupleHeaderGetSpeculativeToken(tuple);
+
+				Assert(snapshot->speculativeToken != 0);
+			}
+
+			snapshot->xmin = HeapTupleHeaderGetRawXmin(tuple);
+			/* XXX shouldn't we fall through to look at xmax? */
+			return true;		/* in insertion by other */
+		}
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return false;
+		}
+	}
+
+	/* by here, the inserting transaction has committed */
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
+		return true;
+
+	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+		return false;			/* updated by other */
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+
+		xmax = HeapTupleGetUpdateXid(tuple);
+
+		/* not LOCKED_ONLY, so it has to have an xmax */
+		Assert(TransactionIdIsValid(xmax));
+
+		if (TransactionIdIsCurrentTransactionId(xmax))
+			return false;
+		if (TransactionIdIsInProgress(xmax))
+		{
+			snapshot->xmax = xmax;
+			return true;
+		}
+		if (TransactionIdDidCommit(xmax))
+			return false;
+		/* it must have aborted or crashed */
+		return true;
+	}
+
+	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			return true;
+		return false;
+	}
+
+	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+			snapshot->xmax = HeapTupleHeaderGetRawXmax(tuple);
+		return true;
+	}
+
+	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+	{
+		/* it must have aborted or crashed */
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return true;
+	}
+
+	/* xmax transaction committed */
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	{
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+					InvalidTransactionId);
+		return true;
+	}
+
+	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+				HeapTupleHeaderGetRawXmax(tuple));
+	return false;				/* updated by other */
+}
+
+/*
+ * HeapTupleSatisfiesMVCC
+ *		True iff heap tuple is valid for the given MVCC snapshot.
+ *
+ *	Here, we consider the effects of:
+ *		all transactions committed as of the time of the given snapshot
+ *		previous commands of this transaction
+ *
+ *	Does _not_ include:
+ *		transactions shown as in-progress by the snapshot
+ *		transactions started after the snapshot was taken
+ *		changes made by the current command
+ *
+ * Notice that here, we will not update the tuple status hint bits if the
+ * inserting/deleting transaction is still running according to our snapshot,
+ * even if in reality it's committed or aborted by now.  This is intentional.
+ * Checking the true transaction state would require access to high-traffic
+ * shared data structures, creating contention we'd rather do without, and it
+ * would not change the result of our visibility check anyway.  The hint bits
+ * will be updated by the first visitor that has a snapshot new enough to see
+ * the inserting/deleting transaction as done.  In the meantime, the cost of
+ * leaving the hint bits unset is basically that each HeapTupleSatisfiesMVCC
+ * call will need to run TransactionIdIsCurrentTransactionId in addition to
+ * XidInMVCCSnapshot (but it would have to do the latter anyway).  In the old
+ * coding where we tried to set the hint bits as soon as possible, we instead
+ * did TransactionIdIsInProgress in each call --- to no avail, as long as the
+ * inserting/deleting transaction was still running --- which was more cycles
+ * and more contention on the PGXACT array.
+ */
+static bool
+HeapTupleSatisfiesMVCC(StorageTuple stup, Snapshot snapshot,
+					   Buffer buffer)
+{
+	HeapTuple	htup = (HeapTuple) stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return false;
+
+		/* Used by pre-9.0 binary upgrades */
+		if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return false;
+			if (!XidInMVCCSnapshot(xvac, snapshot))
+			{
+				if (TransactionIdDidCommit(xvac))
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			}
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (!TransactionIdIsCurrentTransactionId(xvac))
+			{
+				if (XidInMVCCSnapshot(xvac, snapshot))
+					return false;
+				if (TransactionIdDidCommit(xvac))
+					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+								InvalidTransactionId);
+				else
+				{
+					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+								InvalidTransactionId);
+					return false;
+				}
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (HeapTupleHeaderGetCmin(tuple) >= snapshot->curcid)
+				return false;	/* inserted after scan started */
+
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return true;
+
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
+				return true;
+
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				TransactionId xmax;
+
+				xmax = HeapTupleGetUpdateXid(tuple);
+
+				/* not LOCKED_ONLY, so it has to have an xmax */
+				Assert(TransactionIdIsValid(xmax));
+
+				/* updating subtransaction must have aborted */
+				if (!TransactionIdIsCurrentTransactionId(xmax))
+					return true;
+				else if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+					return true;	/* updated after scan started */
+				else
+					return false;	/* updated before scan started */
+			}
+
+			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+			{
+				/* deleting subtransaction must have aborted */
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+				return true;
+			}
+
+			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+				return true;	/* deleted after scan started */
+			else
+				return false;	/* deleted before scan started */
+		}
+		else if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
+			return false;
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return false;
+		}
+	}
+	else
+	{
+		/* xmin is committed, but maybe not according to our snapshot */
+		if (!HeapTupleHeaderXminFrozen(tuple) &&
+			XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
+			return false;		/* treat as still in progress */
+	}
+
+	/* by here, the inserting transaction has committed */
+
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
+		return true;
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+		return true;
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		/* already checked above */
+		Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
+
+		xmax = HeapTupleGetUpdateXid(tuple);
+
+		/* not LOCKED_ONLY, so it has to have an xmax */
+		Assert(TransactionIdIsValid(xmax));
+
+		if (TransactionIdIsCurrentTransactionId(xmax))
+		{
+			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+				return true;	/* deleted after scan started */
+			else
+				return false;	/* deleted before scan started */
+		}
+		if (XidInMVCCSnapshot(xmax, snapshot))
+			return true;
+		if (TransactionIdDidCommit(xmax))
+			return false;		/* updating transaction committed */
+		/* it must have aborted or crashed */
+		return true;
+	}
+
+	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
+	{
+		if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
+		{
+			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+				return true;	/* deleted after scan started */
+			else
+				return false;	/* deleted before scan started */
+		}
+
+		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
+			return true;
+
+		if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+		{
+			/* it must have aborted or crashed */
+			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+						InvalidTransactionId);
+			return true;
+		}
+
+		/* xmax transaction committed */
+		SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+					HeapTupleHeaderGetRawXmax(tuple));
+	}
+	else
+	{
+		/* xmax is committed, but maybe not according to our snapshot */
+		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
+			return true;		/* treat as still in progress */
+	}
+
+	/* xmax transaction committed */
+
+	return false;
+}
+
+
+/*
+ * HeapTupleSatisfiesVacuum
+ *
+ *	Determine the status of tuples for VACUUM purposes.  Here, what
+ *	we mainly want to know is if a tuple is potentially visible to *any*
+ *	running transaction.  If so, it can't be removed yet by VACUUM.
+ *
+ * OldestXmin is a cutoff XID (obtained from GetOldestXmin()).  Tuples
+ * deleted by XIDs >= OldestXmin are deemed "recently dead"; they might
+ * still be visible to some open transaction, so we can't remove them,
+ * even if we see that the deleting transaction has committed.
+ */
+static HTSV_Result
+HeapTupleSatisfiesVacuum(StorageTuple stup, TransactionId OldestXmin,
+						 Buffer buffer)
+{
+	HeapTuple	htup = (HeapTuple) stup;
+	HeapTupleHeader tuple = htup->t_data;
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	/*
+	 * Has inserting transaction committed?
+	 *
+	 * If the inserting transaction aborted, then the tuple was never visible
+	 * to any other transaction, so we can delete it immediately.
+	 */
+	if (!HeapTupleHeaderXminCommitted(tuple))
+	{
+		if (HeapTupleHeaderXminInvalid(tuple))
+			return HEAPTUPLE_DEAD;
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_OFF)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return HEAPTUPLE_DELETE_IN_PROGRESS;
+			if (TransactionIdIsInProgress(xvac))
+				return HEAPTUPLE_DELETE_IN_PROGRESS;
+			if (TransactionIdDidCommit(xvac))
+			{
+				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+							InvalidTransactionId);
+				return HEAPTUPLE_DEAD;
+			}
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						InvalidTransactionId);
+		}
+		/* Used by pre-9.0 binary upgrades */
+		else if (tuple->t_infomask & HEAP_MOVED_IN)
+		{
+			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+			if (TransactionIdIsCurrentTransactionId(xvac))
+				return HEAPTUPLE_INSERT_IN_PROGRESS;
+			if (TransactionIdIsInProgress(xvac))
+				return HEAPTUPLE_INSERT_IN_PROGRESS;
+			if (TransactionIdDidCommit(xvac))
+				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+							InvalidTransactionId);
+			else
+			{
+				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+							InvalidTransactionId);
+				return HEAPTUPLE_DEAD;
+			}
+		}
+		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
+				return HEAPTUPLE_INSERT_IN_PROGRESS;
+			/* only locked? run infomask-only check first, for performance */
+			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask) ||
+				HeapTupleHeaderIsOnlyLocked(tuple))
+				return HEAPTUPLE_INSERT_IN_PROGRESS;
+			/* inserted and then deleted by same xact */
+			if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple)))
+				return HEAPTUPLE_DELETE_IN_PROGRESS;
+			/* deleting subtransaction must have aborted */
+			return HEAPTUPLE_INSERT_IN_PROGRESS;
+		}
+		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
+		{
+			/*
+			 * It'd be possible to discern between INSERT/DELETE in progress
+			 * here by looking at xmax - but that doesn't seem beneficial for
+			 * the majority of callers and even detrimental for some. We'd
+			 * rather have callers look at/wait for xmin than xmax. It's
+			 * always correct to return INSERT_IN_PROGRESS because that's
+			 * what's happening from the view of other backends.
+			 */
+			return HEAPTUPLE_INSERT_IN_PROGRESS;
+		}
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+						HeapTupleHeaderGetRawXmin(tuple));
+		else
+		{
+			/*
+			 * Not in Progress, Not Committed, so either Aborted or crashed
+			 */
+			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+						InvalidTransactionId);
+			return HEAPTUPLE_DEAD;
+		}
+
+		/*
+		 * At this point the xmin is known committed, but we might not have
+		 * been able to set the hint bit yet; so we can no longer Assert that
+		 * it's set.
+		 */
+	}
+
+	/*
+	 * Okay, the inserter committed, so it was good at some point.  Now what
+	 * about the deleting transaction?
+	 */
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)
+		return HEAPTUPLE_LIVE;
+
+	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+	{
+		/*
+		 * "Deleting" xact really only locked it, so the tuple is live in any
+		 * case.  However, we should make sure that either XMAX_COMMITTED or
+		 * XMAX_INVALID gets set once the xact is gone, to reduce the costs of
+		 * examining the tuple for future xacts.
+		 */
+		if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
+		{
+			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				/*
+				 * If it's a pre-pg_upgrade tuple, the multixact cannot
+				 * possibly be running; otherwise have to check.
+				 */
+				if (!HEAP_LOCKED_UPGRADED(tuple->t_infomask) &&
+					MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
+										 true))
+					return HEAPTUPLE_LIVE;
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
+			}
+			else
+			{
+				if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+					return HEAPTUPLE_LIVE;
+				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+							InvalidTransactionId);
+			}
+		}
+
+		/*
+		 * We don't really care whether xmax did commit, abort or crash. We
+		 * know that xmax did lock the tuple, but it did not and will never
+		 * actually update it.
+		 */
+
+		return HEAPTUPLE_LIVE;
+	}
+
+	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		TransactionId xmax;
+
+		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+		{
+			/* already checked above */
+			Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
+
+			xmax = HeapTupleGetUpdateXid(tuple);
+
+			/* not LOCKED_ONLY, so it has to have an xmax */
+			Assert(TransactionIdIsValid(xmax));
+
+			if (TransactionIdIsInProgress(xmax))
+				return HEAPTUPLE_DELETE_IN_PROGRESS;
+			else if (TransactionIdDidCommit(xmax))
+				/* there are still lockers around -- can't return DEAD here */
+				return HEAPTUPLE_RECENTLY_DEAD;
+			/* updating transaction aborted */
+			return HEAPTUPLE_LIVE;
+		}
+
+		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED));
+
+		xmax = HeapTupleGetUpdateXid(tuple);
+
+		/* not LOCKED_ONLY, so it has to have an xmax */
+		Assert(TransactionIdIsValid(xmax));
+
+		/* multi is not running -- updating xact cannot be */
+		Assert(!TransactionIdIsInProgress(xmax));
+		if (TransactionIdDidCommit(xmax))
+		{
+			if (!TransactionIdPrecedes(xmax, OldestXmin))
+				return HEAPTUPLE_RECENTLY_DEAD;
+			else
+				return HEAPTUPLE_DEAD;
+		}
+
+		/*
+		 * Not in Progress, Not Committed, so either Aborted or crashed.
+		 * Remove the Xmax.
+		 */
+		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
+		return HEAPTUPLE_LIVE;
+	}
+
+	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
+	{
+		if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
+			return HEAPTUPLE_DELETE_IN_PROGRESS;
+		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
+			SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
+						HeapTupleHeaderGetRawXmax(tuple));
+		else
+		{
+			/*
+			 * Not in Progress, Not Committed, so either Aborted or crashed
+			 */
+			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+						InvalidTransactionId);
+			return HEAPTUPLE_LIVE;
+		}
+
+		/*
+		 * At this point the xmax is known committed, but we might not have
+		 * been able to set the hint bit yet; so we can no longer Assert that
+		 * it's set.
+		 */
+	}
+
+	/*
+	 * Deleter committed, but perhaps it was recent enough that some open
+	 * transactions could still see the tuple.
+	 */
+	if (!TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin))
+		return HEAPTUPLE_RECENTLY_DEAD;
+
+	/* Otherwise, it's dead and removable */
+	return HEAPTUPLE_DEAD;
+}
+
+/*
+ * HeapTupleSatisfiesNonVacuumable
+ *
+ *     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.
+ */
+static bool
+HeapTupleSatisfiesNonVacuumable(StorageTuple htup, Snapshot snapshot,
+								Buffer buffer)
+{
+	return HeapTupleSatisfiesVacuum(htup, snapshot->xmin, buffer)
+		!= HEAPTUPLE_DEAD;
+}
+
+/*
+ * 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;
+}
+
+/*
+ * check whether the transaction id 'xid' is in the pre-sorted array 'xip'.
+ */
+static bool
+TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num)
+{
+	return bsearch(&xid, xip, num,
+				   sizeof(TransactionId), xidComparator) != NULL;
+}
+
+/*
+ * See the comments for HeapTupleSatisfiesMVCC for the semantics this function
+ * obeys.
+ *
+ * Only usable on tuples from catalog tables!
+ *
+ * We don't need to support HEAP_MOVED_(IN|OFF) for now because we only support
+ * reading catalog pages which couldn't have been created in an older version.
+ *
+ * We don't set any hint bits in here as it seems unlikely to be beneficial as
+ * those should already be set by normal access and it seems to be too
+ * dangerous to do so as the semantics of doing so during timetravel are more
+ * complicated than when dealing "only" with the present.
+ */
+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);
+
+	Assert(ItemPointerIsValid(&htup->t_self));
+	Assert(htup->t_tableOid != InvalidOid);
+
+	/* inserting transaction aborted */
+	if (HeapTupleHeaderXminInvalid(tuple))
+	{
+		Assert(!TransactionIdDidCommit(xmin));
+		return false;
+	}
+	/* check if it's one of our txids, toplevel is also in there */
+	else if (TransactionIdInArray(xmin, snapshot->subxip, snapshot->subxcnt))
+	{
+		bool		resolved;
+		CommandId	cmin = HeapTupleHeaderGetRawCommandId(tuple);
+		CommandId	cmax = InvalidCommandId;
+
+		/*
+		 * another transaction might have (tried to) delete this tuple or
+		 * cmin/cmax was stored in a combocid. So we need to lookup the actual
+		 * values externally.
+		 */
+		resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot,
+												 htup, buffer,
+												 &cmin, &cmax);
+
+		if (!resolved)
+			elog(ERROR, "could not resolve cmin/cmax of catalog tuple");
+
+		Assert(cmin != InvalidCommandId);
+
+		if (cmin >= snapshot->curcid)
+			return false;		/* inserted after scan started */
+		/* fall through */
+	}
+	/* committed before our xmin horizon. Do a normal visibility check. */
+	else if (TransactionIdPrecedes(xmin, snapshot->xmin))
+	{
+		Assert(!(HeapTupleHeaderXminCommitted(tuple) &&
+				 !TransactionIdDidCommit(xmin)));
+
+		/* check for hint bit first, consult clog afterwards */
+		if (!HeapTupleHeaderXminCommitted(tuple) &&
+			!TransactionIdDidCommit(xmin))
+			return false;
+		/* fall through */
+	}
+	/* beyond our xmax horizon, i.e. invisible */
+	else if (TransactionIdFollowsOrEquals(xmin, snapshot->xmax))
+	{
+		return false;
+	}
+	/* check if it's a committed transaction in [xmin, xmax) */
+	else if (TransactionIdInArray(xmin, snapshot->xip, snapshot->xcnt))
+	{
+		/* fall through */
+	}
+
+	/*
+	 * none of the above, i.e. between [xmin, xmax) but hasn't committed. I.e.
+	 * invisible.
+	 */
+	else
+	{
+		return false;
+	}
+
+	/* at this point we know xmin is visible, go on to check xmax */
+
+	/* xid invalid or aborted */
+	if (tuple->t_infomask & HEAP_XMAX_INVALID)
+		return true;
+	/* locked tuples are always visible */
+	else if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
+		return true;
+
+	/*
+	 * We can see multis here if we're looking at user tables or if somebody
+	 * SELECT ... FOR SHARE/UPDATE a system table.
+	 */
+	else if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		xmax = HeapTupleGetUpdateXid(tuple);
+	}
+
+	/* check if it's one of our txids, toplevel is also in there */
+	if (TransactionIdInArray(xmax, snapshot->subxip, snapshot->subxcnt))
+	{
+		bool		resolved;
+		CommandId	cmin;
+		CommandId	cmax = HeapTupleHeaderGetRawCommandId(tuple);
+
+		/* Lookup actual cmin/cmax values */
+		resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot,
+												 htup, buffer,
+												 &cmin, &cmax);
+
+		if (!resolved)
+			elog(ERROR, "could not resolve combocid to cmax");
+
+		Assert(cmax != InvalidCommandId);
+
+		if (cmax >= snapshot->curcid)
+			return true;		/* deleted after scan started */
+		else
+			return false;		/* deleted before scan started */
+	}
+	/* below xmin horizon, normal transaction state is valid */
+	else if (TransactionIdPrecedes(xmax, snapshot->xmin))
+	{
+		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED &&
+				 !TransactionIdDidCommit(xmax)));
+
+		/* check hint bit first */
+		if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
+			return false;
+
+		/* check clog */
+		return !TransactionIdDidCommit(xmax);
+	}
+	/* above xmax horizon, we cannot possibly see the deleting transaction */
+	else if (TransactionIdFollowsOrEquals(xmax, snapshot->xmax))
+		return true;
+	/* xmax is between [xmin, xmax), check known committed array */
+	else if (TransactionIdInArray(xmax, snapshot->xip, snapshot->xcnt))
+		return false;
+	/* xmax is between [xmin, xmax), but known not to have committed yet */
+	else
+		return true;
+}
+
+static 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;
+	}
+}
 
 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/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 52231ac417..74250688a8 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/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index f93c194e18..3118c2d33f 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -108,6 +108,7 @@
 #include "miscadmin.h"
 
 #include "access/heapam.h"
+#include "access/heapam_common.h"
 #include "access/heapam_xlog.h"
 #include "access/rewriteheap.h"
 #include "access/transam.h"
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/catalog/index.c b/src/backend/catalog/index.c
index c7b2f031f0..17be9a4042 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2222,6 +2222,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	TransactionId OldestXmin;
 	BlockNumber root_blkno = InvalidBlockNumber;
 	OffsetNumber root_offsets[MaxHeapTuplesPerPage];
+	StorageAmRoutine *method;
 
 	/*
 	 * sanity checks
@@ -2277,6 +2278,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 */
@@ -2357,8 +2359,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 760d19142e..247d9c2f56 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1083,9 +1083,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 6587db77ac..04729e5c27 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -355,7 +355,7 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
 									   params->log_min_duration))
 		{
 			StringInfoData buf;
-			char *msgfmt;
+			char	   *msgfmt;
 
 			TimestampDifference(starttime, endtime, &secs, &usecs);
 
@@ -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 b885f2a3a6..3e22c6baf6 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 0027d21253..dbc242c699 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -190,6 +190,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
  */
 static void
 ExecCheckHeapTupleVisible(EState *estate,
+						  Relation rel,
 						  HeapTuple tuple,
 						  Buffer buffer)
 {
@@ -201,7 +202,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
@@ -236,7 +237,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);
 }
 
@@ -1312,7 +1313,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);
@@ -1517,8 +1518,8 @@ ExecSetupTransitionCaptureState(ModifyTableState *mtstate, EState *estate)
 		if (mtstate->mt_partition_dispatch_info != NULL)
 		{
 			/*
-			 * For tuple routing among partitions, we need TupleDescs based
-			 * on the partition routing table.
+			 * For tuple routing among partitions, we need TupleDescs based on
+			 * the partition routing table.
 			 */
 			ResultRelInfo **resultRelInfos = mtstate->mt_partitions;
 
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 ba0e3ad87d..16e6755844 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -289,7 +289,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 addf87dc3b..ec8bc82d6e 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;
@@ -2048,7 +2048,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),
@@ -2145,7 +2145,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/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c
deleted file mode 100644
index b7aab0dd19..0000000000
--- a/src/backend/utils/time/tqual.c
+++ /dev/null
@@ -1,1807 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * tqual.c
- *	  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
- * has now committed or aborted (and it is safe to set the hint bits).
- * If the hint bits are changed, MarkBufferDirtyHint is called on
- * the passed-in buffer.  The caller must hold not only a pin, but at least
- * shared buffer content lock on the buffer containing the tuple.
- *
- * NOTE: When using a non-MVCC snapshot, we must check
- * TransactionIdIsInProgress (which looks in the PGXACT array)
- * before TransactionIdDidCommit/TransactionIdDidAbort (which look in
- * pg_xact).  Otherwise we have a race condition: we might decide that a
- * just-committed transaction crashed, because none of the tests succeed.
- * xact.c is careful to record commit/abort in pg_xact before it unsets
- * MyPgXact->xid in the PGXACT array.  That fixes that problem, but it
- * also means there is a window where TransactionIdIsInProgress and
- * TransactionIdDidCommit will both return true.  If we check only
- * TransactionIdDidCommit, we could consider a tuple committed when a
- * later GetSnapshotData call will still think the originating transaction
- * is in progress, which leads to application-level inconsistency.  The
- * upshot is that we gotta check TransactionIdIsInProgress first in all
- * code paths, except for a few cases where we are looking at
- * subtransactions of our own main transaction and so there can't be any
- * race condition.
- *
- * When using an MVCC snapshot, we rely on XidInMVCCSnapshot rather than
- * TransactionIdIsInProgress, but the logic is otherwise the same: do not
- * check pg_xact until after deciding that the xact is no longer in progress.
- *
- *
- * Summary of visibility functions:
- *
- *	 HeapTupleSatisfiesMVCC()
- *		  visible to supplied snapshot, excludes current command
- *	 HeapTupleSatisfiesUpdate()
- *		  visible to instant snapshot, with user-supplied command
- *		  counter and more complex result
- *	 HeapTupleSatisfiesSelf()
- *		  visible to instant snapshot and current command
- *	 HeapTupleSatisfiesDirty()
- *		  like HeapTupleSatisfiesSelf(), but includes open transactions
- *	 HeapTupleSatisfiesVacuum()
- *		  visible to any running transaction, used by VACUUM
- *	 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
- *		True iff heap tuple is valid "for itself".
- *
- *	Here, we consider the effects of:
- *		all committed transactions (as of the current instant)
- *		previous commands of this transaction
- *		changes made by the current command
- *
- * Note:
- *		Assumes heap tuple is valid.
- *
- * The satisfaction of "itself" requires the following:
- *
- * ((Xmin == my-transaction &&				the row was updated by the current transaction, and
- *		(Xmax is null						it was not deleted
- *		 [|| Xmax != my-transaction)])			[or it was deleted by another transaction]
- * ||
- *
- * (Xmin is committed &&					the row was modified by a committed transaction, and
- *		(Xmax is null ||					the row has not been deleted, or
- *			(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)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return false;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return false;
-			if (!TransactionIdIsInProgress(xvac))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (TransactionIdIsInProgress(xvac))
-					return false;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return true;
-
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
-				return true;
-
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleGetUpdateXid(tuple);
-
-				/* not LOCKED_ONLY, so it has to have an xmax */
-				Assert(TransactionIdIsValid(xmax));
-
-				/* updating subtransaction must have aborted */
-				if (!TransactionIdIsCurrentTransactionId(xmax))
-					return true;
-				else
-					return false;
-			}
-
-			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-			{
-				/* deleting subtransaction must have aborted */
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-				return true;
-			}
-
-			return false;
-		}
-		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
-			return false;
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return false;
-		}
-	}
-
-	/* by here, the inserting transaction has committed */
-
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
-		return true;
-
-	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-		return false;			/* updated by other */
-	}
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-
-		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 true;
-		if (TransactionIdDidCommit(xmax))
-			return false;
-		/* it must have aborted or crashed */
-		return true;
-	}
-
-	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-		return false;
-	}
-
-	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-		return true;
-
-	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		/* it must have aborted or crashed */
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return true;
-	}
-
-	/* xmax transaction committed */
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-	{
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return true;
-	}
-
-	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-				HeapTupleHeaderGetRawXmax(tuple));
-	return false;
-}
-
-/*
- * HeapTupleSatisfiesAny
- *		Dummy "satisfies" routine: any tuple satisfies SnapshotAny.
- */
-bool
-HeapTupleSatisfiesAny(HeapTuple htup, Snapshot snapshot, Buffer buffer)
-{
-	return true;
-}
-
-/*
- * HeapTupleSatisfiesToast
- *		True iff heap tuple is valid as a TOAST row.
- *
- * This is a simplified version that only checks for VACUUM moving conditions.
- * It's appropriate for TOAST usage because TOAST really doesn't want to do
- * its own time qual checks; if you can see the main table row that contains
- * a TOAST reference, you should be able to see the TOASTed value.  However,
- * vacuuming a TOAST table is independent of the main table, and in case such
- * a vacuum fails partway through, we'd better do this much checking.
- *
- * Among other things, this means you can't do UPDATEs of rows in a TOAST
- * table.
- */
-bool
-HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot,
-						Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return false;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return false;
-			if (!TransactionIdIsInProgress(xvac))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (TransactionIdIsInProgress(xvac))
-					return false;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-			}
-		}
-
-		/*
-		 * An invalid Xmin can be left behind by a speculative insertion that
-		 * is canceled by super-deleting the tuple.  This also applies to
-		 * TOAST tuples created during speculative insertion.
-		 */
-		else if (!TransactionIdIsValid(HeapTupleHeaderGetXmin(tuple)))
-			return false;
-	}
-
-	/* otherwise assume the tuple is valid for TOAST. */
-	return true;
-}
-
-/*
- * HeapTupleSatisfiesUpdate
- *
- *	This function returns a more detailed result code than most of the
- *	functions in this file, since UPDATE needs to know more than "is it
- *	visible?".  It also allows for user-supplied CommandId rather than
- *	relying on CurrentCommandId.
- *
- *	The possible return codes are:
- *
- *	HeapTupleInvisible: the tuple didn't exist at all when the scan started,
- *	e.g. it was created by a later CommandId.
- *
- *	HeapTupleMayBeUpdated: The tuple is valid and visible, so it may be
- *	updated.
- *
- *	HeapTupleSelfUpdated: The tuple was updated by the current transaction,
- *	after the current scan started.
- *
- *	HeapTupleUpdated: The tuple was updated by a committed transaction.
- *
- *	HeapTupleBeingUpdated: The tuple is being updated by an in-progress
- *	transaction other than the current transaction.  (Note: this includes
- *	the case where the tuple is share-locked by a MultiXact, even if the
- *	MultiXact includes the current transaction.  Callers that want to
- *	distinguish that case must test for it themselves.)
- */
-HTSU_Result
-HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
-						 Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return HeapTupleInvisible;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return HeapTupleInvisible;
-			if (!TransactionIdIsInProgress(xvac))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return HeapTupleInvisible;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (TransactionIdIsInProgress(xvac))
-					return HeapTupleInvisible;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return HeapTupleInvisible;
-				}
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (HeapTupleHeaderGetCmin(tuple) >= curcid)
-				return HeapTupleInvisible;	/* inserted after scan started */
-
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return HeapTupleMayBeUpdated;
-
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleHeaderGetRawXmax(tuple);
-
-				/*
-				 * Careful here: even though this tuple was created by our own
-				 * transaction, it might be locked by other transactions, if
-				 * the original version was key-share locked when we updated
-				 * it.
-				 */
-
-				if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-				{
-					if (MultiXactIdIsRunning(xmax, true))
-						return HeapTupleBeingUpdated;
-					else
-						return HeapTupleMayBeUpdated;
-				}
-
-				/*
-				 * If the locker is gone, then there is nothing of interest
-				 * left in this Xmax; otherwise, report the tuple as
-				 * locked/updated.
-				 */
-				if (!TransactionIdIsInProgress(xmax))
-					return HeapTupleMayBeUpdated;
-				return HeapTupleBeingUpdated;
-			}
-
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleGetUpdateXid(tuple);
-
-				/* not LOCKED_ONLY, so it has to have an xmax */
-				Assert(TransactionIdIsValid(xmax));
-
-				/* deleting subtransaction must have aborted */
-				if (!TransactionIdIsCurrentTransactionId(xmax))
-				{
-					if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
-											 false))
-						return HeapTupleBeingUpdated;
-					return HeapTupleMayBeUpdated;
-				}
-				else
-				{
-					if (HeapTupleHeaderGetCmax(tuple) >= curcid)
-						return HeapTupleSelfUpdated;	/* updated after scan
-														 * started */
-					else
-						return HeapTupleInvisible;	/* updated before scan
-													 * started */
-				}
-			}
-
-			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-			{
-				/* deleting subtransaction must have aborted */
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-				return HeapTupleMayBeUpdated;
-			}
-
-			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
-				return HeapTupleSelfUpdated;	/* updated after scan started */
-			else
-				return HeapTupleInvisible;	/* updated before scan started */
-		}
-		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
-			return HeapTupleInvisible;
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return HeapTupleInvisible;
-		}
-	}
-
-	/* by here, the inserting transaction has committed */
-
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
-		return HeapTupleMayBeUpdated;
-
-	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return HeapTupleMayBeUpdated;
-		return HeapTupleUpdated;	/* updated by other */
-	}
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		if (HEAP_LOCKED_UPGRADED(tuple->t_infomask))
-			return HeapTupleMayBeUpdated;
-
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-		{
-			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), true))
-				return HeapTupleBeingUpdated;
-
-			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
-			return HeapTupleMayBeUpdated;
-		}
-
-		xmax = HeapTupleGetUpdateXid(tuple);
-		if (!TransactionIdIsValid(xmax))
-		{
-			if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
-				return HeapTupleBeingUpdated;
-		}
-
-		/* not LOCKED_ONLY, so it has to have an xmax */
-		Assert(TransactionIdIsValid(xmax));
-
-		if (TransactionIdIsCurrentTransactionId(xmax))
-		{
-			if (HeapTupleHeaderGetCmax(tuple) >= curcid)
-				return HeapTupleSelfUpdated;	/* updated after scan started */
-			else
-				return HeapTupleInvisible;	/* updated before scan started */
-		}
-
-		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
-			return HeapTupleBeingUpdated;
-
-		if (TransactionIdDidCommit(xmax))
-			return HeapTupleUpdated;
-
-		/*
-		 * By here, the update in the Xmax is either aborted or crashed, but
-		 * what about the other members?
-		 */
-
-		if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
-		{
-			/*
-			 * There's no member, even just a locker, alive anymore, so we can
-			 * mark the Xmax as invalid.
-			 */
-			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-						InvalidTransactionId);
-			return HeapTupleMayBeUpdated;
-		}
-		else
-		{
-			/* There are lockers running */
-			return HeapTupleBeingUpdated;
-		}
-	}
-
-	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return HeapTupleBeingUpdated;
-		if (HeapTupleHeaderGetCmax(tuple) >= curcid)
-			return HeapTupleSelfUpdated;	/* updated after scan started */
-		else
-			return HeapTupleInvisible;	/* updated before scan started */
-	}
-
-	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-		return HeapTupleBeingUpdated;
-
-	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		/* it must have aborted or crashed */
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return HeapTupleMayBeUpdated;
-	}
-
-	/* xmax transaction committed */
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-	{
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return HeapTupleMayBeUpdated;
-	}
-
-	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-				HeapTupleHeaderGetRawXmax(tuple));
-	return HeapTupleUpdated;	/* updated by other */
-}
-
-/*
- * HeapTupleSatisfiesDirty
- *		True iff heap tuple is valid including effects of open transactions.
- *
- *	Here, we consider the effects of:
- *		all committed and in-progress transactions (as of the current instant)
- *		previous commands of this transaction
- *		changes made by the current command
- *
- * This is essentially like HeapTupleSatisfiesSelf as far as effects of
- * the current transaction and committed/aborted xacts are concerned.
- * However, we also include the effects of other xacts still in progress.
- *
- * A special hack is that the passed-in snapshot struct is used as an
- * output argument to return the xids of concurrent xacts that affected the
- * tuple.  snapshot->xmin is set to the tuple's xmin if that is another
- * transaction that's still in progress; or to InvalidTransactionId if the
- * tuple's xmin is committed good, committed dead, or my own xact.
- * Similarly for snapshot->xmax and the tuple's xmax.  If the tuple was
- * inserted speculatively, meaning that the inserter might still back down
- * on the insertion without aborting the whole transaction, the associated
- * token is also returned in snapshot->speculativeToken.
- */
-bool
-HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
-						Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	snapshot->xmin = snapshot->xmax = InvalidTransactionId;
-	snapshot->speculativeToken = 0;
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return false;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return false;
-			if (!TransactionIdIsInProgress(xvac))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (TransactionIdIsInProgress(xvac))
-					return false;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return true;
-
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
-				return true;
-
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleGetUpdateXid(tuple);
-
-				/* not LOCKED_ONLY, so it has to have an xmax */
-				Assert(TransactionIdIsValid(xmax));
-
-				/* updating subtransaction must have aborted */
-				if (!TransactionIdIsCurrentTransactionId(xmax))
-					return true;
-				else
-					return false;
-			}
-
-			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-			{
-				/* deleting subtransaction must have aborted */
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-				return true;
-			}
-
-			return false;
-		}
-		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			/*
-			 * Return the speculative token to caller.  Caller can worry about
-			 * xmax, since it requires a conclusively locked row version, and
-			 * a concurrent update to this tuple is a conflict of its
-			 * purposes.
-			 */
-			if (HeapTupleHeaderIsSpeculative(tuple))
-			{
-				snapshot->speculativeToken =
-					HeapTupleHeaderGetSpeculativeToken(tuple);
-
-				Assert(snapshot->speculativeToken != 0);
-			}
-
-			snapshot->xmin = HeapTupleHeaderGetRawXmin(tuple);
-			/* XXX shouldn't we fall through to look at xmax? */
-			return true;		/* in insertion by other */
-		}
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return false;
-		}
-	}
-
-	/* by here, the inserting transaction has committed */
-
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
-		return true;
-
-	if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-		return false;			/* updated by other */
-	}
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-
-		xmax = HeapTupleGetUpdateXid(tuple);
-
-		/* not LOCKED_ONLY, so it has to have an xmax */
-		Assert(TransactionIdIsValid(xmax));
-
-		if (TransactionIdIsCurrentTransactionId(xmax))
-			return false;
-		if (TransactionIdIsInProgress(xmax))
-		{
-			snapshot->xmax = xmax;
-			return true;
-		}
-		if (TransactionIdDidCommit(xmax))
-			return false;
-		/* it must have aborted or crashed */
-		return true;
-	}
-
-	if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			return true;
-		return false;
-	}
-
-	if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		if (!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-			snapshot->xmax = HeapTupleHeaderGetRawXmax(tuple);
-		return true;
-	}
-
-	if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-	{
-		/* it must have aborted or crashed */
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return true;
-	}
-
-	/* xmax transaction committed */
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-	{
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-					InvalidTransactionId);
-		return true;
-	}
-
-	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-				HeapTupleHeaderGetRawXmax(tuple));
-	return false;				/* updated by other */
-}
-
-/*
- * HeapTupleSatisfiesMVCC
- *		True iff heap tuple is valid for the given MVCC snapshot.
- *
- *	Here, we consider the effects of:
- *		all transactions committed as of the time of the given snapshot
- *		previous commands of this transaction
- *
- *	Does _not_ include:
- *		transactions shown as in-progress by the snapshot
- *		transactions started after the snapshot was taken
- *		changes made by the current command
- *
- * Notice that here, we will not update the tuple status hint bits if the
- * inserting/deleting transaction is still running according to our snapshot,
- * even if in reality it's committed or aborted by now.  This is intentional.
- * Checking the true transaction state would require access to high-traffic
- * shared data structures, creating contention we'd rather do without, and it
- * would not change the result of our visibility check anyway.  The hint bits
- * will be updated by the first visitor that has a snapshot new enough to see
- * the inserting/deleting transaction as done.  In the meantime, the cost of
- * leaving the hint bits unset is basically that each HeapTupleSatisfiesMVCC
- * call will need to run TransactionIdIsCurrentTransactionId in addition to
- * XidInMVCCSnapshot (but it would have to do the latter anyway).  In the old
- * coding where we tried to set the hint bits as soon as possible, we instead
- * did TransactionIdIsInProgress in each call --- to no avail, as long as the
- * inserting/deleting transaction was still running --- which was more cycles
- * and more contention on the PGXACT array.
- */
-bool
-HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
-					   Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return false;
-
-		/* Used by pre-9.0 binary upgrades */
-		if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return false;
-			if (!XidInMVCCSnapshot(xvac, snapshot))
-			{
-				if (TransactionIdDidCommit(xvac))
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			}
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (!TransactionIdIsCurrentTransactionId(xvac))
-			{
-				if (XidInMVCCSnapshot(xvac, snapshot))
-					return false;
-				if (TransactionIdDidCommit(xvac))
-					SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-								InvalidTransactionId);
-				else
-				{
-					SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-								InvalidTransactionId);
-					return false;
-				}
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (HeapTupleHeaderGetCmin(tuple) >= snapshot->curcid)
-				return false;	/* inserted after scan started */
-
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return true;
-
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))	/* not deleter */
-				return true;
-
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				TransactionId xmax;
-
-				xmax = HeapTupleGetUpdateXid(tuple);
-
-				/* not LOCKED_ONLY, so it has to have an xmax */
-				Assert(TransactionIdIsValid(xmax));
-
-				/* updating subtransaction must have aborted */
-				if (!TransactionIdIsCurrentTransactionId(xmax))
-					return true;
-				else if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
-					return true;	/* updated after scan started */
-				else
-					return false;	/* updated before scan started */
-			}
-
-			if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-			{
-				/* deleting subtransaction must have aborted */
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-				return true;
-			}
-
-			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
-				return true;	/* deleted after scan started */
-			else
-				return false;	/* deleted before scan started */
-		}
-		else if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
-			return false;
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return false;
-		}
-	}
-	else
-	{
-		/* xmin is committed, but maybe not according to our snapshot */
-		if (!HeapTupleHeaderXminFrozen(tuple) &&
-			XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
-			return false;		/* treat as still in progress */
-	}
-
-	/* by here, the inserting transaction has committed */
-
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
-		return true;
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-		return true;
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		/* already checked above */
-		Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
-
-		xmax = HeapTupleGetUpdateXid(tuple);
-
-		/* not LOCKED_ONLY, so it has to have an xmax */
-		Assert(TransactionIdIsValid(xmax));
-
-		if (TransactionIdIsCurrentTransactionId(xmax))
-		{
-			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
-				return true;	/* deleted after scan started */
-			else
-				return false;	/* deleted before scan started */
-		}
-		if (XidInMVCCSnapshot(xmax, snapshot))
-			return true;
-		if (TransactionIdDidCommit(xmax))
-			return false;		/* updating transaction committed */
-		/* it must have aborted or crashed */
-		return true;
-	}
-
-	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
-	{
-		if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
-		{
-			if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
-				return true;	/* deleted after scan started */
-			else
-				return false;	/* deleted before scan started */
-		}
-
-		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
-			return true;
-
-		if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-		{
-			/* it must have aborted or crashed */
-			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-						InvalidTransactionId);
-			return true;
-		}
-
-		/* xmax transaction committed */
-		SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-					HeapTupleHeaderGetRawXmax(tuple));
-	}
-	else
-	{
-		/* xmax is committed, but maybe not according to our snapshot */
-		if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
-			return true;		/* treat as still in progress */
-	}
-
-	/* xmax transaction committed */
-
-	return false;
-}
-
-
-/*
- * HeapTupleSatisfiesVacuum
- *
- *	Determine the status of tuples for VACUUM purposes.  Here, what
- *	we mainly want to know is if a tuple is potentially visible to *any*
- *	running transaction.  If so, it can't be removed yet by VACUUM.
- *
- * OldestXmin is a cutoff XID (obtained from GetOldestXmin()).  Tuples
- * deleted by XIDs >= OldestXmin are deemed "recently dead"; they might
- * still be visible to some open transaction, so we can't remove them,
- * even if we see that the deleting transaction has committed.
- */
-HTSV_Result
-HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
-						 Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	/*
-	 * Has inserting transaction committed?
-	 *
-	 * If the inserting transaction aborted, then the tuple was never visible
-	 * to any other transaction, so we can delete it immediately.
-	 */
-	if (!HeapTupleHeaderXminCommitted(tuple))
-	{
-		if (HeapTupleHeaderXminInvalid(tuple))
-			return HEAPTUPLE_DEAD;
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_OFF)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return HEAPTUPLE_DELETE_IN_PROGRESS;
-			if (TransactionIdIsInProgress(xvac))
-				return HEAPTUPLE_DELETE_IN_PROGRESS;
-			if (TransactionIdDidCommit(xvac))
-			{
-				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-							InvalidTransactionId);
-				return HEAPTUPLE_DEAD;
-			}
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						InvalidTransactionId);
-		}
-		/* Used by pre-9.0 binary upgrades */
-		else if (tuple->t_infomask & HEAP_MOVED_IN)
-		{
-			TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
-
-			if (TransactionIdIsCurrentTransactionId(xvac))
-				return HEAPTUPLE_INSERT_IN_PROGRESS;
-			if (TransactionIdIsInProgress(xvac))
-				return HEAPTUPLE_INSERT_IN_PROGRESS;
-			if (TransactionIdDidCommit(xvac))
-				SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-							InvalidTransactionId);
-			else
-			{
-				SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-							InvalidTransactionId);
-				return HEAPTUPLE_DEAD;
-			}
-		}
-		else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid */
-				return HEAPTUPLE_INSERT_IN_PROGRESS;
-			/* only locked? run infomask-only check first, for performance */
-			if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask) ||
-				HeapTupleHeaderIsOnlyLocked(tuple))
-				return HEAPTUPLE_INSERT_IN_PROGRESS;
-			/* inserted and then deleted by same xact */
-			if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple)))
-				return HEAPTUPLE_DELETE_IN_PROGRESS;
-			/* deleting subtransaction must have aborted */
-			return HEAPTUPLE_INSERT_IN_PROGRESS;
-		}
-		else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
-		{
-			/*
-			 * It'd be possible to discern between INSERT/DELETE in progress
-			 * here by looking at xmax - but that doesn't seem beneficial for
-			 * the majority of callers and even detrimental for some. We'd
-			 * rather have callers look at/wait for xmin than xmax. It's
-			 * always correct to return INSERT_IN_PROGRESS because that's
-			 * what's happening from the view of other backends.
-			 */
-			return HEAPTUPLE_INSERT_IN_PROGRESS;
-		}
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
-						HeapTupleHeaderGetRawXmin(tuple));
-		else
-		{
-			/*
-			 * Not in Progress, Not Committed, so either Aborted or crashed
-			 */
-			SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
-						InvalidTransactionId);
-			return HEAPTUPLE_DEAD;
-		}
-
-		/*
-		 * At this point the xmin is known committed, but we might not have
-		 * been able to set the hint bit yet; so we can no longer Assert that
-		 * it's set.
-		 */
-	}
-
-	/*
-	 * Okay, the inserter committed, so it was good at some point.  Now what
-	 * about the deleting transaction?
-	 */
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)
-		return HEAPTUPLE_LIVE;
-
-	if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-	{
-		/*
-		 * "Deleting" xact really only locked it, so the tuple is live in any
-		 * case.  However, we should make sure that either XMAX_COMMITTED or
-		 * XMAX_INVALID gets set once the xact is gone, to reduce the costs of
-		 * examining the tuple for future xacts.
-		 */
-		if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
-		{
-			if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				/*
-				 * If it's a pre-pg_upgrade tuple, the multixact cannot
-				 * possibly be running; otherwise have to check.
-				 */
-				if (!HEAP_LOCKED_UPGRADED(tuple->t_infomask) &&
-					MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple),
-										 true))
-					return HEAPTUPLE_LIVE;
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
-			}
-			else
-			{
-				if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-					return HEAPTUPLE_LIVE;
-				SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-							InvalidTransactionId);
-			}
-		}
-
-		/*
-		 * We don't really care whether xmax did commit, abort or crash. We
-		 * know that xmax did lock the tuple, but it did not and will never
-		 * actually update it.
-		 */
-
-		return HEAPTUPLE_LIVE;
-	}
-
-	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		TransactionId xmax;
-
-		if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
-		{
-			/* already checked above */
-			Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
-
-			xmax = HeapTupleGetUpdateXid(tuple);
-
-			/* not LOCKED_ONLY, so it has to have an xmax */
-			Assert(TransactionIdIsValid(xmax));
-
-			if (TransactionIdIsInProgress(xmax))
-				return HEAPTUPLE_DELETE_IN_PROGRESS;
-			else if (TransactionIdDidCommit(xmax))
-				/* there are still lockers around -- can't return DEAD here */
-				return HEAPTUPLE_RECENTLY_DEAD;
-			/* updating transaction aborted */
-			return HEAPTUPLE_LIVE;
-		}
-
-		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED));
-
-		xmax = HeapTupleGetUpdateXid(tuple);
-
-		/* not LOCKED_ONLY, so it has to have an xmax */
-		Assert(TransactionIdIsValid(xmax));
-
-		/* multi is not running -- updating xact cannot be */
-		Assert(!TransactionIdIsInProgress(xmax));
-		if (TransactionIdDidCommit(xmax))
-		{
-			if (!TransactionIdPrecedes(xmax, OldestXmin))
-				return HEAPTUPLE_RECENTLY_DEAD;
-			else
-				return HEAPTUPLE_DEAD;
-		}
-
-		/*
-		 * Not in Progress, Not Committed, so either Aborted or crashed.
-		 * Remove the Xmax.
-		 */
-		SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
-		return HEAPTUPLE_LIVE;
-	}
-
-	if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
-	{
-		if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
-			return HEAPTUPLE_DELETE_IN_PROGRESS;
-		else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
-			SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
-						HeapTupleHeaderGetRawXmax(tuple));
-		else
-		{
-			/*
-			 * Not in Progress, Not Committed, so either Aborted or crashed
-			 */
-			SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
-						InvalidTransactionId);
-			return HEAPTUPLE_LIVE;
-		}
-
-		/*
-		 * At this point the xmax is known committed, but we might not have
-		 * been able to set the hint bit yet; so we can no longer Assert that
-		 * it's set.
-		 */
-	}
-
-	/*
-	 * Deleter committed, but perhaps it was recent enough that some open
-	 * transactions could still see the tuple.
-	 */
-	if (!TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin))
-		return HEAPTUPLE_RECENTLY_DEAD;
-
-	/* Otherwise, it's dead and removable */
-	return HEAPTUPLE_DEAD;
-}
-
-
-/*
- * HeapTupleSatisfiesNonVacuumable
- *
- *	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.
- */
-bool
-HeapTupleSatisfiesNonVacuumable(HeapTuple 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'.
- */
-static bool
-TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num)
-{
-	return bsearch(&xid, xip, num,
-				   sizeof(TransactionId), xidComparator) != NULL;
-}
-
-/*
- * See the comments for HeapTupleSatisfiesMVCC for the semantics this function
- * obeys.
- *
- * Only usable on tuples from catalog tables!
- *
- * We don't need to support HEAP_MOVED_(IN|OFF) for now because we only support
- * reading catalog pages which couldn't have been created in an older version.
- *
- * We don't set any hint bits in here as it seems unlikely to be beneficial as
- * those should already be set by normal access and it seems to be too
- * 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,
-							   Buffer buffer)
-{
-	HeapTupleHeader tuple = htup->t_data;
-	TransactionId xmin = HeapTupleHeaderGetXmin(tuple);
-	TransactionId xmax = HeapTupleHeaderGetRawXmax(tuple);
-
-	Assert(ItemPointerIsValid(&htup->t_self));
-	Assert(htup->t_tableOid != InvalidOid);
-
-	/* inserting transaction aborted */
-	if (HeapTupleHeaderXminInvalid(tuple))
-	{
-		Assert(!TransactionIdDidCommit(xmin));
-		return false;
-	}
-	/* check if it's one of our txids, toplevel is also in there */
-	else if (TransactionIdInArray(xmin, snapshot->subxip, snapshot->subxcnt))
-	{
-		bool		resolved;
-		CommandId	cmin = HeapTupleHeaderGetRawCommandId(tuple);
-		CommandId	cmax = InvalidCommandId;
-
-		/*
-		 * another transaction might have (tried to) delete this tuple or
-		 * cmin/cmax was stored in a combocid. So we need to lookup the actual
-		 * values externally.
-		 */
-		resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot,
-												 htup, buffer,
-												 &cmin, &cmax);
-
-		if (!resolved)
-			elog(ERROR, "could not resolve cmin/cmax of catalog tuple");
-
-		Assert(cmin != InvalidCommandId);
-
-		if (cmin >= snapshot->curcid)
-			return false;		/* inserted after scan started */
-		/* fall through */
-	}
-	/* committed before our xmin horizon. Do a normal visibility check. */
-	else if (TransactionIdPrecedes(xmin, snapshot->xmin))
-	{
-		Assert(!(HeapTupleHeaderXminCommitted(tuple) &&
-				 !TransactionIdDidCommit(xmin)));
-
-		/* check for hint bit first, consult clog afterwards */
-		if (!HeapTupleHeaderXminCommitted(tuple) &&
-			!TransactionIdDidCommit(xmin))
-			return false;
-		/* fall through */
-	}
-	/* beyond our xmax horizon, i.e. invisible */
-	else if (TransactionIdFollowsOrEquals(xmin, snapshot->xmax))
-	{
-		return false;
-	}
-	/* check if it's a committed transaction in [xmin, xmax) */
-	else if (TransactionIdInArray(xmin, snapshot->xip, snapshot->xcnt))
-	{
-		/* fall through */
-	}
-
-	/*
-	 * none of the above, i.e. between [xmin, xmax) but hasn't committed. I.e.
-	 * invisible.
-	 */
-	else
-	{
-		return false;
-	}
-
-	/* at this point we know xmin is visible, go on to check xmax */
-
-	/* xid invalid or aborted */
-	if (tuple->t_infomask & HEAP_XMAX_INVALID)
-		return true;
-	/* locked tuples are always visible */
-	else if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
-		return true;
-
-	/*
-	 * We can see multis here if we're looking at user tables or if somebody
-	 * SELECT ... FOR SHARE/UPDATE a system table.
-	 */
-	else if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		xmax = HeapTupleGetUpdateXid(tuple);
-	}
-
-	/* check if it's one of our txids, toplevel is also in there */
-	if (TransactionIdInArray(xmax, snapshot->subxip, snapshot->subxcnt))
-	{
-		bool		resolved;
-		CommandId	cmin;
-		CommandId	cmax = HeapTupleHeaderGetRawCommandId(tuple);
-
-		/* Lookup actual cmin/cmax values */
-		resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot,
-												 htup, buffer,
-												 &cmin, &cmax);
-
-		if (!resolved)
-			elog(ERROR, "could not resolve combocid to cmax");
-
-		Assert(cmax != InvalidCommandId);
-
-		if (cmax >= snapshot->curcid)
-			return true;		/* deleted after scan started */
-		else
-			return false;		/* deleted before scan started */
-	}
-	/* below xmin horizon, normal transaction state is valid */
-	else if (TransactionIdPrecedes(xmax, snapshot->xmin))
-	{
-		Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED &&
-				 !TransactionIdDidCommit(xmax)));
-
-		/* check hint bit first */
-		if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
-			return false;
-
-		/* check clog */
-		return !TransactionIdDidCommit(xmax);
-	}
-	/* above xmax horizon, we cannot possibly see the deleting transaction */
-	else if (TransactionIdFollowsOrEquals(xmax, snapshot->xmax))
-		return true;
-	/* xmax is between [xmin, xmax), check known committed array */
-	else if (TransactionIdInArray(xmax, snapshot->xip, snapshot->xcnt))
-		return false;
-	/* xmax is between [xmin, xmax), but known not to have committed yet */
-	else
-		return true;
-}
diff --git a/src/include/access/heapam_common.h b/src/include/access/heapam_common.h
new file mode 100644
index 0000000000..ff63cf3615
--- /dev/null
+++ b/src/include/access/heapam_common.h
@@ -0,0 +1,96 @@
+/*-------------------------------------------------------------------------
+ *
+ * heapam_shared.h
+ *	  POSTGRES heap access method definitions shared across
+ *	  server and heapam methods.
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/heapam_shared.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef HEAPAM_SHARED_H
+#define HEAPAM_SHARED_H
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/sdir.h"
+#include "access/skey.h"
+#include "access/transam.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "nodes/lockoptions.h"
+#include "nodes/primnodes.h"
+#include "storage/bufpage.h"
+#include "storage/bufmgr.h"
+#include "storage/lockdefs.h"
+#include "storage/lmgr.h"
+#include "utils/relcache.h"
+#include "utils/snapshot.h"
+
+
+/* in heap/heapam_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);
+
+/*
+ * 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							/* HEAPAM_H */
diff --git a/src/include/access/htup.h b/src/include/access/htup.h
index 6459435c78..a5268242d7 100644
--- a/src/include/access/htup.h
+++ b/src/include/access/htup.h
@@ -28,15 +28,16 @@ typedef MinimalTupleData *MinimalTuple;
 
 typedef enum tuple_visibility_type
 {
-	MVCC_VISIBILITY = 0, 		/* HeapTupleSatisfiesMVCC */
-	SELF_VISIBILITY,				/* HeapTupleSatisfiesSelf */
+	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;
+}			tuple_visibility_type;
 
 /* Result codes for HeapTupleSatisfiesVacuum */
 typedef enum
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 98b63fc5ba..b1c94de683 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -14,13 +14,13 @@
 #ifndef BUFMGR_H
 #define BUFMGR_H
 
+#include "access/storageamapi.h"
 #include "storage/block.h"
 #include "storage/buf.h"
 #include "storage/bufpage.h"
 #include "storage/relfilenode.h"
 #include "utils/relcache.h"
 #include "utils/snapmgr.h"
-#include "utils/tqual.h"
 
 typedef void *Block;
 
@@ -268,8 +268,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..5752ee4731 100644
--- a/src/include/utils/snapshot.h
+++ b/src/include/utils/snapshot.h
@@ -52,7 +52,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 4fda7f8384..3deeb71970 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,64 +43,25 @@ 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))
+#define HeapTupleSatisfiesVisibility(method, tuple, snapshot, buffer) \
+	(((method)->snapshot_satisfies) (tuple, snapshot, buffer))
 
-
-/* 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);
-
-/*
- * To avoid leaking too much knowledge about reorderbuffer implementation
- * details this is implemented in reorderbuffer.c not tqual.c.
- */
-struct HTAB;
-extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data,
-							  Snapshot snapshot,
-							  HeapTuple htup,
-							  Buffer buffer,
-							  CommandId *cmin, CommandId *cmax);
-
 /*
  * We don't provide a static SnapshotDirty variable because it would be
  * non-reentrant.  Instead, users of that snapshot type should declare a
  * 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))
 
 /*
@@ -107,8 +69,19 @@ 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))
 
+/*
+ * To avoid leaking too much knowledge about reorderbuffer implementation
+ * details this is implemented in reorderbuffer.c not tqual.c.
+ */
+struct HTAB;
+extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data,
+							  Snapshot snapshot,
+							  HeapTuple htup,
+							  Buffer buffer,
+							  CommandId *cmin, CommandId *cmax);
+
 #endif							/* TQUAL_H */
-- 
2.14.2.windows.1

0005-slot-hooks-are-added-to-storage-AM.patchapplication/octet-stream; name=0005-slot-hooks-are-added-to-storage-AM.patchDownload
From 635ad38523e294cb5a4b659d8032149f784df9e0 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Fri, 27 Oct 2017 16:02:46 +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_common.c  | 404 +++++++++++++++++++++++++++++++
 src/backend/access/heap/heapam_storage.c |   5 +-
 src/backend/commands/copy.c              |   8 +-
 src/backend/commands/createas.c          |   2 +-
 src/backend/commands/matview.c           |   2 +-
 src/backend/commands/trigger.c           |  25 +-
 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/heapam_common.h       |   2 +
 src/include/access/htup_details.h        |  15 +-
 src/include/executor/tuptable.h          |  54 +++--
 17 files changed, 660 insertions(+), 585 deletions(-)

diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 13ee528e26..aabb4f0944 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_common.c b/src/backend/access/heap/heapam_common.c
index 4cc3eeff0c..6ebe0b290c 100644
--- a/src/backend/access/heap/heapam_common.c
+++ b/src/backend/access/heap/heapam_common.c
@@ -100,6 +100,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/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index f7e11d4946..a874e243c9 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -1108,7 +1108,6 @@ HeapTupleSatisfiesMVCC(StorageTuple stup, Snapshot snapshot,
 	return false;
 }
 
-
 /*
  * HeapTupleSatisfiesVacuum
  *
@@ -1695,8 +1694,10 @@ heapam_storage_handler(PG_FUNCTION_ARGS)
 	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
 
 	amroutine->snapshot_satisfies = HeapTupleSatisfies;
-
 	amroutine->snapshot_satisfiesUpdate = HeapTupleSatisfiesUpdate;
 	amroutine->snapshot_satisfiesVacuum = HeapTupleSatisfiesVacuum;
+
+	amroutine->slot_storageam = heapam_storage_slot_handler;
+
 	PG_RETURN_POINTER(amroutine);
 }
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 1bdd4927d9..96cda7091b 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -167,7 +167,7 @@ typedef struct CopyStateData
 	PartitionDispatch *partition_dispatch_info;
 	int			num_dispatch;	/* Number of entries in the above array */
 	int			num_partitions; /* Number of members in the following arrays */
-	ResultRelInfo **partitions;	/* Per partition result relation pointers */
+	ResultRelInfo **partitions; /* Per partition result relation pointers */
 	TupleConversionMap **partition_tupconv_maps;
 	TupleTableSlot *partition_tuple_slot;
 	TransitionCaptureState *transition_capture;
@@ -359,7 +359,7 @@ SendCopyBegin(CopyState cstate)
 		pq_sendbyte(&buf, format);	/* overall format */
 		pq_sendint16(&buf, natts);
 		for (i = 0; i < natts; i++)
-			pq_sendint16(&buf, format);	/* per-column formats */
+			pq_sendint16(&buf, format); /* per-column formats */
 		pq_endmessage(&buf);
 		cstate->copy_dest = COPY_NEW_FE;
 	}
@@ -392,7 +392,7 @@ ReceiveCopyBegin(CopyState cstate)
 		pq_sendbyte(&buf, format);	/* overall format */
 		pq_sendint16(&buf, natts);
 		for (i = 0; i < natts; i++)
-			pq_sendint16(&buf, format);	/* per-column formats */
+			pq_sendint16(&buf, format); /* per-column formats */
 		pq_endmessage(&buf);
 		cstate->copy_dest = COPY_NEW_FE;
 		cstate->fe_msgbuf = makeStringInfo();
@@ -2717,7 +2717,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 8d0345cd64..005bdbd023 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;
@@ -3608,9 +3608,9 @@ static void AfterTriggerExecute(AfterTriggerEvent event,
 					MemoryContext per_tuple_context,
 					TupleTableSlot *trig_tuple_slot1,
 					TupleTableSlot *trig_tuple_slot2);
-static AfterTriggersTableData *GetAfterTriggersTableData(Oid relid,
-						  CmdType cmdType);
-static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs);
+static AfterTriggersTableData * GetAfterTriggersTableData(Oid relid,
+														  CmdType cmdType);
+static void AfterTriggerFreeQuery(AfterTriggersQueryData * qs);
 static SetConstraintState SetConstraintStateCreate(int numalloc);
 static SetConstraintState SetConstraintStateCopy(SetConstraintState state);
 static SetConstraintState SetConstraintStateAddItem(SetConstraintState state,
@@ -3882,7 +3882,7 @@ afterTriggerRestoreEventList(AfterTriggerEventList *events,
  * ----------
  */
 static void
-afterTriggerDeleteHeadEventChunk(AfterTriggersQueryData *qs)
+afterTriggerDeleteHeadEventChunk(AfterTriggersQueryData * qs)
 {
 	AfterTriggerEventChunk *target = qs->events.head;
 	ListCell   *lc;
@@ -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;
@@ -4634,7 +4633,7 @@ AfterTriggerEndQuery(EState *estate)
  *	and then called again for the same query level.
  */
 static void
-AfterTriggerFreeQuery(AfterTriggersQueryData *qs)
+AfterTriggerFreeQuery(AfterTriggersQueryData * qs)
 {
 	Tuplestorestate *ts;
 	List	   *tables;
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index a0f537b706..a7d20bc391 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 fb538c0297..07640a9992 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..93aeb3eb6e 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -81,6 +81,7 @@
  */
 #include "postgres.h"
 
+#include "access/heapam_common.h"
 #include "access/htup_details.h"
 #include "access/tuptoaster.h"
 #include "funcapi.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 20892d6d5f..02f6c816aa 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 dbc242c699..ec5c543bdd 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -171,7 +171,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;
@@ -271,7 +271,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
@@ -406,7 +406,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -419,7 +419,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		newId = InvalidOid;
 	}
@@ -437,7 +437,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
@@ -746,7 +746,7 @@ ExecDelete(ModifyTableState *mtstate,
 		 */
 		if (slot->tts_isempty)
 			ExecStoreAllNullTuple(slot);
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
 	}
 	else
@@ -897,7 +897,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))
@@ -958,7 +958,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
@@ -977,7 +977,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW UPDATE Triggers */
@@ -991,7 +991,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
@@ -1007,7 +1007,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
@@ -1123,7 +1123,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 e9a5d5a1a5..9a47276274 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 0e68670767..1e4f9be0d7 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -727,9 +727,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/heapam_common.h b/src/include/access/heapam_common.h
index ff63cf3615..feb35c5024 100644
--- a/src/include/access/heapam_common.h
+++ b/src/include/access/heapam_common.h
@@ -40,6 +40,8 @@ extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
 					 uint16 infomask, TransactionId xid);
 extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
 extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin);
+typedef struct StorageSlotAmRoutine StorageSlotAmRoutine;
+extern StorageSlotAmRoutine * heapam_storage_slot_handler(void);
 
 /*
  * SetHintBits()
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/executor/tuptable.h b/src/include/executor/tuptable.h
index 55f4cce4ee..a730f265dd 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.14.2.windows.1

0006-Tuple-Insert-API-is-added-to-Storage-AM.patchapplication/octet-stream; name=0006-Tuple-Insert-API-is-added-to-Storage-AM.patchDownload
From c575e7ae8bc8d3e563b03e76f611273ce70de212 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Tue, 7 Nov 2017 12:06:12 +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/Makefile         |    2 +-
 src/backend/access/heap/heapam.c         | 2859 ++++--------------------------
 src/backend/access/heap/heapam_common.c  |    2 +
 src/backend/access/heap/heapam_storage.c | 2203 ++++++++++++++++++++++-
 src/backend/access/heap/rewriteheap.c    |    5 +-
 src/backend/access/heap/storageam.c      |  281 +++
 src/backend/access/heap/tuptoaster.c     |    9 +-
 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           |   49 +-
 src/backend/executor/execIndexing.c      |    2 +-
 src/backend/executor/execMain.c          |  130 +-
 src/backend/executor/execReplication.c   |   72 +-
 src/backend/executor/execTuples.c        |    1 +
 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              |   26 +-
 src/include/access/heapam_common.h       |  127 ++
 src/include/access/htup_details.h        |    1 +
 src/include/access/storageam.h           |   86 +
 src/include/access/storageamapi.h        |   34 +-
 src/include/commands/trigger.h           |    2 +-
 src/include/executor/executor.h          |   15 +-
 src/include/executor/tuptable.h          |    1 +
 src/include/nodes/execnodes.h            |    6 +-
 src/include/storage/bufmgr.h             |    1 -
 31 files changed, 3457 insertions(+), 2891 deletions(-)
 create mode 100644 src/backend/access/heap/storageam.c
 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 aabb4f0944..a66eed8d3b 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/Makefile b/src/backend/access/heap/Makefile
index e6bc18e5ea..162736ff15 100644
--- a/src/backend/access/heap/Makefile
+++ b/src/backend/access/heap/Makefile
@@ -13,7 +13,7 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = heapam.o heapam_common.o heapam_storage.o hio.o \
-	pruneheap.o rewriteheap.o storageamapi.o \
+	pruneheap.o rewriteheap.o storageam.o storageamapi.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 f62f04e185..d91452963a 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -94,8 +94,6 @@ static HeapScanDesc heap_beginscan_internal(Relation relation,
 						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,
-					TransactionId xid, CommandId cid, int options);
 static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 				Buffer newbuf, HeapTuple oldtup,
 				HeapTuple newtup, HeapTuple old_key_tup,
@@ -103,108 +101,17 @@ static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
 static Bitmapset *HeapDetermineModifiedColumns(Relation relation,
 							 Bitmapset *interesting_cols,
 							 HeapTuple oldtup, HeapTuple newtup);
-static bool heap_acquire_tuplock(Relation relation, ItemPointer tid,
-					 LockTupleMode mode, LockWaitPolicy wait_policy,
-					 bool *have_tuple_lock);
-static void compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
-						  uint16 old_infomask2, TransactionId add_to_xmax,
-						  LockTupleMode mode, bool is_update,
-						  TransactionId *result_xmax, uint16 *result_infomask,
-						  uint16 *result_infomask2);
-static HTSU_Result heap_lock_updated_tuple(Relation rel, HeapTuple tuple,
-						ItemPointer ctid, TransactionId xid,
-						LockTupleMode mode);
 static void GetMultiXactIdHintBits(MultiXactId multi, uint16 *new_infomask,
 					   uint16 *new_infomask2);
 static TransactionId MultiXactIdGetUpdateXid(TransactionId xmax,
 						uint16 t_infomask);
-static bool DoesMultiXactIdConflict(MultiXactId multi, uint16 infomask,
-						LockTupleMode lockmode);
-static void MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
-				Relation rel, ItemPointer ctid, XLTW_Oper oper,
-				int *remaining);
-static bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
-						   uint16 infomask, Relation rel, int *remaining);
-static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
+static bool Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
+				   uint16 infomask, bool nowait,
+				   Relation rel, ItemPointer ctid, XLTW_Oper oper,
+				   int *remaining);
 static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_modified,
 					   bool *copy);
 
-
-/*
- * Each tuple lock mode has a corresponding heavyweight lock, and one or two
- * corresponding MultiXactStatuses (one to merely lock tuples, another one to
- * update them).  This table (and the macros below) helps us determine the
- * heavyweight lock mode and MultiXactStatus values to use for any particular
- * tuple lock strength.
- *
- * Don't look at lockstatus/updstatus directly!  Use get_mxact_status_for_lock
- * instead.
- */
-static const struct
-{
-	LOCKMODE	hwlock;
-	int			lockstatus;
-	int			updstatus;
-}
-
-			tupleLockExtraInfo[MaxLockTupleMode + 1] =
-{
-	{							/* LockTupleKeyShare */
-		AccessShareLock,
-		MultiXactStatusForKeyShare,
-		-1						/* KeyShare does not allow updating tuples */
-	},
-	{							/* LockTupleShare */
-		RowShareLock,
-		MultiXactStatusForShare,
-		-1						/* Share does not allow updating tuples */
-	},
-	{							/* LockTupleNoKeyExclusive */
-		ExclusiveLock,
-		MultiXactStatusForNoKeyUpdate,
-		MultiXactStatusNoKeyUpdate
-	},
-	{							/* LockTupleExclusive */
-		AccessExclusiveLock,
-		MultiXactStatusForUpdate,
-		MultiXactStatusUpdate
-	}
-};
-
-/* Get the LOCKMODE for a given MultiXactStatus */
-#define LOCKMODE_from_mxstatus(status) \
-			(tupleLockExtraInfo[TUPLOCK_from_mxstatus((status))].hwlock)
-
-/*
- * Acquire heavyweight locks on tuples, using a LockTupleMode strength value.
- * This is more readable than having every caller translate it to lock.h's
- * LOCKMODE.
- */
-#define LockTupleTuplock(rel, tup, mode) \
-	LockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
-#define UnlockTupleTuplock(rel, tup, mode) \
-	UnlockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
-#define ConditionalLockTupleTuplock(rel, tup, mode) \
-	ConditionalLockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
-
-/*
- * This table maps tuple lock strength values for each particular
- * MultiXactStatus value.
- */
-static const int MultiXactStatusLock[MaxMultiXactStatus + 1] =
-{
-	LockTupleKeyShare,			/* ForKeyShare */
-	LockTupleShare,				/* ForShare */
-	LockTupleNoKeyExclusive,	/* ForNoKeyUpdate */
-	LockTupleExclusive,			/* ForUpdate */
-	LockTupleNoKeyExclusive,	/* NoKeyUpdate */
-	LockTupleExclusive			/* Update */
-};
-
-/* Get the LockTupleMode for a given MultiXactStatus */
-#define TUPLOCK_from_mxstatus(status) \
-			(MultiXactStatusLock[(status)])
-
 /* ----------------------------------------------------------------
  *						 heap support routines
  * ----------------------------------------------------------------
@@ -1854,158 +1761,6 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 	return heap_copytuple(&(scan->rs_ctup));
 }
 
-/*
- *	heap_fetch		- retrieve tuple with given tid
- *
- * On entry, tuple->t_self is the TID to fetch.  We pin the buffer holding
- * the tuple, fill in the remaining fields of *tuple, and check the tuple
- * against the specified snapshot.
- *
- * If successful (tuple found and passes snapshot time qual), then *userbuf
- * is set to the buffer holding the tuple and TRUE is returned.  The caller
- * must unpin the buffer when done with the tuple.
- *
- * If the tuple is not found (ie, item number references a deleted slot),
- * then tuple->t_data is set to NULL and FALSE is returned.
- *
- * If the tuple is found but fails the time qual check, then FALSE is returned
- * but tuple->t_data is left pointing to the tuple.
- *
- * keep_buf determines what is done with the buffer in the FALSE-result cases.
- * When the caller specifies keep_buf = true, we retain the pin on the buffer
- * and return it in *userbuf (so the caller must eventually unpin it); when
- * keep_buf = false, the pin is released and *userbuf is set to InvalidBuffer.
- *
- * stats_relation is the relation to charge the heap_fetch operation against
- * for statistical purposes.  (This could be the heap rel itself, an
- * associated index, or NULL to not count the fetch at all.)
- *
- * heap_fetch does not follow HOT chains: only the exact TID requested will
- * be fetched.
- *
- * It is somewhat inconsistent that we ereport() on invalid block number but
- * return false on invalid item number.  There are a couple of reasons though.
- * One is that the caller can relatively easily check the block number for
- * validity, but cannot check the item number without reading the page
- * himself.  Another is that when we are following a t_ctid link, we can be
- * reasonably confident that the page number is valid (since VACUUM shouldn't
- * truncate off the destination page without having killed the referencing
- * tuple first), but the item number might well not be good.
- */
-bool
-heap_fetch(Relation relation,
-		   Snapshot snapshot,
-		   HeapTuple tuple,
-		   Buffer *userbuf,
-		   bool keep_buf,
-		   Relation stats_relation)
-{
-	ItemPointer tid = &(tuple->t_self);
-	ItemId		lp;
-	Buffer		buffer;
-	Page		page;
-	OffsetNumber offnum;
-	bool		valid;
-
-	/*
-	 * Fetch and pin the appropriate page of the relation.
-	 */
-	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
-
-	/*
-	 * Need share lock on buffer to examine tuple commit status.
-	 */
-	LockBuffer(buffer, BUFFER_LOCK_SHARE);
-	page = BufferGetPage(buffer);
-	TestForOldSnapshot(snapshot, relation, page);
-
-	/*
-	 * We'd better check for out-of-range offnum in case of VACUUM since the
-	 * TID was obtained.
-	 */
-	offnum = ItemPointerGetOffsetNumber(tid);
-	if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
-	{
-		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-		if (keep_buf)
-			*userbuf = buffer;
-		else
-		{
-			ReleaseBuffer(buffer);
-			*userbuf = InvalidBuffer;
-		}
-		tuple->t_data = NULL;
-		return false;
-	}
-
-	/*
-	 * get the item line pointer corresponding to the requested tid
-	 */
-	lp = PageGetItemId(page, offnum);
-
-	/*
-	 * Must check for deleted tuple.
-	 */
-	if (!ItemIdIsNormal(lp))
-	{
-		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-		if (keep_buf)
-			*userbuf = buffer;
-		else
-		{
-			ReleaseBuffer(buffer);
-			*userbuf = InvalidBuffer;
-		}
-		tuple->t_data = NULL;
-		return false;
-	}
-
-	/*
-	 * fill in *tuple fields
-	 */
-	tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	tuple->t_len = ItemIdGetLength(lp);
-	tuple->t_tableOid = RelationGetRelid(relation);
-
-	/*
-	 * check time qualification of tuple, then release lock
-	 */
-	valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, tuple, snapshot, buffer);
-
-	if (valid)
-		PredicateLockTuple(relation, tuple, snapshot);
-
-	CheckForSerializableConflictOut(valid, relation, tuple, buffer, snapshot);
-
-	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
-
-	if (valid)
-	{
-		/*
-		 * All checks passed, so return the tuple as valid. Caller is now
-		 * responsible for releasing the buffer.
-		 */
-		*userbuf = buffer;
-
-		/* Count the successful fetch against appropriate rel, if any */
-		if (stats_relation != NULL)
-			pgstat_count_heap_fetch(stats_relation);
-
-		return true;
-	}
-
-	/* Tuple failed time qual, but maybe caller wants to see it anyway. */
-	if (keep_buf)
-		*userbuf = buffer;
-	else
-	{
-		ReleaseBuffer(buffer);
-		*userbuf = InvalidBuffer;
-	}
-
-	return false;
-}
-
 /*
  *	heap_hot_search_buffer	- search HOT chain for tuple satisfying snapshot
  *
@@ -2189,131 +1944,6 @@ heap_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot,
 	return result;
 }
 
-/*
- *	heap_get_latest_tid -  get the latest tid of a specified tuple
- *
- * Actually, this gets the latest version that is visible according to
- * the passed snapshot.  You can pass SnapshotDirty to get the very latest,
- * possibly uncommitted version.
- *
- * *tid is both an input and an output parameter: it is updated to
- * show the latest version of the row.  Note that it will not be changed
- * if no version of the row passes the snapshot test.
- */
-void
-heap_get_latest_tid(Relation relation,
-					Snapshot snapshot,
-					ItemPointer tid)
-{
-	BlockNumber blk;
-	ItemPointerData ctid;
-	TransactionId priorXmax;
-
-	/* this is to avoid Assert failures on bad input */
-	if (!ItemPointerIsValid(tid))
-		return;
-
-	/*
-	 * Since this can be called with user-supplied TID, don't trust the input
-	 * too much.  (RelationGetNumberOfBlocks is an expensive check, so we
-	 * don't check t_ctid links again this way.  Note that it would not do to
-	 * call it just once and save the result, either.)
-	 */
-	blk = ItemPointerGetBlockNumber(tid);
-	if (blk >= RelationGetNumberOfBlocks(relation))
-		elog(ERROR, "block number %u is out of range for relation \"%s\"",
-			 blk, RelationGetRelationName(relation));
-
-	/*
-	 * Loop to chase down t_ctid links.  At top of loop, ctid is the tuple we
-	 * need to examine, and *tid is the TID we will return if ctid turns out
-	 * to be bogus.
-	 *
-	 * Note that we will loop until we reach the end of the t_ctid chain.
-	 * Depending on the snapshot passed, there might be at most one visible
-	 * version of the row, but we don't try to optimize for that.
-	 */
-	ctid = *tid;
-	priorXmax = InvalidTransactionId;	/* cannot check first XMIN */
-	for (;;)
-	{
-		Buffer		buffer;
-		Page		page;
-		OffsetNumber offnum;
-		ItemId		lp;
-		HeapTupleData tp;
-		bool		valid;
-
-		/*
-		 * Read, pin, and lock the page.
-		 */
-		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&ctid));
-		LockBuffer(buffer, BUFFER_LOCK_SHARE);
-		page = BufferGetPage(buffer);
-		TestForOldSnapshot(snapshot, relation, page);
-
-		/*
-		 * Check for bogus item number.  This is not treated as an error
-		 * condition because it can happen while following a t_ctid link. We
-		 * just assume that the prior tid is OK and return it unchanged.
-		 */
-		offnum = ItemPointerGetOffsetNumber(&ctid);
-		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-		lp = PageGetItemId(page, offnum);
-		if (!ItemIdIsNormal(lp))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-
-		/* OK to access the tuple */
-		tp.t_self = ctid;
-		tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-		tp.t_len = ItemIdGetLength(lp);
-		tp.t_tableOid = RelationGetRelid(relation);
-
-		/*
-		 * After following a t_ctid link, we might arrive at an unrelated
-		 * tuple.  Check for XMIN match.
-		 */
-		if (TransactionIdIsValid(priorXmax) &&
-			!TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(tp.t_data)))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-
-		/*
-		 * Check time qualification of tuple; if visible, set it as the new
-		 * result candidate.
-		 */
-		valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, &tp, snapshot, buffer);
-		CheckForSerializableConflictOut(valid, relation, &tp, buffer, snapshot);
-		if (valid)
-			*tid = ctid;
-
-		/*
-		 * If there's a valid t_ctid link, follow it, else we're done.
-		 */
-		if ((tp.t_data->t_infomask & HEAP_XMAX_INVALID) ||
-			HeapTupleHeaderIsOnlyLocked(tp.t_data) ||
-			ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid))
-		{
-			UnlockReleaseBuffer(buffer);
-			break;
-		}
-
-		ctid = tp.t_data->t_ctid;
-		priorXmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
-		UnlockReleaseBuffer(buffer);
-	}							/* end of loop */
-}
-
-
 /*
  * UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends
  *
@@ -2330,7 +1960,7 @@ heap_get_latest_tid(Relation relation,
  *
  * Note this is not allowed for tuples whose xmax is a multixact.
  */
-static void
+void
 UpdateXmaxHintBits(HeapTupleHeader tuple, Buffer buffer, TransactionId xid)
 {
 	Assert(TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple), xid));
@@ -2386,7 +2016,6 @@ ReleaseBulkInsertStatePin(BulkInsertState bistate)
 	bistate->current_buf = InvalidBuffer;
 }
 
-
 /*
  *	heap_insert		- insert tuple into a heap
  *
@@ -2613,7 +2242,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
  * tuple if not. Note that in any case, the header fields are also set in
  * the original tuple.
  */
-static HeapTuple
+HeapTuple
 heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
 					CommandId cid, int options)
 {
@@ -2683,406 +2312,104 @@ heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
 }
 
 /*
- *	heap_multi_insert	- insert multiple tuple into a heap
+ *	simple_heap_insert - insert a tuple
+ *
+ * Currently, this routine differs from heap_insert only in supplying
+ * a default command ID and not allowing access to the speedup options.
  *
- * This is like heap_insert(), but inserts multiple tuples in one operation.
- * That's faster than calling heap_insert() in a loop, because when multiple
- * tuples can be inserted on a single page, we can write just a single WAL
- * record covering all of them, and only need to lock/unlock the page once.
+ * This should be used rather than using heap_insert directly in most places
+ * where we are modifying system catalogs.
+ */
+Oid
+simple_heap_insert(Relation relation, HeapTuple tup)
+{
+	return heap_insert(relation, tup, GetCurrentCommandId(true), 0, NULL);
+}
+
+/*
+ * Given infomask/infomask2, compute the bits that must be saved in the
+ * "infobits" field of xl_heap_delete, xl_heap_update, xl_heap_lock,
+ * xl_heap_lock_updated WAL records.
  *
- * Note: this leaks memory into the current memory context. You can create a
- * temporary context before calling this, if that's a problem.
+ * See fix_infomask_from_infobits.
  */
-void
-heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
-				  CommandId cid, int options, BulkInsertState bistate)
+uint8
+compute_infobits(uint16 infomask, uint16 infomask2)
+{
+	return
+		((infomask & HEAP_XMAX_IS_MULTI) != 0 ? XLHL_XMAX_IS_MULTI : 0) |
+		((infomask & HEAP_XMAX_LOCK_ONLY) != 0 ? XLHL_XMAX_LOCK_ONLY : 0) |
+		((infomask & HEAP_XMAX_EXCL_LOCK) != 0 ? XLHL_XMAX_EXCL_LOCK : 0) |
+	/* note we ignore HEAP_XMAX_SHR_LOCK here */
+		((infomask & HEAP_XMAX_KEYSHR_LOCK) != 0 ? XLHL_XMAX_KEYSHR_LOCK : 0) |
+		((infomask2 & HEAP_KEYS_UPDATED) != 0 ?
+		 XLHL_KEYS_UPDATED : 0);
+}
+
+
+
+/*
+ *	heap_delete - delete a tuple
+ *
+ * NB: do not call this directly unless you are prepared to deal with
+ * concurrent-update conditions.  Use simple_heap_delete instead.
+ *
+ *	relation - table to be modified (caller must hold suitable lock)
+ *	tid - TID of tuple to be deleted
+ *	cid - delete command ID (used for visibility test, and stored into
+ *		cmax if successful)
+ *	crosscheck - if not InvalidSnapshot, also check tuple against this
+ *	wait - true if should wait for any conflicting update to commit/abort
+ *	hufd - output parameter, filled in failure cases (see below)
+ *
+ * Normal, successful return value is HeapTupleMayBeUpdated, which
+ * actually means we did delete it.  Failure return codes are
+ * HeapTupleSelfUpdated, HeapTupleUpdated, or HeapTupleBeingUpdated
+ * (the last only possible if wait == false).
+ *
+ * In the failure cases, 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.
+ */
+HTSU_Result
+heap_delete(Relation relation, ItemPointer tid,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			HeapUpdateFailureData *hufd)
 {
+	HTSU_Result result;
 	TransactionId xid = GetCurrentTransactionId();
-	HeapTuple  *heaptuples;
-	int			i;
-	int			ndone;
-	char	   *scratch = NULL;
+	ItemId		lp;
+	HeapTupleData tp;
 	Page		page;
-	bool		needwal;
-	Size		saveFreeSpace;
-	bool		need_tuple_data = RelationIsLogicallyLogged(relation);
-	bool		need_cids = RelationIsAccessibleInLogicalDecoding(relation);
-
-	needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation);
-	saveFreeSpace = RelationGetTargetPageFreeSpace(relation,
-												   HEAP_DEFAULT_FILLFACTOR);
+	BlockNumber block;
+	Buffer		buffer;
+	Buffer		vmbuffer = InvalidBuffer;
+	TransactionId new_xmax;
+	uint16		new_infomask,
+				new_infomask2;
+	bool		have_tuple_lock = false;
+	bool		iscombo;
+	bool		all_visible_cleared = false;
+	HeapTuple	old_key_tuple = NULL;	/* replica identity of the tuple */
+	bool		old_key_copied = false;
 
-	/* Toast and set header data in all the tuples */
-	heaptuples = palloc(ntuples * sizeof(HeapTuple));
-	for (i = 0; i < ntuples; i++)
-		heaptuples[i] = heap_prepare_insert(relation, tuples[i],
-											xid, cid, options);
+	Assert(ItemPointerIsValid(tid));
 
 	/*
-	 * Allocate some memory to use for constructing the WAL record. Using
-	 * palloc() within a critical section is not safe, so we allocate this
-	 * beforehand.
+	 * Forbid this during a parallel operation, lest it allocate a combocid.
+	 * Other workers might need that combocid for visibility checks, and we
+	 * have no provision for broadcasting it to them.
 	 */
-	if (needwal)
-		scratch = palloc(BLCKSZ);
+	if (IsInParallelMode())
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+				 errmsg("cannot delete tuples during a parallel operation")));
 
-	/*
-	 * We're about to do the actual inserts -- but check for conflict first,
-	 * to minimize the possibility of having to roll back work we've just
-	 * done.
-	 *
-	 * A check here does not definitively prevent a serialization anomaly;
-	 * that check MUST be done at least past the point of acquiring an
-	 * exclusive buffer content lock on every buffer that will be affected,
-	 * and MAY be done after all inserts are reflected in the buffers and
-	 * those locks are released; otherwise there race condition.  Since
-	 * multiple buffers can be locked and unlocked in the loop below, and it
-	 * would not be feasible to identify and lock all of those buffers before
-	 * the loop, we must do a final check at the end.
-	 *
-	 * The check here could be omitted with no loss of correctness; it is
-	 * present strictly as an optimization.
-	 *
-	 * For heap inserts, we only need to check for table-level SSI locks. Our
-	 * new tuples can't possibly conflict with existing tuple locks, and heap
-	 * page locks are only consolidated versions of tuple locks; they do not
-	 * lock "gaps" as index page locks do.  So we don't need to specify a
-	 * buffer when making the call, which makes for a faster check.
-	 */
-	CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
-
-	ndone = 0;
-	while (ndone < ntuples)
-	{
-		Buffer		buffer;
-		Buffer		vmbuffer = InvalidBuffer;
-		bool		all_visible_cleared = false;
-		int			nthispage;
-
-		CHECK_FOR_INTERRUPTS();
-
-		/*
-		 * Find buffer where at least the next tuple will fit.  If the page is
-		 * all-visible, this will also pin the requisite visibility map page.
-		 */
-		buffer = RelationGetBufferForTuple(relation, heaptuples[ndone]->t_len,
-										   InvalidBuffer, options, bistate,
-										   &vmbuffer, NULL);
-		page = BufferGetPage(buffer);
-
-		/* NO EREPORT(ERROR) from here till changes are logged */
-		START_CRIT_SECTION();
-
-		/*
-		 * RelationGetBufferForTuple has ensured that the first tuple fits.
-		 * Put that on the page, and then as many other tuples as fit.
-		 */
-		RelationPutHeapTuple(relation, buffer, heaptuples[ndone], false);
-		for (nthispage = 1; ndone + nthispage < ntuples; nthispage++)
-		{
-			HeapTuple	heaptup = heaptuples[ndone + nthispage];
-
-			if (PageGetHeapFreeSpace(page) < MAXALIGN(heaptup->t_len) + saveFreeSpace)
-				break;
-
-			RelationPutHeapTuple(relation, buffer, heaptup, false);
-
-			/*
-			 * We don't use heap_multi_insert for catalog tuples yet, but
-			 * better be prepared...
-			 */
-			if (needwal && need_cids)
-				log_heap_new_cid(relation, heaptup);
-		}
-
-		if (PageIsAllVisible(page))
-		{
-			all_visible_cleared = true;
-			PageClearAllVisible(page);
-			visibilitymap_clear(relation,
-								BufferGetBlockNumber(buffer),
-								vmbuffer, VISIBILITYMAP_VALID_BITS);
-		}
-
-		/*
-		 * XXX Should we set PageSetPrunable on this page ? See heap_insert()
-		 */
-
-		MarkBufferDirty(buffer);
-
-		/* XLOG stuff */
-		if (needwal)
-		{
-			XLogRecPtr	recptr;
-			xl_heap_multi_insert *xlrec;
-			uint8		info = XLOG_HEAP2_MULTI_INSERT;
-			char	   *tupledata;
-			int			totaldatalen;
-			char	   *scratchptr = scratch;
-			bool		init;
-			int			bufflags = 0;
-
-			/*
-			 * If the page was previously empty, we can reinit the page
-			 * instead of restoring the whole thing.
-			 */
-			init = (ItemPointerGetOffsetNumber(&(heaptuples[ndone]->t_self)) == FirstOffsetNumber &&
-					PageGetMaxOffsetNumber(page) == FirstOffsetNumber + nthispage - 1);
-
-			/* allocate xl_heap_multi_insert struct from the scratch area */
-			xlrec = (xl_heap_multi_insert *) scratchptr;
-			scratchptr += SizeOfHeapMultiInsert;
-
-			/*
-			 * Allocate offsets array. Unless we're reinitializing the page,
-			 * in that case the tuples are stored in order starting at
-			 * FirstOffsetNumber and we don't need to store the offsets
-			 * explicitly.
-			 */
-			if (!init)
-				scratchptr += nthispage * sizeof(OffsetNumber);
-
-			/* the rest of the scratch space is used for tuple data */
-			tupledata = scratchptr;
-
-			xlrec->flags = all_visible_cleared ? XLH_INSERT_ALL_VISIBLE_CLEARED : 0;
-			xlrec->ntuples = nthispage;
-
-			/*
-			 * Write out an xl_multi_insert_tuple and the tuple data itself
-			 * for each tuple.
-			 */
-			for (i = 0; i < nthispage; i++)
-			{
-				HeapTuple	heaptup = heaptuples[ndone + i];
-				xl_multi_insert_tuple *tuphdr;
-				int			datalen;
-
-				if (!init)
-					xlrec->offsets[i] = ItemPointerGetOffsetNumber(&heaptup->t_self);
-				/* xl_multi_insert_tuple needs two-byte alignment. */
-				tuphdr = (xl_multi_insert_tuple *) SHORTALIGN(scratchptr);
-				scratchptr = ((char *) tuphdr) + SizeOfMultiInsertTuple;
-
-				tuphdr->t_infomask2 = heaptup->t_data->t_infomask2;
-				tuphdr->t_infomask = heaptup->t_data->t_infomask;
-				tuphdr->t_hoff = heaptup->t_data->t_hoff;
-
-				/* write bitmap [+ padding] [+ oid] + data */
-				datalen = heaptup->t_len - SizeofHeapTupleHeader;
-				memcpy(scratchptr,
-					   (char *) heaptup->t_data + SizeofHeapTupleHeader,
-					   datalen);
-				tuphdr->datalen = datalen;
-				scratchptr += datalen;
-			}
-			totaldatalen = scratchptr - tupledata;
-			Assert((scratchptr - scratch) < BLCKSZ);
-
-			if (need_tuple_data)
-				xlrec->flags |= XLH_INSERT_CONTAINS_NEW_TUPLE;
-
-			/*
-			 * Signal that this is the last xl_heap_multi_insert record
-			 * emitted by this call to heap_multi_insert(). Needed for logical
-			 * decoding so it knows when to cleanup temporary data.
-			 */
-			if (ndone + nthispage == ntuples)
-				xlrec->flags |= XLH_INSERT_LAST_IN_MULTI;
-
-			if (init)
-			{
-				info |= XLOG_HEAP_INIT_PAGE;
-				bufflags |= REGBUF_WILL_INIT;
-			}
-
-			/*
-			 * If we're doing logical decoding, include the new tuple data
-			 * even if we take a full-page image of the page.
-			 */
-			if (need_tuple_data)
-				bufflags |= REGBUF_KEEP_DATA;
-
-			XLogBeginInsert();
-			XLogRegisterData((char *) xlrec, tupledata - scratch);
-			XLogRegisterBuffer(0, buffer, REGBUF_STANDARD | bufflags);
-
-			XLogRegisterBufData(0, tupledata, totaldatalen);
-
-			/* filtering by origin on a row level is much more efficient */
-			XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
-
-			recptr = XLogInsert(RM_HEAP2_ID, info);
-
-			PageSetLSN(page, recptr);
-		}
-
-		END_CRIT_SECTION();
-
-		UnlockReleaseBuffer(buffer);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
-
-		ndone += nthispage;
-	}
-
-	/*
-	 * We're done with the actual inserts.  Check for conflicts again, to
-	 * ensure that all rw-conflicts in to these inserts are detected.  Without
-	 * this final check, a sequential scan of the heap may have locked the
-	 * table after the "before" check, missing one opportunity to detect the
-	 * conflict, and then scanned the table before the new tuples were there,
-	 * missing the other chance to detect the conflict.
-	 *
-	 * For heap inserts, we only need to check for table-level SSI locks. Our
-	 * new tuples can't possibly conflict with existing tuple locks, and heap
-	 * page locks are only consolidated versions of tuple locks; they do not
-	 * lock "gaps" as index page locks do.  So we don't need to specify a
-	 * buffer when making the call.
-	 */
-	CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
-
-	/*
-	 * If tuples are cachable, mark them for invalidation from the caches in
-	 * case we abort.  Note it is OK to do this after releasing the buffer,
-	 * because the heaptuples data structure is all in local memory, not in
-	 * the shared buffer.
-	 */
-	if (IsCatalogRelation(relation))
-	{
-		for (i = 0; i < ntuples; i++)
-			CacheInvalidateHeapTuple(relation, heaptuples[i], NULL);
-	}
-
-	/*
-	 * Copy t_self fields back to the caller's original tuples. This does
-	 * nothing for untoasted tuples (tuples[i] == heaptuples[i)], but it's
-	 * probably faster to always copy than check.
-	 */
-	for (i = 0; i < ntuples; i++)
-		tuples[i]->t_self = heaptuples[i]->t_self;
-
-	pgstat_count_heap_insert(relation, ntuples);
-}
-
-/*
- *	simple_heap_insert - insert a tuple
- *
- * Currently, this routine differs from heap_insert only in supplying
- * a default command ID and not allowing access to the speedup options.
- *
- * This should be used rather than using heap_insert directly in most places
- * where we are modifying system catalogs.
- */
-Oid
-simple_heap_insert(Relation relation, HeapTuple tup)
-{
-	return heap_insert(relation, tup, GetCurrentCommandId(true), 0, NULL);
-}
-
-/*
- * Given infomask/infomask2, compute the bits that must be saved in the
- * "infobits" field of xl_heap_delete, xl_heap_update, xl_heap_lock,
- * xl_heap_lock_updated WAL records.
- *
- * See fix_infomask_from_infobits.
- */
-static uint8
-compute_infobits(uint16 infomask, uint16 infomask2)
-{
-	return
-		((infomask & HEAP_XMAX_IS_MULTI) != 0 ? XLHL_XMAX_IS_MULTI : 0) |
-		((infomask & HEAP_XMAX_LOCK_ONLY) != 0 ? XLHL_XMAX_LOCK_ONLY : 0) |
-		((infomask & HEAP_XMAX_EXCL_LOCK) != 0 ? XLHL_XMAX_EXCL_LOCK : 0) |
-	/* note we ignore HEAP_XMAX_SHR_LOCK here */
-		((infomask & HEAP_XMAX_KEYSHR_LOCK) != 0 ? XLHL_XMAX_KEYSHR_LOCK : 0) |
-		((infomask2 & HEAP_KEYS_UPDATED) != 0 ?
-		 XLHL_KEYS_UPDATED : 0);
-}
-
-/*
- * Given two versions of the same t_infomask for a tuple, compare them and
- * return whether the relevant status for a tuple Xmax has changed.  This is
- * used after a buffer lock has been released and reacquired: we want to ensure
- * that the tuple state continues to be the same it was when we previously
- * examined it.
- *
- * Note the Xmax field itself must be compared separately.
- */
-static inline bool
-xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask)
-{
-	const uint16 interesting =
-	HEAP_XMAX_IS_MULTI | HEAP_XMAX_LOCK_ONLY | HEAP_LOCK_MASK;
-
-	if ((new_infomask & interesting) != (old_infomask & interesting))
-		return true;
-
-	return false;
-}
-
-/*
- *	heap_delete - delete a tuple
- *
- * NB: do not call this directly unless you are prepared to deal with
- * concurrent-update conditions.  Use simple_heap_delete instead.
- *
- *	relation - table to be modified (caller must hold suitable lock)
- *	tid - TID of tuple to be deleted
- *	cid - delete command ID (used for visibility test, and stored into
- *		cmax if successful)
- *	crosscheck - if not InvalidSnapshot, also check tuple against this
- *	wait - true if should wait for any conflicting update to commit/abort
- *	hufd - output parameter, filled in failure cases (see below)
- *
- * Normal, successful return value is HeapTupleMayBeUpdated, which
- * actually means we did delete it.  Failure return codes are
- * HeapTupleSelfUpdated, HeapTupleUpdated, or HeapTupleBeingUpdated
- * (the last only possible if wait == false).
- *
- * In the failure cases, 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.
- */
-HTSU_Result
-heap_delete(Relation relation, ItemPointer tid,
-			CommandId cid, Snapshot crosscheck, bool wait,
-			HeapUpdateFailureData *hufd)
-{
-	HTSU_Result result;
-	TransactionId xid = GetCurrentTransactionId();
-	ItemId		lp;
-	HeapTupleData tp;
-	Page		page;
-	BlockNumber block;
-	Buffer		buffer;
-	Buffer		vmbuffer = InvalidBuffer;
-	TransactionId new_xmax;
-	uint16		new_infomask,
-				new_infomask2;
-	bool		have_tuple_lock = false;
-	bool		iscombo;
-	bool		all_visible_cleared = false;
-	HeapTuple	old_key_tuple = NULL;	/* replica identity of the tuple */
-	bool		old_key_copied = false;
-
-	Assert(ItemPointerIsValid(tid));
-
-	/*
-	 * Forbid this during a parallel operation, lest it allocate a combocid.
-	 * Other workers might need that combocid for visibility checks, and we
-	 * have no provision for broadcasting it to them.
-	 */
-	if (IsInParallelMode())
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
-				 errmsg("cannot delete tuples during a parallel operation")));
-
-	block = ItemPointerGetBlockNumber(tid);
-	buffer = ReadBuffer(relation, block);
-	page = BufferGetPage(buffer);
+	block = ItemPointerGetBlockNumber(tid);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
 
 	/*
 	 * Before locking the buffer, pin the visibility map page if it appears to
@@ -4523,7 +3850,7 @@ simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup)
 /*
  * Return the MultiXactStatus corresponding to the given tuple lock mode.
  */
-static MultiXactStatus
+MultiXactStatus
 get_mxact_status_for_lock(LockTupleMode mode, bool is_update)
 {
 	int			retval;
@@ -4541,1712 +3868,330 @@ get_mxact_status_for_lock(LockTupleMode mode, bool is_update)
 }
 
 /*
- *	heap_lock_tuple - lock a tuple in shared or exclusive mode
- *
- * Note that this acquires a buffer pin, which the caller must release.
- *
- * Input parameters:
- *	relation: relation containing tuple (caller must hold suitable lock)
- *	tuple->t_self: TID of tuple to lock (rest of struct need not be valid)
- *	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
- *	follow_updates: if true, follow the update chain to also lock descendant
- *		tuples.
- *
- * Output parameters:
- *	*tuple: all fields filled in
- *	*buffer: set to buffer holding tuple (pinned but not locked at exit)
- *	*hufd: filled in failure cases (see below)
- *
- * 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
+ * Acquire heavyweight lock on the given tuple, in preparation for acquiring
+ * its normal, Xmax-based tuple lock.
  *
- * 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.
+ * have_tuple_lock is an input and output parameter: on input, it indicates
+ * whether the lock has previously been acquired (and this function does
+ * nothing in that case).  If this function returns success, have_tuple_lock
+ * has been flipped to true.
  *
- * See README.tuplock for a thorough explanation of this mechanism.
+ * Returns false if it was unable to obtain the lock; this can only happen if
+ * wait_policy is Skip.
  */
-HTSU_Result
-heap_lock_tuple(Relation relation, HeapTuple tuple,
-				CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
-				bool follow_updates,
-				Buffer *buffer, HeapUpdateFailureData *hufd)
+bool
+heap_acquire_tuplock(Relation relation, ItemPointer tid, LockTupleMode mode,
+					 LockWaitPolicy wait_policy, bool *have_tuple_lock)
 {
-	HTSU_Result result;
-	ItemPointer tid = &(tuple->t_self);
-	ItemId		lp;
-	Page		page;
-	Buffer		vmbuffer = InvalidBuffer;
-	BlockNumber block;
-	TransactionId xid,
-				xmax;
-	uint16		old_infomask,
-				new_infomask,
-				new_infomask2;
-	bool		first_time = true;
-	bool		have_tuple_lock = false;
-	bool		cleared_all_frozen = false;
-
-	*buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
-	block = ItemPointerGetBlockNumber(tid);
-
-	/*
-	 * Before locking the buffer, pin the visibility map page if it appears to
-	 * be necessary.  Since we haven't got the lock yet, someone else might be
-	 * in the middle of changing this, so we'll need to recheck after we have
-	 * the lock.
-	 */
-	if (PageIsAllVisible(BufferGetPage(*buffer)))
-		visibilitymap_pin(relation, block, &vmbuffer);
+	if (*have_tuple_lock)
+		return true;
 
-	LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+	switch (wait_policy)
+	{
+		case LockWaitBlock:
+			LockTupleTuplock(relation, tid, mode);
+			break;
 
-	page = BufferGetPage(*buffer);
-	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
-	Assert(ItemIdIsNormal(lp));
+		case LockWaitSkip:
+			if (!ConditionalLockTupleTuplock(relation, tid, mode))
+				return false;
+			break;
 
-	tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	tuple->t_len = ItemIdGetLength(lp);
-	tuple->t_tableOid = RelationGetRelid(relation);
+		case LockWaitError:
+			if (!ConditionalLockTupleTuplock(relation, tid, mode))
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 errmsg("could not obtain lock on row in relation \"%s\"",
+								RelationGetRelationName(relation))));
+			break;
+	}
+	*have_tuple_lock = true;
 
-l3:
-	result = relation->rd_stamroutine->snapshot_satisfiesUpdate(tuple, cid, *buffer);
-
-	if (result == HeapTupleInvisible)
-	{
-		/*
-		 * This is possible, but only when locking a tuple for ON CONFLICT
-		 * UPDATE.  We return this value here rather than throwing an error in
-		 * order to give that case the opportunity to throw a more specific
-		 * error.
-		 */
-		result = HeapTupleInvisible;
-		goto out_locked;
-	}
-	else if (result == HeapTupleBeingUpdated || result == HeapTupleUpdated)
-	{
-		TransactionId xwait;
-		uint16		infomask;
-		uint16		infomask2;
-		bool		require_sleep;
-		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);
-
-		LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
-
-		/*
-		 * If any subtransaction of the current top transaction already holds
-		 * a lock as strong as or stronger than what we're requesting, we
-		 * effectively hold the desired lock already.  We *must* succeed
-		 * without trying to take the tuple lock, else we will deadlock
-		 * against anyone wanting to acquire a stronger lock.
-		 *
-		 * Note we only do this the first time we loop on the HTSU result;
-		 * there is no point in testing in subsequent passes, because
-		 * evidently our own transaction cannot have acquired a new lock after
-		 * the first time we checked.
-		 */
-		if (first_time)
-		{
-			first_time = false;
-
-			if (infomask & HEAP_XMAX_IS_MULTI)
-			{
-				int			i;
-				int			nmembers;
-				MultiXactMember *members;
-
-				/*
-				 * We don't need to allow old multixacts here; if that had
-				 * been the case, HeapTupleSatisfiesUpdate would have returned
-				 * MayBeUpdated and we wouldn't be here.
-				 */
-				nmembers =
-					GetMultiXactIdMembers(xwait, &members, false,
-										  HEAP_XMAX_IS_LOCKED_ONLY(infomask));
-
-				for (i = 0; i < nmembers; i++)
-				{
-					/* only consider members of our own transaction */
-					if (!TransactionIdIsCurrentTransactionId(members[i].xid))
-						continue;
-
-					if (TUPLOCK_from_mxstatus(members[i].status) >= mode)
-					{
-						pfree(members);
-						result = HeapTupleMayBeUpdated;
-						goto out_unlocked;
-					}
-				}
-
-				if (members)
-					pfree(members);
-			}
-			else if (TransactionIdIsCurrentTransactionId(xwait))
-			{
-				switch (mode)
-				{
-					case LockTupleKeyShare:
-						Assert(HEAP_XMAX_IS_KEYSHR_LOCKED(infomask) ||
-							   HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
-							   HEAP_XMAX_IS_EXCL_LOCKED(infomask));
-						result = HeapTupleMayBeUpdated;
-						goto out_unlocked;
-					case LockTupleShare:
-						if (HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
-							HEAP_XMAX_IS_EXCL_LOCKED(infomask))
-						{
-							result = HeapTupleMayBeUpdated;
-							goto out_unlocked;
-						}
-						break;
-					case LockTupleNoKeyExclusive:
-						if (HEAP_XMAX_IS_EXCL_LOCKED(infomask))
-						{
-							result = HeapTupleMayBeUpdated;
-							goto out_unlocked;
-						}
-						break;
-					case LockTupleExclusive:
-						if (HEAP_XMAX_IS_EXCL_LOCKED(infomask) &&
-							infomask2 & HEAP_KEYS_UPDATED)
-						{
-							result = HeapTupleMayBeUpdated;
-							goto out_unlocked;
-						}
-						break;
-				}
-			}
-		}
-
-		/*
-		 * Initially assume that we will have to wait for the locking
-		 * transaction(s) to finish.  We check various cases below in which
-		 * this can be turned off.
-		 */
-		require_sleep = true;
-		if (mode == LockTupleKeyShare)
-		{
-			/*
-			 * If we're requesting KeyShare, and there's no update present, we
-			 * don't need to wait.  Even if there is an update, we can still
-			 * continue if the key hasn't been modified.
-			 *
-			 * However, if there are updates, we need to walk the update chain
-			 * to mark future versions of the row as locked, too.  That way,
-			 * if somebody deletes that future version, we're protected
-			 * against the key going away.  This locking of future versions
-			 * could block momentarily, if a concurrent transaction is
-			 * deleting a key; or it could return a value to the effect that
-			 * the transaction deleting the key has already committed.  So we
-			 * do this before re-locking the buffer; otherwise this would be
-			 * prone to deadlocks.
-			 *
-			 * Note that the TID we're locking was grabbed before we unlocked
-			 * the buffer.  For it to change while we're not looking, the
-			 * other properties we're testing for below after re-locking the
-			 * buffer would also change, in which case we would restart this
-			 * loop above.
-			 */
-			if (!(infomask2 & HEAP_KEYS_UPDATED))
-			{
-				bool		updated;
-
-				updated = !HEAP_XMAX_IS_LOCKED_ONLY(infomask);
-
-				/*
-				 * If there are updates, follow the update chain; bail out if
-				 * that cannot be done.
-				 */
-				if (follow_updates && updated)
-				{
-					HTSU_Result res;
-
-					res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
-												  GetCurrentTransactionId(),
-												  mode);
-					if (res != HeapTupleMayBeUpdated)
-					{
-						result = res;
-						/* recovery code expects to have buffer lock held */
-						LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-						goto failed;
-					}
-				}
-
-				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-				/*
-				 * Make sure it's still an appropriate lock, else start over.
-				 * Also, if it wasn't updated before we released the lock, but
-				 * is updated now, we start over too; the reason is that we
-				 * 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) ||
-					 !updated))
-					goto l3;
-
-				/* Things look okay, so we can skip sleeping */
-				require_sleep = false;
-
-				/*
-				 * Note we allow Xmax to change here; other updaters/lockers
-				 * could have modified it before we grabbed the buffer lock.
-				 * However, this is not a problem, because with the recheck we
-				 * just did we ensure that they still don't conflict with the
-				 * lock we want.
-				 */
-			}
-		}
-		else if (mode == LockTupleShare)
-		{
-			/*
-			 * If we're requesting Share, we can similarly avoid sleeping if
-			 * there's no update and no exclusive lock present.
-			 */
-			if (HEAP_XMAX_IS_LOCKED_ONLY(infomask) &&
-				!HEAP_XMAX_IS_EXCL_LOCKED(infomask))
-			{
-				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-				/*
-				 * 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))
-					goto l3;
-				require_sleep = false;
-			}
-		}
-		else if (mode == LockTupleNoKeyExclusive)
-		{
-			/*
-			 * If we're requesting NoKeyExclusive, we might also be able to
-			 * avoid sleeping; just ensure that there no conflicting lock
-			 * already acquired.
-			 */
-			if (infomask & HEAP_XMAX_IS_MULTI)
-			{
-				if (!DoesMultiXactIdConflict((MultiXactId) xwait, infomask,
-											 mode))
-				{
-					/*
-					 * No conflict, but if the xmax changed under us 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),
-											 xwait))
-						goto l3;
-
-					/* otherwise, we're good */
-					require_sleep = false;
-				}
-			}
-			else if (HEAP_XMAX_IS_KEYSHR_LOCKED(infomask))
-			{
-				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-				/* if the xmax changed in the meantime, start over */
-				if (xmax_infomask_changed(tuple->t_data->t_infomask, infomask) ||
-					!TransactionIdEquals(
-										 HeapTupleHeaderGetRawXmax(tuple->t_data),
-										 xwait))
-					goto l3;
-				/* otherwise, we're good */
-				require_sleep = false;
-			}
-		}
-
-		/*
-		 * As a check independent from those above, we can also avoid sleeping
-		 * if the current transaction is the sole locker of the tuple.  Note
-		 * that the strength of the lock already held is irrelevant; this is
-		 * not about recording the lock in Xmax (which will be done regardless
-		 * of this optimization, below).  Also, note that the cases where we
-		 * hold a lock stronger than we are requesting are already handled
-		 * above by not doing anything.
-		 *
-		 * Note we only deal with the non-multixact case here; MultiXactIdWait
-		 * is well equipped to deal with this situation on its own.
-		 */
-		if (require_sleep && !(infomask & HEAP_XMAX_IS_MULTI) &&
-			TransactionIdIsCurrentTransactionId(xwait))
-		{
-			/* ... 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),
-									 xwait))
-				goto l3;
-			Assert(HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask));
-			require_sleep = false;
-		}
-
-		/*
-		 * Time to sleep on the other transaction/multixact, if necessary.
-		 *
-		 * If the other transaction is an update that's already committed,
-		 * then sleeping cannot possibly do any good: if we're required to
-		 * sleep, get out to raise an error instead.
-		 *
-		 * By here, we either have already acquired the buffer exclusive lock,
-		 * 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)
-		{
-			LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-			goto failed;
-		}
-		else if (require_sleep)
-		{
-			/*
-			 * Acquire tuple lock to establish our priority for the tuple, or
-			 * die trying.  LockTuple will release us when we are next-in-line
-			 * for the tuple.  We must do this even if we are share-locking.
-			 *
-			 * If we are forced to "start over" below, we keep the tuple lock;
-			 * this arranges that we stay at the head of the line while
-			 * rechecking tuple state.
-			 */
-			if (!heap_acquire_tuplock(relation, tid, mode, wait_policy,
-									  &have_tuple_lock))
-			{
-				/*
-				 * This can only happen if wait_policy is Skip and the lock
-				 * couldn't be obtained.
-				 */
-				result = HeapTupleWouldBlock;
-				/* recovery code expects to have buffer lock held */
-				LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-				goto failed;
-			}
-
-			if (infomask & HEAP_XMAX_IS_MULTI)
-			{
-				MultiXactStatus status = get_mxact_status_for_lock(mode, false);
-
-				/* We only ever lock tuples, never update them */
-				if (status >= MultiXactStatusNoKeyUpdate)
-					elog(ERROR, "invalid lock mode in heap_lock_tuple");
-
-				/* wait for multixact to end, or die trying  */
-				switch (wait_policy)
-				{
-					case LockWaitBlock:
-						MultiXactIdWait((MultiXactId) xwait, status, infomask,
-										relation, &tuple->t_self, XLTW_Lock, NULL);
-						break;
-					case LockWaitSkip:
-						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
-														status, infomask, relation,
-														NULL))
-						{
-							result = HeapTupleWouldBlock;
-							/* recovery code expects to have buffer lock held */
-							LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-							goto failed;
-						}
-						break;
-					case LockWaitError:
-						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
-														status, infomask, relation,
-														NULL))
-							ereport(ERROR,
-									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-									 errmsg("could not obtain lock on row in relation \"%s\"",
-											RelationGetRelationName(relation))));
-
-						break;
-				}
-
-				/*
-				 * Of course, the multixact might not be done here: if we're
-				 * requesting a light lock mode, other transactions with light
-				 * locks could still be alive, as well as locks owned by our
-				 * own xact or other subxacts of this backend.  We need to
-				 * preserve the surviving MultiXact members.  Note that it
-				 * isn't absolutely necessary in the latter case, but doing so
-				 * is simpler.
-				 */
-			}
-			else
-			{
-				/* wait for regular transaction to end, or die trying */
-				switch (wait_policy)
-				{
-					case LockWaitBlock:
-						XactLockTableWait(xwait, relation, &tuple->t_self,
-										  XLTW_Lock);
-						break;
-					case LockWaitSkip:
-						if (!ConditionalXactLockTableWait(xwait))
-						{
-							result = HeapTupleWouldBlock;
-							/* recovery code expects to have buffer lock held */
-							LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-							goto failed;
-						}
-						break;
-					case LockWaitError:
-						if (!ConditionalXactLockTableWait(xwait))
-							ereport(ERROR,
-									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-									 errmsg("could not obtain lock on row in relation \"%s\"",
-											RelationGetRelationName(relation))));
-						break;
-				}
-			}
-
-			/* if there are updates, follow the update chain */
-			if (follow_updates && !HEAP_XMAX_IS_LOCKED_ONLY(infomask))
-			{
-				HTSU_Result res;
-
-				res = heap_lock_updated_tuple(relation, tuple, &t_ctid,
-											  GetCurrentTransactionId(),
-											  mode);
-				if (res != HeapTupleMayBeUpdated)
-				{
-					result = res;
-					/* recovery code expects to have buffer lock held */
-					LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-					goto failed;
-				}
-			}
-
-			LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-			/*
-			 * xwait is done, but if xwait had just locked the tuple then some
-			 * 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),
-									 xwait))
-				goto l3;
-
-			if (!(infomask & HEAP_XMAX_IS_MULTI))
-			{
-				/*
-				 * Otherwise check if it committed or aborted.  Note we cannot
-				 * be here if the tuple was only locked by somebody who didn't
-				 * conflict with us; that would have been handled above.  So
-				 * that transaction must necessarily be gone by now.  But
-				 * don't check for this in the multixact case, because some
-				 * locker transactions might still be running.
-				 */
-				UpdateXmaxHintBits(tuple->t_data, *buffer, xwait);
-			}
-		}
-
-		/* By here, we're certain that we hold buffer exclusive lock again */
-
-		/*
-		 * We may lock if previous xmax aborted, or if it committed but only
-		 * locked the tuple without updating it; or if we didn't have to wait
-		 * 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))
-			result = HeapTupleMayBeUpdated;
-		else
-			result = HeapTupleUpdated;
-	}
-
-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);
-		if (result == HeapTupleSelfUpdated)
-			hufd->cmax = HeapTupleHeaderGetCmax(tuple->t_data);
-		else
-			hufd->cmax = InvalidCommandId;
-		goto out_locked;
-	}
-
-	/*
-	 * If we didn't pin the visibility map page and the page has become all
-	 * visible while we were busy locking the buffer, or during some
-	 * subsequent window during which we had it unlocked, we'll have to unlock
-	 * and re-lock, to avoid holding the buffer lock across I/O.  That's a bit
-	 * unfortunate, especially since we'll now have to recheck whether the
-	 * tuple has been locked or updated under us, but hopefully it won't
-	 * happen very often.
-	 */
-	if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
-	{
-		LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
-		visibilitymap_pin(relation, block, &vmbuffer);
-		LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-		goto l3;
-	}
-
-	xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
-	old_infomask = tuple->t_data->t_infomask;
-
-	/*
-	 * If this is the first possibly-multixact-able operation in the current
-	 * transaction, set my per-backend OldestMemberMXactId setting. We can be
-	 * certain that the transaction will never become a member of any older
-	 * MultiXactIds than that.  (We have to do this even if we end up just
-	 * using our own TransactionId below, since some other backend could
-	 * incorporate our XID into a MultiXact immediately afterwards.)
-	 */
-	MultiXactIdSetOldestMember();
-
-	/*
-	 * Compute the new xmax and infomask to store into the tuple.  Note we do
-	 * 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,
-							  GetCurrentTransactionId(), mode, false,
-							  &xid, &new_infomask, &new_infomask2);
-
-	START_CRIT_SECTION();
-
-	/*
-	 * Store transaction information of xact locking the tuple.
-	 *
-	 * Note: Cmax is meaningless in this context, so don't set it; this avoids
-	 * possibly generating a useless combo CID.  Moreover, if we're locking a
-	 * previously updated tuple, it's important to preserve the Cmax.
-	 *
-	 * 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;
-	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
-		HeapTupleHeaderClearHotUpdated(tuple->t_data);
-	HeapTupleHeaderSetXmax(tuple->t_data, xid);
-
-	/*
-	 * Make sure there is no forward chain link in t_ctid.  Note that in the
-	 * cases where the tuple has been updated, we must not overwrite t_ctid,
-	 * because it was set by the updater.  Moreover, if the tuple has been
-	 * updated, we need to follow the update chain to lock the new versions of
-	 * the tuple as well.
-	 */
-	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
-		tuple->t_data->t_ctid = *tid;
-
-	/* Clear only the all-frozen bit on visibility map if needed */
-	if (PageIsAllVisible(page) &&
-		visibilitymap_clear(relation, block, vmbuffer,
-							VISIBILITYMAP_ALL_FROZEN))
-		cleared_all_frozen = true;
-
-
-	MarkBufferDirty(*buffer);
-
-	/*
-	 * XLOG stuff.  You might think that we don't need an XLOG record because
-	 * there is no state change worth restoring after a crash.  You would be
-	 * wrong however: we have just written either a TransactionId or a
-	 * MultiXactId that may never have been seen on disk before, and we need
-	 * to make sure that there are XLOG entries covering those ID numbers.
-	 * Else the same IDs might be re-used after a crash, which would be
-	 * disastrous if this page made it to disk before the crash.  Essentially
-	 * we have to enforce the WAL log-before-data rule even in this case.
-	 * (Also, in a PITR log-shipping or 2PC environment, we have to have XLOG
-	 * entries for everything anyway.)
-	 */
-	if (RelationNeedsWAL(relation))
-	{
-		xl_heap_lock xlrec;
-		XLogRecPtr	recptr;
-
-		XLogBeginInsert();
-		XLogRegisterBuffer(0, *buffer, REGBUF_STANDARD);
-
-		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
-		xlrec.locking_xid = xid;
-		xlrec.infobits_set = compute_infobits(new_infomask,
-											  tuple->t_data->t_infomask2);
-		xlrec.flags = cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
-		XLogRegisterData((char *) &xlrec, SizeOfHeapLock);
-
-		/* we don't decode row locks atm, so no need to log the origin */
-
-		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_LOCK);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	result = HeapTupleMayBeUpdated;
-
-out_locked:
-	LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
-
-out_unlocked:
-	if (BufferIsValid(vmbuffer))
-		ReleaseBuffer(vmbuffer);
-
-	/*
-	 * Don't update the visibility map here. Locking a tuple doesn't change
-	 * visibility info.
-	 */
-
-	/*
-	 * Now that we have successfully marked the tuple as locked, we can
-	 * release the lmgr tuple lock, if we had it.
-	 */
-	if (have_tuple_lock)
-		UnlockTupleTuplock(relation, tid, mode);
-
-	return result;
-}
-
-/*
- * Acquire heavyweight lock on the given tuple, in preparation for acquiring
- * its normal, Xmax-based tuple lock.
- *
- * have_tuple_lock is an input and output parameter: on input, it indicates
- * whether the lock has previously been acquired (and this function does
- * nothing in that case).  If this function returns success, have_tuple_lock
- * has been flipped to true.
- *
- * Returns false if it was unable to obtain the lock; this can only happen if
- * wait_policy is Skip.
- */
-static bool
-heap_acquire_tuplock(Relation relation, ItemPointer tid, LockTupleMode mode,
-					 LockWaitPolicy wait_policy, bool *have_tuple_lock)
-{
-	if (*have_tuple_lock)
-		return true;
-
-	switch (wait_policy)
-	{
-		case LockWaitBlock:
-			LockTupleTuplock(relation, tid, mode);
-			break;
-
-		case LockWaitSkip:
-			if (!ConditionalLockTupleTuplock(relation, tid, mode))
-				return false;
-			break;
-
-		case LockWaitError:
-			if (!ConditionalLockTupleTuplock(relation, tid, mode))
-				ereport(ERROR,
-						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-						 errmsg("could not obtain lock on row in relation \"%s\"",
-								RelationGetRelationName(relation))));
-			break;
-	}
-	*have_tuple_lock = true;
-
-	return true;
-}
-
-/*
- * Given an original set of Xmax and infomask, and a transaction (identified by
- * add_to_xmax) acquiring a new lock of some mode, compute the new Xmax and
- * corresponding infomasks to use on the tuple.
- *
- * Note that this might have side effects such as creating a new MultiXactId.
- *
- * Most callers will have called HeapTupleSatisfiesUpdate before this function;
- * that will have set the HEAP_XMAX_INVALID bit if the xmax was a MultiXactId
- * but it was not running anymore. There is a race condition, which is that the
- * MultiXactId may have finished since then, but that uncommon case is handled
- * either here, or within MultiXactIdExpand.
- *
- * There is a similar race condition possible when the old xmax was a regular
- * TransactionId.  We test TransactionIdIsInProgress again just to narrow the
- * window, but it's still possible to end up creating an unnecessary
- * MultiXactId.  Fortunately this is harmless.
- */
-static void
-compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
-						  uint16 old_infomask2, TransactionId add_to_xmax,
-						  LockTupleMode mode, bool is_update,
-						  TransactionId *result_xmax, uint16 *result_infomask,
-						  uint16 *result_infomask2)
-{
-	TransactionId new_xmax;
-	uint16		new_infomask,
-				new_infomask2;
-
-	Assert(TransactionIdIsCurrentTransactionId(add_to_xmax));
-
-l5:
-	new_infomask = 0;
-	new_infomask2 = 0;
-	if (old_infomask & HEAP_XMAX_INVALID)
-	{
-		/*
-		 * No previous locker; we just insert our own TransactionId.
-		 *
-		 * Note that it's critical that this case be the first one checked,
-		 * because there are several blocks below that come back to this one
-		 * to implement certain optimizations; old_infomask might contain
-		 * other dirty bits in those cases, but we don't really care.
-		 */
-		if (is_update)
-		{
-			new_xmax = add_to_xmax;
-			if (mode == LockTupleExclusive)
-				new_infomask2 |= HEAP_KEYS_UPDATED;
-		}
-		else
-		{
-			new_infomask |= HEAP_XMAX_LOCK_ONLY;
-			switch (mode)
-			{
-				case LockTupleKeyShare:
-					new_xmax = add_to_xmax;
-					new_infomask |= HEAP_XMAX_KEYSHR_LOCK;
-					break;
-				case LockTupleShare:
-					new_xmax = add_to_xmax;
-					new_infomask |= HEAP_XMAX_SHR_LOCK;
-					break;
-				case LockTupleNoKeyExclusive:
-					new_xmax = add_to_xmax;
-					new_infomask |= HEAP_XMAX_EXCL_LOCK;
-					break;
-				case LockTupleExclusive:
-					new_xmax = add_to_xmax;
-					new_infomask |= HEAP_XMAX_EXCL_LOCK;
-					new_infomask2 |= HEAP_KEYS_UPDATED;
-					break;
-				default:
-					new_xmax = InvalidTransactionId;	/* silence compiler */
-					elog(ERROR, "invalid lock mode");
-			}
-		}
-	}
-	else if (old_infomask & HEAP_XMAX_IS_MULTI)
-	{
-		MultiXactStatus new_status;
-
-		/*
-		 * Currently we don't allow XMAX_COMMITTED to be set for multis, so
-		 * cross-check.
-		 */
-		Assert(!(old_infomask & HEAP_XMAX_COMMITTED));
-
-		/*
-		 * A multixact together with LOCK_ONLY set but neither lock bit set
-		 * (i.e. a pg_upgraded share locked tuple) cannot possibly be running
-		 * anymore.  This check is critical for databases upgraded by
-		 * pg_upgrade; both MultiXactIdIsRunning and MultiXactIdExpand assume
-		 * that such multis are never passed.
-		 */
-		if (HEAP_LOCKED_UPGRADED(old_infomask))
-		{
-			old_infomask &= ~HEAP_XMAX_IS_MULTI;
-			old_infomask |= HEAP_XMAX_INVALID;
-			goto l5;
-		}
-
-		/*
-		 * If the XMAX is already a MultiXactId, then we need to expand it to
-		 * include add_to_xmax; but if all the members were lockers and are
-		 * all gone, we can do away with the IS_MULTI bit and just set
-		 * add_to_xmax as the only locker/updater.  If all lockers are gone
-		 * and we have an updater that aborted, we can also do without a
-		 * multi.
-		 *
-		 * The cost of doing GetMultiXactIdMembers would be paid by
-		 * MultiXactIdExpand if we weren't to do this, so this check is not
-		 * incurring extra work anyhow.
-		 */
-		if (!MultiXactIdIsRunning(xmax, HEAP_XMAX_IS_LOCKED_ONLY(old_infomask)))
-		{
-			if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) ||
-				!TransactionIdDidCommit(MultiXactIdGetUpdateXid(xmax,
-																old_infomask)))
-			{
-				/*
-				 * Reset these bits and restart; otherwise fall through to
-				 * create a new multi below.
-				 */
-				old_infomask &= ~HEAP_XMAX_IS_MULTI;
-				old_infomask |= HEAP_XMAX_INVALID;
-				goto l5;
-			}
-		}
-
-		new_status = get_mxact_status_for_lock(mode, is_update);
-
-		new_xmax = MultiXactIdExpand((MultiXactId) xmax, add_to_xmax,
-									 new_status);
-		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
-	}
-	else if (old_infomask & HEAP_XMAX_COMMITTED)
-	{
-		/*
-		 * It's a committed update, so we need to preserve him as updater of
-		 * the tuple.
-		 */
-		MultiXactStatus status;
-		MultiXactStatus new_status;
-
-		if (old_infomask2 & HEAP_KEYS_UPDATED)
-			status = MultiXactStatusUpdate;
-		else
-			status = MultiXactStatusNoKeyUpdate;
-
-		new_status = get_mxact_status_for_lock(mode, is_update);
-
-		/*
-		 * since it's not running, it's obviously impossible for the old
-		 * updater to be identical to the current one, so we need not check
-		 * for that case as we do in the block above.
-		 */
-		new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
-		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
-	}
-	else if (TransactionIdIsInProgress(xmax))
-	{
-		/*
-		 * If the XMAX is a valid, in-progress TransactionId, then we need to
-		 * create a new MultiXactId that includes both the old locker or
-		 * updater and our own TransactionId.
-		 */
-		MultiXactStatus new_status;
-		MultiXactStatus old_status;
-		LockTupleMode old_mode;
-
-		if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
-		{
-			if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
-				old_status = MultiXactStatusForKeyShare;
-			else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
-				old_status = MultiXactStatusForShare;
-			else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
-			{
-				if (old_infomask2 & HEAP_KEYS_UPDATED)
-					old_status = MultiXactStatusForUpdate;
-				else
-					old_status = MultiXactStatusForNoKeyUpdate;
-			}
-			else
-			{
-				/*
-				 * LOCK_ONLY can be present alone only when a page has been
-				 * upgraded by pg_upgrade.  But in that case,
-				 * TransactionIdIsInProgress() should have returned false.  We
-				 * assume it's no longer locked in this case.
-				 */
-				elog(WARNING, "LOCK_ONLY found for Xid in progress %u", xmax);
-				old_infomask |= HEAP_XMAX_INVALID;
-				old_infomask &= ~HEAP_XMAX_LOCK_ONLY;
-				goto l5;
-			}
-		}
-		else
-		{
-			/* it's an update, but which kind? */
-			if (old_infomask2 & HEAP_KEYS_UPDATED)
-				old_status = MultiXactStatusUpdate;
-			else
-				old_status = MultiXactStatusNoKeyUpdate;
-		}
-
-		old_mode = TUPLOCK_from_mxstatus(old_status);
-
-		/*
-		 * If the lock to be acquired is for the same TransactionId as the
-		 * existing lock, there's an optimization possible: consider only the
-		 * strongest of both locks as the only one present, and restart.
-		 */
-		if (xmax == add_to_xmax)
-		{
-			/*
-			 * Note that it's not possible for the original tuple to be
-			 * updated: we wouldn't be here because the tuple would have been
-			 * invisible and we wouldn't try to update it.  As a subtlety,
-			 * this code can also run when traversing an update chain to lock
-			 * future versions of a tuple.  But we wouldn't be here either,
-			 * because the add_to_xmax would be different from the original
-			 * updater.
-			 */
-			Assert(HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
-
-			/* acquire the strongest of both */
-			if (mode < old_mode)
-				mode = old_mode;
-			/* mustn't touch is_update */
-
-			old_infomask |= HEAP_XMAX_INVALID;
-			goto l5;
-		}
-
-		/* otherwise, just fall back to creating a new multixact */
-		new_status = get_mxact_status_for_lock(mode, is_update);
-		new_xmax = MultiXactIdCreate(xmax, old_status,
-									 add_to_xmax, new_status);
-		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
-	}
-	else if (!HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) &&
-			 TransactionIdDidCommit(xmax))
-	{
-		/*
-		 * It's a committed update, so we gotta preserve him as updater of the
-		 * tuple.
-		 */
-		MultiXactStatus status;
-		MultiXactStatus new_status;
-
-		if (old_infomask2 & HEAP_KEYS_UPDATED)
-			status = MultiXactStatusUpdate;
-		else
-			status = MultiXactStatusNoKeyUpdate;
-
-		new_status = get_mxact_status_for_lock(mode, is_update);
-
-		/*
-		 * since it's not running, it's obviously impossible for the old
-		 * updater to be identical to the current one, so we need not check
-		 * for that case as we do in the block above.
-		 */
-		new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
-		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
-	}
-	else
-	{
-		/*
-		 * Can get here iff the locking/updating transaction was running when
-		 * the infomask was extracted from the tuple, but finished before
-		 * TransactionIdIsInProgress got to run.  Deal with it as if there was
-		 * no locker at all in the first place.
-		 */
-		old_infomask |= HEAP_XMAX_INVALID;
-		goto l5;
-	}
-
-	*result_infomask = new_infomask;
-	*result_infomask2 = new_infomask2;
-	*result_xmax = new_xmax;
-}
-
-/*
- * Subroutine for heap_lock_updated_tuple_rec.
- *
- * Given a hypothetical multixact status held by the transaction identified
- * with the given xid, does the current transaction need to wait, fail, or can
- * it continue if it wanted to acquire a lock of the given mode?  "needwait"
- * is set to true if waiting is necessary; if it can continue, then
- * HeapTupleMayBeUpdated is returned.  If the lock is already held by the
- * current transaction, return HeapTupleSelfUpdated.  In case of a conflict
- * with another transaction, a different HeapTupleSatisfiesUpdate return code
- * is returned.
- *
- * The held status is said to be hypothetical because it might correspond to a
- * lock held by a single Xid, i.e. not a real MultiXactId; we express it this
- * way for simplicity of API.
- */
-static HTSU_Result
-test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid,
-						   LockTupleMode mode, bool *needwait)
-{
-	MultiXactStatus wantedstatus;
-
-	*needwait = false;
-	wantedstatus = get_mxact_status_for_lock(mode, false);
-
-	/*
-	 * Note: we *must* check TransactionIdIsInProgress before
-	 * TransactionIdDidAbort/Commit; see comment at top of tqual.c for an
-	 * explanation.
-	 */
-	if (TransactionIdIsCurrentTransactionId(xid))
-	{
-		/*
-		 * The tuple has already been locked by our own transaction.  This is
-		 * very rare but can happen if multiple transactions are trying to
-		 * lock an ancient version of the same tuple.
-		 */
-		return HeapTupleSelfUpdated;
-	}
-	else if (TransactionIdIsInProgress(xid))
-	{
-		/*
-		 * If the locking transaction is running, what we do depends on
-		 * whether the lock modes conflict: if they do, then we must wait for
-		 * it to finish; otherwise we can fall through to lock this tuple
-		 * version without waiting.
-		 */
-		if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
-								LOCKMODE_from_mxstatus(wantedstatus)))
-		{
-			*needwait = true;
-		}
-
-		/*
-		 * If we set needwait above, then this value doesn't matter;
-		 * otherwise, this value signals to caller that it's okay to proceed.
-		 */
-		return HeapTupleMayBeUpdated;
-	}
-	else if (TransactionIdDidAbort(xid))
-		return HeapTupleMayBeUpdated;
-	else if (TransactionIdDidCommit(xid))
-	{
-		/*
-		 * The other transaction committed.  If it was only a locker, then the
-		 * lock is completely gone now and we can return success; but if it
-		 * was an update, then what we do depends on whether the two lock
-		 * modes conflict.  If they conflict, then we must report error to
-		 * caller. But if they don't, we can fall through to allow the current
-		 * transaction to lock the tuple.
-		 *
-		 * Note: the reason we worry about ISUPDATE here is because as soon as
-		 * a transaction ends, all its locks are gone and meaningless, and
-		 * thus we can ignore them; whereas its updates persist.  In the
-		 * TransactionIdIsInProgress case, above, we don't need to check
-		 * because we know the lock is still "alive" and thus a conflict needs
-		 * always be checked.
-		 */
-		if (!ISUPDATE_from_mxstatus(status))
-			return HeapTupleMayBeUpdated;
-
-		if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
-								LOCKMODE_from_mxstatus(wantedstatus)))
-			/* bummer */
-			return HeapTupleUpdated;
-
-		return HeapTupleMayBeUpdated;
-	}
-
-	/* Not in progress, not aborted, not committed -- must have crashed */
-	return HeapTupleMayBeUpdated;
-}
-
-
-/*
- * Recursive part of heap_lock_updated_tuple
- *
- * Fetch the tuple pointed to by tid in rel, and mark it as locked by the given
- * xid with the given mode; if this tuple is updated, recurse to lock the new
- * version as well.
- */
-static HTSU_Result
-heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid,
-							LockTupleMode mode)
-{
-	HTSU_Result result;
-	ItemPointerData tupid;
-	HeapTupleData mytup;
-	Buffer		buf;
-	uint16		new_infomask,
-				new_infomask2,
-				old_infomask,
-				old_infomask2;
-	TransactionId xmax,
-				new_xmax;
-	TransactionId priorXmax = InvalidTransactionId;
-	bool		cleared_all_frozen = false;
-	Buffer		vmbuffer = InvalidBuffer;
-	BlockNumber block;
-
-	ItemPointerCopy(tid, &tupid);
-
-	for (;;)
-	{
-		new_infomask = 0;
-		new_xmax = InvalidTransactionId;
-		block = ItemPointerGetBlockNumber(&tupid);
-		ItemPointerCopy(&tupid, &(mytup.t_self));
-
-		if (!heap_fetch(rel, SnapshotAny, &mytup, &buf, false, NULL))
-		{
-			/*
-			 * if we fail to find the updated version of the tuple, it's
-			 * because it was vacuumed/pruned away after its creator
-			 * transaction aborted.  So behave as if we got to the end of the
-			 * chain, and there's no further tuple to lock: return success to
-			 * caller.
-			 */
-			return HeapTupleMayBeUpdated;
-		}
-
-l4:
-		CHECK_FOR_INTERRUPTS();
-
-		/*
-		 * Before locking the buffer, pin the visibility map page if it
-		 * appears to be necessary.  Since we haven't got the lock yet,
-		 * someone else might be in the middle of changing this, so we'll need
-		 * to recheck after we have the lock.
-		 */
-		if (PageIsAllVisible(BufferGetPage(buf)))
-			visibilitymap_pin(rel, block, &vmbuffer);
-		else
-			vmbuffer = InvalidBuffer;
-
-		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
-
-		/*
-		 * If we didn't pin the visibility map page and the page has become
-		 * all visible while we were busy locking the buffer, we'll have to
-		 * unlock and re-lock, to avoid holding the buffer lock across I/O.
-		 * That's a bit unfortunate, but hopefully shouldn't happen often.
-		 */
-		if (vmbuffer == InvalidBuffer && PageIsAllVisible(BufferGetPage(buf)))
-		{
-			LockBuffer(buf, BUFFER_LOCK_UNLOCK);
-			visibilitymap_pin(rel, block, &vmbuffer);
-			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
-		}
-
-		/*
-		 * Check the tuple XMIN against prior XMAX, if any.  If we reached the
-		 * end of the chain, we're done, so return success.
-		 */
-		if (TransactionIdIsValid(priorXmax) &&
-			!TransactionIdEquals(HeapTupleHeaderGetXmin(mytup.t_data),
-								 priorXmax))
-		{
-			result = HeapTupleMayBeUpdated;
-			goto out_locked;
-		}
-
-		/*
-		 * Also check Xmin: if this tuple was created by an aborted
-		 * (sub)transaction, then we already locked the last live one in the
-		 * chain, thus we're done, so return success.
-		 */
-		if (TransactionIdDidAbort(HeapTupleHeaderGetXmin(mytup.t_data)))
-		{
-			UnlockReleaseBuffer(buf);
-			return HeapTupleMayBeUpdated;
-		}
-
-		old_infomask = mytup.t_data->t_infomask;
-		old_infomask2 = mytup.t_data->t_infomask2;
-		xmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
-
-		/*
-		 * If this tuple version has been updated or locked by some concurrent
-		 * transaction(s), what we do depends on whether our lock mode
-		 * conflicts with what those other transactions hold, and also on the
-		 * status of them.
-		 */
-		if (!(old_infomask & HEAP_XMAX_INVALID))
-		{
-			TransactionId rawxmax;
-			bool		needwait;
-
-			rawxmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
-			if (old_infomask & HEAP_XMAX_IS_MULTI)
-			{
-				int			nmembers;
-				int			i;
-				MultiXactMember *members;
-
-				/*
-				 * We don't need a test for pg_upgrade'd tuples: this is only
-				 * applied to tuples after the first in an update chain.  Said
-				 * first tuple in the chain may well be locked-in-9.2-and-
-				 * pg_upgraded, but that one was already locked by our caller,
-				 * not us; and any subsequent ones cannot be because our
-				 * caller must necessarily have obtained a snapshot later than
-				 * the pg_upgrade itself.
-				 */
-				Assert(!HEAP_LOCKED_UPGRADED(mytup.t_data->t_infomask));
-
-				nmembers = GetMultiXactIdMembers(rawxmax, &members, false,
-												 HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
-				for (i = 0; i < nmembers; i++)
-				{
-					result = test_lockmode_for_conflict(members[i].status,
-														members[i].xid,
-														mode, &needwait);
-
-					/*
-					 * If the tuple was already locked by ourselves in a
-					 * previous iteration of this (say heap_lock_tuple was
-					 * forced to restart the locking loop because of a change
-					 * in xmax), then we hold the lock already on this tuple
-					 * version and we don't need to do anything; and this is
-					 * not an error condition either.  We just need to skip
-					 * this tuple and continue locking the next version in the
-					 * update chain.
-					 */
-					if (result == HeapTupleSelfUpdated)
-					{
-						pfree(members);
-						goto next;
-					}
-
-					if (needwait)
-					{
-						LockBuffer(buf, BUFFER_LOCK_UNLOCK);
-						XactLockTableWait(members[i].xid, rel,
-										  &mytup.t_self,
-										  XLTW_LockUpdated);
-						pfree(members);
-						goto l4;
-					}
-					if (result != HeapTupleMayBeUpdated)
-					{
-						pfree(members);
-						goto out_locked;
-					}
-				}
-				if (members)
-					pfree(members);
-			}
-			else
-			{
-				MultiXactStatus status;
-
-				/*
-				 * For a non-multi Xmax, we first need to compute the
-				 * corresponding MultiXactStatus by using the infomask bits.
-				 */
-				if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
-				{
-					if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
-						status = MultiXactStatusForKeyShare;
-					else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
-						status = MultiXactStatusForShare;
-					else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
-					{
-						if (old_infomask2 & HEAP_KEYS_UPDATED)
-							status = MultiXactStatusForUpdate;
-						else
-							status = MultiXactStatusForNoKeyUpdate;
-					}
-					else
-					{
-						/*
-						 * LOCK_ONLY present alone (a pg_upgraded tuple marked
-						 * as share-locked in the old cluster) shouldn't be
-						 * seen in the middle of an update chain.
-						 */
-						elog(ERROR, "invalid lock status in tuple");
-					}
-				}
-				else
-				{
-					/* it's an update, but which kind? */
-					if (old_infomask2 & HEAP_KEYS_UPDATED)
-						status = MultiXactStatusUpdate;
-					else
-						status = MultiXactStatusNoKeyUpdate;
-				}
-
-				result = test_lockmode_for_conflict(status, rawxmax, mode,
-													&needwait);
-
-				/*
-				 * If the tuple was already locked by ourselves in a previous
-				 * iteration of this (say heap_lock_tuple was forced to
-				 * restart the locking loop because of a change in xmax), then
-				 * we hold the lock already on this tuple version and we don't
-				 * need to do anything; and this is not an error condition
-				 * either.  We just need to skip this tuple and continue
-				 * locking the next version in the update chain.
-				 */
-				if (result == HeapTupleSelfUpdated)
-					goto next;
-
-				if (needwait)
-				{
-					LockBuffer(buf, BUFFER_LOCK_UNLOCK);
-					XactLockTableWait(rawxmax, rel, &mytup.t_self,
-									  XLTW_LockUpdated);
-					goto l4;
-				}
-				if (result != HeapTupleMayBeUpdated)
-				{
-					goto out_locked;
-				}
-			}
-		}
-
-		/* compute the new Xmax and infomask values for the tuple ... */
-		compute_new_xmax_infomask(xmax, old_infomask, mytup.t_data->t_infomask2,
-								  xid, mode, false,
-								  &new_xmax, &new_infomask, &new_infomask2);
-
-		if (PageIsAllVisible(BufferGetPage(buf)) &&
-			visibilitymap_clear(rel, block, vmbuffer,
-								VISIBILITYMAP_ALL_FROZEN))
-			cleared_all_frozen = true;
-
-		START_CRIT_SECTION();
-
-		/* ... and set them */
-		HeapTupleHeaderSetXmax(mytup.t_data, new_xmax);
-		mytup.t_data->t_infomask &= ~HEAP_XMAX_BITS;
-		mytup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
-		mytup.t_data->t_infomask |= new_infomask;
-		mytup.t_data->t_infomask2 |= new_infomask2;
-
-		MarkBufferDirty(buf);
-
-		/* XLOG stuff */
-		if (RelationNeedsWAL(rel))
-		{
-			xl_heap_lock_updated xlrec;
-			XLogRecPtr	recptr;
-			Page		page = BufferGetPage(buf);
-
-			XLogBeginInsert();
-			XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
-
-			xlrec.offnum = ItemPointerGetOffsetNumber(&mytup.t_self);
-			xlrec.xmax = new_xmax;
-			xlrec.infobits_set = compute_infobits(new_infomask, new_infomask2);
-			xlrec.flags =
-				cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
-
-			XLogRegisterData((char *) &xlrec, SizeOfHeapLockUpdated);
-
-			recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_LOCK_UPDATED);
-
-			PageSetLSN(page, recptr);
-		}
-
-		END_CRIT_SECTION();
-
-next:
-		/* if we find the end of update chain, we're done. */
-		if (mytup.t_data->t_infomask & HEAP_XMAX_INVALID ||
-			ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid) ||
-			HeapTupleHeaderIsOnlyLocked(mytup.t_data))
-		{
-			result = HeapTupleMayBeUpdated;
-			goto out_locked;
-		}
-
-		/* tail recursion */
-		priorXmax = HeapTupleHeaderGetUpdateXid(mytup.t_data);
-		ItemPointerCopy(&(mytup.t_data->t_ctid), &tupid);
-		UnlockReleaseBuffer(buf);
-		if (vmbuffer != InvalidBuffer)
-			ReleaseBuffer(vmbuffer);
-	}
-
-	result = HeapTupleMayBeUpdated;
-
-out_locked:
-	UnlockReleaseBuffer(buf);
-
-	if (vmbuffer != InvalidBuffer)
-		ReleaseBuffer(vmbuffer);
-
-	return result;
-
-}
+	return true;
+}
 
 /*
- * heap_lock_updated_tuple
- *		Follow update chain when locking an updated tuple, acquiring locks (row
- *		marks) on the updated versions.
- *
- * The initial tuple is assumed to be already locked.
- *
- * This function doesn't check visibility, it just unconditionally marks the
- * tuple(s) as locked.  If any tuple in the updated chain is being deleted
- * concurrently (or updated with the key being modified), sleep until the
- * transaction doing it is finished.
+ * Given an original set of Xmax and infomask, and a transaction (identified by
+ * add_to_xmax) acquiring a new lock of some mode, compute the new Xmax and
+ * corresponding infomasks to use on the tuple.
  *
- * Note that we don't acquire heavyweight tuple locks on the tuples we walk
- * when we have to wait for other transactions to release them, as opposed to
- * what heap_lock_tuple does.  The reason is that having more than one
- * transaction walking the chain is probably uncommon enough that risk of
- * starvation is not likely: one of the preconditions for being here is that
- * the snapshot in use predates the update that created this tuple (because we
- * started at an earlier version of the tuple), but at the same time such a
- * transaction cannot be using repeatable read or serializable isolation
- * levels, because that would lead to a serializability failure.
- */
-static HTSU_Result
-heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid,
-						TransactionId xid, LockTupleMode mode)
-{
-	if (!ItemPointerEquals(&tuple->t_self, ctid))
-	{
-		/*
-		 * If this is the first possibly-multixact-able operation in the
-		 * current transaction, set my per-backend OldestMemberMXactId
-		 * setting. We can be certain that the transaction will never become a
-		 * member of any older MultiXactIds than that.  (We have to do this
-		 * even if we end up just using our own TransactionId below, since
-		 * some other backend could incorporate our XID into a MultiXact
-		 * immediately afterwards.)
-		 */
-		MultiXactIdSetOldestMember();
-
-		return heap_lock_updated_tuple_rec(rel, ctid, xid, mode);
-	}
-
-	/* nothing to lock */
-	return HeapTupleMayBeUpdated;
-}
-
-/*
- *	heap_finish_speculative - mark speculative insertion as successful
+ * Note that this might have side effects such as creating a new MultiXactId.
  *
- * To successfully finish a speculative insertion we have to clear speculative
- * token from tuple.  To do so the t_ctid field, which will contain a
- * speculative token value, is modified in place to point to the tuple itself,
- * which is characteristic of a newly inserted ordinary tuple.
+ * Most callers will have called HeapTupleSatisfiesUpdate before this function;
+ * that will have set the HEAP_XMAX_INVALID bit if the xmax was a MultiXactId
+ * but it was not running anymore. There is a race condition, which is that the
+ * MultiXactId may have finished since then, but that uncommon case is handled
+ * either here, or within MultiXactIdExpand.
  *
- * NB: It is not ok to commit without either finishing or aborting a
- * speculative insertion.  We could treat speculative tuples of committed
- * transactions implicitly as completed, but then we would have to be prepared
- * to deal with speculative tokens on committed tuples.  That wouldn't be
- * difficult - no-one looks at the ctid field of a tuple with invalid xmax -
- * but clearing the token at completion isn't very expensive either.
- * An explicit confirmation WAL record also makes logical decoding simpler.
+ * There is a similar race condition possible when the old xmax was a regular
+ * TransactionId.  We test TransactionIdIsInProgress again just to narrow the
+ * window, but it's still possible to end up creating an unnecessary
+ * MultiXactId.  Fortunately this is harmless.
  */
 void
-heap_finish_speculative(Relation relation, HeapTuple tuple)
+compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
+						  uint16 old_infomask2, TransactionId add_to_xmax,
+						  LockTupleMode mode, bool is_update,
+						  TransactionId *result_xmax, uint16 *result_infomask,
+						  uint16 *result_infomask2)
 {
-	Buffer		buffer;
-	Page		page;
-	OffsetNumber offnum;
-	ItemId		lp = NULL;
-	HeapTupleHeader htup;
-
-	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&(tuple->t_self)));
-	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
-	page = (Page) BufferGetPage(buffer);
-
-	offnum = ItemPointerGetOffsetNumber(&(tuple->t_self));
-	if (PageGetMaxOffsetNumber(page) >= offnum)
-		lp = PageGetItemId(page, offnum);
-
-	if (PageGetMaxOffsetNumber(page) < offnum || !ItemIdIsNormal(lp))
-		elog(ERROR, "invalid lp");
-
-	htup = (HeapTupleHeader) PageGetItem(page, lp);
-
-	/* SpecTokenOffsetNumber should be distinguishable from any real offset */
-	StaticAssertStmt(MaxOffsetNumber < SpecTokenOffsetNumber,
-					 "invalid speculative token constant");
-
-	/* NO EREPORT(ERROR) from here till changes are logged */
-	START_CRIT_SECTION();
-
-	Assert(HeapTupleHeaderIsSpeculative(tuple->t_data));
-
-	MarkBufferDirty(buffer);
+	TransactionId new_xmax;
+	uint16		new_infomask,
+				new_infomask2;
 
-	/*
-	 * Replace the speculative insertion token with a real t_ctid, pointing to
-	 * itself like it does on regular tuples.
-	 */
-	htup->t_ctid = tuple->t_self;
+	Assert(TransactionIdIsCurrentTransactionId(add_to_xmax));
 
-	/* XLOG stuff */
-	if (RelationNeedsWAL(relation))
+l5:
+	new_infomask = 0;
+	new_infomask2 = 0;
+	if (old_infomask & HEAP_XMAX_INVALID)
 	{
-		xl_heap_confirm xlrec;
-		XLogRecPtr	recptr;
-
-		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
-
-		XLogBeginInsert();
-
-		/* We want the same filtering on this as on a plain insert */
-		XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
-
-		XLogRegisterData((char *) &xlrec, SizeOfHeapConfirm);
-		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
-
-		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_CONFIRM);
-
-		PageSetLSN(page, recptr);
-	}
-
-	END_CRIT_SECTION();
-
-	UnlockReleaseBuffer(buffer);
-}
-
-/*
- *	heap_abort_speculative - kill a speculatively inserted tuple
- *
- * Marks a tuple that was speculatively inserted in the same command as dead,
- * by setting its xmin as invalid.  That makes it immediately appear as dead
- * to all transactions, including our own.  In particular, it makes
- * HeapTupleSatisfiesDirty() regard the tuple as dead, so that another backend
- * inserting a duplicate key value won't unnecessarily wait for our whole
- * transaction to finish (it'll just wait for our speculative insertion to
- * finish).
- *
- * Killing the tuple prevents "unprincipled deadlocks", which are deadlocks
- * that arise due to a mutual dependency that is not user visible.  By
- * definition, unprincipled deadlocks cannot be prevented by the user
- * reordering lock acquisition in client code, because the implementation level
- * lock acquisitions are not under the user's direct control.  If speculative
- * inserters did not take this precaution, then under high concurrency they
- * could deadlock with each other, which would not be acceptable.
- *
- * This is somewhat redundant with heap_delete, but we prefer to have a
- * dedicated routine with stripped down requirements.  Note that this is also
- * used to delete the TOAST tuples created during speculative insertion.
- *
- * This routine does not affect logical decoding as it only looks at
- * confirmation records.
- */
-void
-heap_abort_speculative(Relation relation, HeapTuple tuple)
-{
-	TransactionId xid = GetCurrentTransactionId();
-	ItemPointer tid = &(tuple->t_self);
-	ItemId		lp;
-	HeapTupleData tp;
-	Page		page;
-	BlockNumber block;
-	Buffer		buffer;
-
-	Assert(ItemPointerIsValid(tid));
-
-	block = ItemPointerGetBlockNumber(tid);
-	buffer = ReadBuffer(relation, block);
-	page = BufferGetPage(buffer);
-
-	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
-
-	/*
-	 * Page can't be all visible, we just inserted into it, and are still
-	 * running.
-	 */
-	Assert(!PageIsAllVisible(page));
-
-	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
-	Assert(ItemIdIsNormal(lp));
-
-	tp.t_tableOid = RelationGetRelid(relation);
-	tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
-	tp.t_len = ItemIdGetLength(lp);
-	tp.t_self = *tid;
-
-	/*
-	 * Sanity check that the tuple really is a speculatively inserted tuple,
-	 * inserted by us.
-	 */
-	if (tp.t_data->t_choice.t_heap.t_xmin != xid)
-		elog(ERROR, "attempted to kill a tuple inserted by another transaction");
-	if (!(IsToastRelation(relation) || HeapTupleHeaderIsSpeculative(tp.t_data)))
-		elog(ERROR, "attempted to kill a non-speculative tuple");
-	Assert(!HeapTupleHeaderIsHeapOnly(tp.t_data));
+		/*
+		 * No previous locker; we just insert our own TransactionId.
+		 *
+		 * Note that it's critical that this case be the first one checked,
+		 * because there are several blocks below that come back to this one
+		 * to implement certain optimizations; old_infomask might contain
+		 * other dirty bits in those cases, but we don't really care.
+		 */
+		if (is_update)
+		{
+			new_xmax = add_to_xmax;
+			if (mode == LockTupleExclusive)
+				new_infomask2 |= HEAP_KEYS_UPDATED;
+		}
+		else
+		{
+			new_infomask |= HEAP_XMAX_LOCK_ONLY;
+			switch (mode)
+			{
+				case LockTupleKeyShare:
+					new_xmax = add_to_xmax;
+					new_infomask |= HEAP_XMAX_KEYSHR_LOCK;
+					break;
+				case LockTupleShare:
+					new_xmax = add_to_xmax;
+					new_infomask |= HEAP_XMAX_SHR_LOCK;
+					break;
+				case LockTupleNoKeyExclusive:
+					new_xmax = add_to_xmax;
+					new_infomask |= HEAP_XMAX_EXCL_LOCK;
+					break;
+				case LockTupleExclusive:
+					new_xmax = add_to_xmax;
+					new_infomask |= HEAP_XMAX_EXCL_LOCK;
+					new_infomask2 |= HEAP_KEYS_UPDATED;
+					break;
+				default:
+					new_xmax = InvalidTransactionId;	/* silence compiler */
+					elog(ERROR, "invalid lock mode");
+			}
+		}
+	}
+	else if (old_infomask & HEAP_XMAX_IS_MULTI)
+	{
+		MultiXactStatus new_status;
 
-	/*
-	 * No need to check for serializable conflicts here.  There is never a
-	 * need for a combocid, either.  No need to extract replica identity, or
-	 * do anything special with infomask bits.
-	 */
+		/*
+		 * Currently we don't allow XMAX_COMMITTED to be set for multis, so
+		 * cross-check.
+		 */
+		Assert(!(old_infomask & HEAP_XMAX_COMMITTED));
 
-	START_CRIT_SECTION();
+		/*
+		 * A multixact together with LOCK_ONLY set but neither lock bit set
+		 * (i.e. a pg_upgraded share locked tuple) cannot possibly be running
+		 * anymore.  This check is critical for databases upgraded by
+		 * pg_upgrade; both MultiXactIdIsRunning and MultiXactIdExpand assume
+		 * that such multis are never passed.
+		 */
+		if (HEAP_LOCKED_UPGRADED(old_infomask))
+		{
+			old_infomask &= ~HEAP_XMAX_IS_MULTI;
+			old_infomask |= HEAP_XMAX_INVALID;
+			goto l5;
+		}
 
-	/*
-	 * The tuple will become DEAD immediately.  Flag that this page
-	 * immediately is a candidate for pruning by setting xmin to
-	 * RecentGlobalXmin.  That's not pretty, but it doesn't seem worth
-	 * inventing a nicer API for this.
-	 */
-	Assert(TransactionIdIsValid(RecentGlobalXmin));
-	PageSetPrunable(page, RecentGlobalXmin);
+		/*
+		 * If the XMAX is already a MultiXactId, then we need to expand it to
+		 * include add_to_xmax; but if all the members were lockers and are
+		 * all gone, we can do away with the IS_MULTI bit and just set
+		 * add_to_xmax as the only locker/updater.  If all lockers are gone
+		 * and we have an updater that aborted, we can also do without a
+		 * multi.
+		 *
+		 * The cost of doing GetMultiXactIdMembers would be paid by
+		 * MultiXactIdExpand if we weren't to do this, so this check is not
+		 * incurring extra work anyhow.
+		 */
+		if (!MultiXactIdIsRunning(xmax, HEAP_XMAX_IS_LOCKED_ONLY(old_infomask)))
+		{
+			if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) ||
+				!TransactionIdDidCommit(MultiXactIdGetUpdateXid(xmax,
+																old_infomask)))
+			{
+				/*
+				 * Reset these bits and restart; otherwise fall through to
+				 * create a new multi below.
+				 */
+				old_infomask &= ~HEAP_XMAX_IS_MULTI;
+				old_infomask |= HEAP_XMAX_INVALID;
+				goto l5;
+			}
+		}
 
-	/* store transaction information of xact deleting the tuple */
-	tp.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
-	tp.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+		new_status = get_mxact_status_for_lock(mode, is_update);
 
-	/*
-	 * Set the tuple header xmin to InvalidTransactionId.  This makes the
-	 * tuple immediately invisible everyone.  (In particular, to any
-	 * transactions waiting on the speculative token, woken up later.)
-	 */
-	HeapTupleHeaderSetXmin(tp.t_data, InvalidTransactionId);
+		new_xmax = MultiXactIdExpand((MultiXactId) xmax, add_to_xmax,
+									 new_status);
+		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
+	}
+	else if (old_infomask & HEAP_XMAX_COMMITTED)
+	{
+		/*
+		 * It's a committed update, so we need to preserve him as updater of
+		 * the tuple.
+		 */
+		MultiXactStatus status;
+		MultiXactStatus new_status;
 
-	/* Clear the speculative insertion token too */
-	tp.t_data->t_ctid = tp.t_self;
+		if (old_infomask2 & HEAP_KEYS_UPDATED)
+			status = MultiXactStatusUpdate;
+		else
+			status = MultiXactStatusNoKeyUpdate;
 
-	MarkBufferDirty(buffer);
+		new_status = get_mxact_status_for_lock(mode, is_update);
 
-	/*
-	 * XLOG stuff
-	 *
-	 * The WAL records generated here match heap_delete().  The same recovery
-	 * routines are used.
-	 */
-	if (RelationNeedsWAL(relation))
+		/*
+		 * since it's not running, it's obviously impossible for the old
+		 * updater to be identical to the current one, so we need not check
+		 * for that case as we do in the block above.
+		 */
+		new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
+		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
+	}
+	else if (TransactionIdIsInProgress(xmax))
 	{
-		xl_heap_delete xlrec;
-		XLogRecPtr	recptr;
+		/*
+		 * If the XMAX is a valid, in-progress TransactionId, then we need to
+		 * create a new MultiXactId that includes both the old locker or
+		 * updater and our own TransactionId.
+		 */
+		MultiXactStatus new_status;
+		MultiXactStatus old_status;
+		LockTupleMode old_mode;
 
-		xlrec.flags = XLH_DELETE_IS_SUPER;
-		xlrec.infobits_set = compute_infobits(tp.t_data->t_infomask,
-											  tp.t_data->t_infomask2);
-		xlrec.offnum = ItemPointerGetOffsetNumber(&tp.t_self);
-		xlrec.xmax = xid;
+		if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
+		{
+			if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
+				old_status = MultiXactStatusForKeyShare;
+			else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
+				old_status = MultiXactStatusForShare;
+			else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
+			{
+				if (old_infomask2 & HEAP_KEYS_UPDATED)
+					old_status = MultiXactStatusForUpdate;
+				else
+					old_status = MultiXactStatusForNoKeyUpdate;
+			}
+			else
+			{
+				/*
+				 * LOCK_ONLY can be present alone only when a page has been
+				 * upgraded by pg_upgrade.  But in that case,
+				 * TransactionIdIsInProgress() should have returned false.  We
+				 * assume it's no longer locked in this case.
+				 */
+				elog(WARNING, "LOCK_ONLY found for Xid in progress %u", xmax);
+				old_infomask |= HEAP_XMAX_INVALID;
+				old_infomask &= ~HEAP_XMAX_LOCK_ONLY;
+				goto l5;
+			}
+		}
+		else
+		{
+			/* it's an update, but which kind? */
+			if (old_infomask2 & HEAP_KEYS_UPDATED)
+				old_status = MultiXactStatusUpdate;
+			else
+				old_status = MultiXactStatusNoKeyUpdate;
+		}
 
-		XLogBeginInsert();
-		XLogRegisterData((char *) &xlrec, SizeOfHeapDelete);
-		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
+		old_mode = TUPLOCK_from_mxstatus(old_status);
+
+		/*
+		 * If the lock to be acquired is for the same TransactionId as the
+		 * existing lock, there's an optimization possible: consider only the
+		 * strongest of both locks as the only one present, and restart.
+		 */
+		if (xmax == add_to_xmax)
+		{
+			/*
+			 * Note that it's not possible for the original tuple to be
+			 * updated: we wouldn't be here because the tuple would have been
+			 * invisible and we wouldn't try to update it.  As a subtlety,
+			 * this code can also run when traversing an update chain to lock
+			 * future versions of a tuple.  But we wouldn't be here either,
+			 * because the add_to_xmax would be different from the original
+			 * updater.
+			 */
+			Assert(HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
 
-		/* No replica identity & replication origin logged */
+			/* acquire the strongest of both */
+			if (mode < old_mode)
+				mode = old_mode;
+			/* mustn't touch is_update */
 
-		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_DELETE);
+			old_infomask |= HEAP_XMAX_INVALID;
+			goto l5;
+		}
 
-		PageSetLSN(page, recptr);
+		/* otherwise, just fall back to creating a new multixact */
+		new_status = get_mxact_status_for_lock(mode, is_update);
+		new_xmax = MultiXactIdCreate(xmax, old_status,
+									 add_to_xmax, new_status);
+		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
 	}
+	else if (!HEAP_XMAX_IS_LOCKED_ONLY(old_infomask) &&
+			 TransactionIdDidCommit(xmax))
+	{
+		/*
+		 * It's a committed update, so we gotta preserve him as updater of the
+		 * tuple.
+		 */
+		MultiXactStatus status;
+		MultiXactStatus new_status;
 
-	END_CRIT_SECTION();
+		if (old_infomask2 & HEAP_KEYS_UPDATED)
+			status = MultiXactStatusUpdate;
+		else
+			status = MultiXactStatusNoKeyUpdate;
 
-	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+		new_status = get_mxact_status_for_lock(mode, is_update);
 
-	if (HeapTupleHasExternal(&tp))
+		/*
+		 * since it's not running, it's obviously impossible for the old
+		 * updater to be identical to the current one, so we need not check
+		 * for that case as we do in the block above.
+		 */
+		new_xmax = MultiXactIdCreate(xmax, status, add_to_xmax, new_status);
+		GetMultiXactIdHintBits(new_xmax, &new_infomask, &new_infomask2);
+	}
+	else
 	{
-		Assert(!IsToastRelation(relation));
-		toast_delete(relation, &tp, true);
+		/*
+		 * Can get here iff the locking/updating transaction was running when
+		 * the infomask was extracted from the tuple, but finished before
+		 * TransactionIdIsInProgress got to run.  Deal with it as if there was
+		 * no locker at all in the first place.
+		 */
+		old_infomask |= HEAP_XMAX_INVALID;
+		goto l5;
 	}
 
-	/*
-	 * Never need to mark tuple for invalidation, since catalogs don't support
-	 * speculative insertion
-	 */
-
-	/* Now we can release the buffer */
-	ReleaseBuffer(buffer);
-
-	/* count deletion, as we counted the insertion too */
-	pgstat_count_heap_delete(relation);
+	*result_infomask = new_infomask;
+	*result_infomask2 = new_infomask2;
+	*result_xmax = new_xmax;
 }
 
 /*
@@ -7012,7 +4957,7 @@ HeapTupleGetUpdateXid(HeapTupleHeader tuple)
  *
  * The passed infomask pairs up with the given multixact in the tuple header.
  */
-static bool
+bool
 DoesMultiXactIdConflict(MultiXactId multi, uint16 infomask,
 						LockTupleMode lockmode)
 {
@@ -7179,7 +5124,7 @@ Do_MultiXactIdWait(MultiXactId multi, MultiXactStatus status,
  * We return (in *remaining, if not NULL) the number of members that are still
  * running, including any (non-aborted) subtransactions of our own transaction.
  */
-static void
+void
 MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
 				Relation rel, ItemPointer ctid, XLTW_Oper oper,
 				int *remaining)
@@ -7201,7 +5146,7 @@ MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
  * We return (in *remaining, if not NULL) the number of members that are still
  * running, including any (non-aborted) subtransactions of our own transaction.
  */
-static bool
+bool
 ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
 						   uint16 infomask, Relation rel, int *remaining)
 {
@@ -7763,7 +5708,7 @@ log_heap_update(Relation reln, Buffer oldbuf,
  * This is only used in wal_level >= WAL_LEVEL_LOGICAL, and only for catalog
  * tuples.
  */
-static XLogRecPtr
+XLogRecPtr
 log_heap_new_cid(Relation relation, HeapTuple tup)
 {
 	xl_heap_new_cid xlrec;
@@ -9139,46 +7084,6 @@ heap2_redo(XLogReaderState *record)
 	}
 }
 
-/*
- *	heap_sync		- sync a heap, for use when no WAL has been written
- *
- * This forces the heap contents (including TOAST heap if any) down to disk.
- * If we skipped using WAL, and WAL is otherwise needed, we must force the
- * relation down to disk before it's safe to commit the transaction.  This
- * requires writing out any dirty buffers and then doing a forced fsync.
- *
- * Indexes are not touched.  (Currently, index operations associated with
- * the commands that use this are WAL-logged and so do not need fsync.
- * That behavior might change someday, but in any case it's likely that
- * any fsync decisions required would be per-index and hence not appropriate
- * to be done here.)
- */
-void
-heap_sync(Relation rel)
-{
-	/* non-WAL-logged tables never need fsync */
-	if (!RelationNeedsWAL(rel))
-		return;
-
-	/* main heap */
-	FlushRelationBuffers(rel);
-	/* FlushRelationBuffers will have opened rd_smgr */
-	smgrimmedsync(rel->rd_smgr, MAIN_FORKNUM);
-
-	/* FSM is not critical, don't bother syncing it */
-
-	/* toast heap, if any */
-	if (OidIsValid(rel->rd_rel->reltoastrelid))
-	{
-		Relation	toastrel;
-
-		toastrel = heap_open(rel->rd_rel->reltoastrelid, AccessShareLock);
-		FlushRelationBuffers(toastrel);
-		smgrimmedsync(toastrel->rd_smgr, MAIN_FORKNUM);
-		heap_close(toastrel, AccessShareLock);
-	}
-}
-
 /*
  * Mask a heap page before performing consistency checks on it.
  */
diff --git a/src/backend/access/heap/heapam_common.c b/src/backend/access/heap/heapam_common.c
index 6ebe0b290c..342c575b8c 100644
--- a/src/backend/access/heap/heapam_common.c
+++ b/src/backend/access/heap/heapam_common.c
@@ -24,9 +24,11 @@
 #include "access/heapam.h"
 #include "access/heapam_common.h"
 #include "access/htup_details.h"
+#include "access/storageamapi.h"
 #include "access/transam.h"
 #include "access/xact.h"
 #include "access/xlog.h"
+#include "executor/tuptable.h"
 #include "storage/bufmgr.h"
 #include "storage/procarray.h"
 
diff --git a/src/backend/access/heap/heapam_storage.c b/src/backend/access/heap/heapam_storage.c
index a874e243c9..567e80de8c 100644
--- a/src/backend/access/heap/heapam_storage.c
+++ b/src/backend/access/heap/heapam_storage.c
@@ -48,6 +48,18 @@
 #include "utils/rel.h"
 #include "utils/tqual.h"
 
+/* local functions */
+static bool heap_fetch(Relation relation,
+		   ItemPointer tid,
+		   Snapshot snapshot,
+		   HeapTuple tuple,
+		   Buffer *userbuf,
+		   bool keep_buf,
+		   Relation stats_relation);
+
+static void heapam_finish_speculative(Relation relation, TupleTableSlot *slot);
+static void heapam_abort_speculative(Relation relation, TupleTableSlot *slot);
+
 /*-------------------------------------------------------------------------
  *
  * POSTGRES "time qualification" code, ie, tuple visibility rules.
@@ -1688,10 +1700,2195 @@ HeapTupleSatisfies(StorageTuple stup, Snapshot snapshot, Buffer buffer)
 	}
 }
 
-Datum
-heapam_storage_handler(PG_FUNCTION_ARGS)
+
+/*
+ *	heap_fetch		- retrieve tuple with given tid
+ *
+ * On entry, tuple->t_self is the TID to fetch.  We pin the buffer holding
+ * the tuple, fill in the remaining fields of *tuple, and check the tuple
+ * against the specified snapshot.
+ *
+ * If successful (tuple found and passes snapshot time qual), then *userbuf
+ * is set to the buffer holding the tuple and TRUE is returned.  The caller
+ * must unpin the buffer when done with the tuple.
+ *
+ * If the tuple is not found (ie, item number references a deleted slot),
+ * then tuple->t_data is set to NULL and FALSE is returned.
+ *
+ * If the tuple is found but fails the time qual check, then FALSE is returned
+ * but tuple->t_data is left pointing to the tuple.
+ *
+ * keep_buf determines what is done with the buffer in the FALSE-result cases.
+ * When the caller specifies keep_buf = true, we retain the pin on the buffer
+ * and return it in *userbuf (so the caller must eventually unpin it); when
+ * keep_buf = false, the pin is released and *userbuf is set to InvalidBuffer.
+ *
+ * stats_relation is the relation to charge the heap_fetch operation against
+ * for statistical purposes.  (This could be the heap rel itself, an
+ * associated index, or NULL to not count the fetch at all.)
+ *
+ * heap_fetch does not follow HOT chains: only the exact TID requested will
+ * be fetched.
+ *
+ * It is somewhat inconsistent that we ereport() on invalid block number but
+ * return false on invalid item number.  There are a couple of reasons though.
+ * One is that the caller can relatively easily check the block number for
+ * validity, but cannot check the item number without reading the page
+ * himself.  Another is that when we are following a t_ctid link, we can be
+ * reasonably confident that the page number is valid (since VACUUM shouldn't
+ * truncate off the destination page without having killed the referencing
+ * tuple first), but the item number might well not be good.
+ */
+static bool
+heap_fetch(Relation relation,
+		   ItemPointer tid,
+		   Snapshot snapshot,
+		   HeapTuple tuple,
+		   Buffer *userbuf,
+		   bool keep_buf,
+		   Relation stats_relation)
 {
-	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
+	ItemId		lp;
+	Buffer		buffer;
+	Page		page;
+	OffsetNumber offnum;
+	bool		valid;
+
+	/*
+	 * Fetch and pin the appropriate page of the relation.
+	 */
+	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
+
+	/*
+	 * Need share lock on buffer to examine tuple commit status.
+	 */
+	LockBuffer(buffer, BUFFER_LOCK_SHARE);
+	page = BufferGetPage(buffer);
+	TestForOldSnapshot(snapshot, relation, page);
+
+	/*
+	 * We'd better check for out-of-range offnum in case of VACUUM since the
+	 * TID was obtained.
+	 */
+	offnum = ItemPointerGetOffsetNumber(tid);
+	if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
+	{
+		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+		if (keep_buf)
+			*userbuf = buffer;
+		else
+		{
+			ReleaseBuffer(buffer);
+			*userbuf = InvalidBuffer;
+		}
+		return false;
+	}
+
+	/*
+	 * get the item line pointer corresponding to the requested tid
+	 */
+	lp = PageGetItemId(page, offnum);
+
+	/*
+	 * Must check for deleted tuple.
+	 */
+	if (!ItemIdIsNormal(lp))
+	{
+		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+		if (keep_buf)
+			*userbuf = buffer;
+		else
+		{
+			ReleaseBuffer(buffer);
+			*userbuf = InvalidBuffer;
+		}
+		return false;
+	}
+
+	/*
+	 * 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);
+
+	/*
+	 * check time qualification of tuple, then release lock
+	 */
+	valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, tuple, snapshot, buffer);
+
+	if (valid)
+		PredicateLockTuple(relation, tuple, snapshot);
+
+	CheckForSerializableConflictOut(valid, relation, tuple, buffer, snapshot);
+
+	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+
+	if (valid)
+	{
+		/*
+		 * All checks passed, so return the tuple as valid. Caller is now
+		 * responsible for releasing the buffer.
+		 */
+		*userbuf = buffer;
+
+		/* Count the successful fetch against appropriate rel, if any */
+		if (stats_relation != NULL)
+			pgstat_count_heap_fetch(stats_relation);
+
+		return true;
+	}
+
+	/* Tuple failed time qual, but maybe caller wants to see it anyway. */
+	if (keep_buf)
+		*userbuf = buffer;
+	else
+	{
+		ReleaseBuffer(buffer);
+		*userbuf = InvalidBuffer;
+	}
+
+	return false;
+}
+
+
+
+
+
+/* ----------------------------------------------------------------
+ *				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)
+				heapam_finish_speculative(relation, slot);
+			else
+			{
+				heapam_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->es_result_relation_info->ri_NumIndices > 0) &&
+		(!HeapTupleIsHeapOnly(tuple)))
+		*recheckIndexes = (IndexFunc) (slot, estate, false, NULL, NIL);
+
+	return result;
+}
+
+static void
+heapam_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);
+
+	offnum = ItemPointerGetOffsetNumber(&(tuple->t_self));
+	if (PageGetMaxOffsetNumber(page) >= offnum)
+		lp = PageGetItemId(page, offnum);
+
+	if (PageGetMaxOffsetNumber(page) < offnum || !ItemIdIsNormal(lp))
+		elog(ERROR, "invalid lp");
+
+	htup = (HeapTupleHeader) PageGetItem(page, lp);
+
+	/* SpecTokenOffsetNumber should be distinguishable from any real offset */
+	StaticAssertStmt(MaxOffsetNumber < SpecTokenOffsetNumber,
+					 "invalid speculative token constant");
+
+	/* NO EREPORT(ERROR) from here till changes are logged */
+	START_CRIT_SECTION();
+
+	Assert(HeapTupleHeaderIsSpeculative(tuple->t_data));
+
+	MarkBufferDirty(buffer);
+
+	/*
+	 * Replace the speculative insertion token with a real t_ctid, pointing to
+	 * itself like it does on regular tuples.
+	 */
+	htup->t_ctid = tuple->t_self;
+
+	/* XLOG stuff */
+	if (RelationNeedsWAL(relation))
+	{
+		xl_heap_confirm xlrec;
+		XLogRecPtr	recptr;
+
+		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
+
+		XLogBeginInsert();
+
+		/* We want the same filtering on this as on a plain insert */
+		XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
+
+		XLogRegisterData((char *) &xlrec, SizeOfHeapConfirm);
+		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
+
+		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_CONFIRM);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	UnlockReleaseBuffer(buffer);
+	slot->tts_speculativeToken = 0;
+}
+
+static void
+heapam_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;
+	HeapTupleData tp;
+	Page		page;
+	BlockNumber block;
+	Buffer		buffer;
+
+	/*
+	 * Assert(slot->tts_speculativeToken != 0); This needs some update in
+	 * toast
+	 */
+	Assert(ItemPointerIsValid(tid));
+
+	block = ItemPointerGetBlockNumber(tid);
+	buffer = ReadBuffer(relation, block);
+	page = BufferGetPage(buffer);
+
+	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+
+	/*
+	 * Page can't be all visible, we just inserted into it, and are still
+	 * running.
+	 */
+	Assert(!PageIsAllVisible(page));
+
+	lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
+	Assert(ItemIdIsNormal(lp));
+
+	tp.t_tableOid = RelationGetRelid(relation);
+	tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+	tp.t_len = ItemIdGetLength(lp);
+	tp.t_self = *tid;
+
+	/*
+	 * Sanity check that the tuple really is a speculatively inserted tuple,
+	 * inserted by us.
+	 */
+	if (tp.t_data->t_choice.t_heap.t_xmin != xid)
+		elog(ERROR, "attempted to kill a tuple inserted by another transaction");
+	if (!(IsToastRelation(relation) || HeapTupleHeaderIsSpeculative(tp.t_data)))
+		elog(ERROR, "attempted to kill a non-speculative tuple");
+	Assert(!HeapTupleHeaderIsHeapOnly(tp.t_data));
+
+	/*
+	 * No need to check for serializable conflicts here.  There is never a
+	 * need for a combocid, either.  No need to extract replica identity, or
+	 * do anything special with infomask bits.
+	 */
+
+	START_CRIT_SECTION();
+
+	/*
+	 * The tuple will become DEAD immediately.  Flag that this page
+	 * immediately is a candidate for pruning by setting xmin to
+	 * RecentGlobalXmin.  That's not pretty, but it doesn't seem worth
+	 * inventing a nicer API for this.
+	 */
+	Assert(TransactionIdIsValid(RecentGlobalXmin));
+	PageSetPrunable(page, RecentGlobalXmin);
+
+	/* store transaction information of xact deleting the tuple */
+	tp.t_data->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
+	tp.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+
+	/*
+	 * Set the tuple header xmin to InvalidTransactionId.  This makes the
+	 * tuple immediately invisible everyone.  (In particular, to any
+	 * transactions waiting on the speculative token, woken up later.)
+	 */
+	HeapTupleHeaderSetXmin(tp.t_data, InvalidTransactionId);
+
+	/* Clear the speculative insertion token too */
+	tp.t_data->t_ctid = tp.t_self;
+
+	MarkBufferDirty(buffer);
+
+	/*
+	 * XLOG stuff
+	 *
+	 * The WAL records generated here match heap_delete().  The same recovery
+	 * routines are used.
+	 */
+	if (RelationNeedsWAL(relation))
+	{
+		xl_heap_delete xlrec;
+		XLogRecPtr	recptr;
+
+		xlrec.flags = XLH_DELETE_IS_SUPER;
+		xlrec.infobits_set = compute_infobits(tp.t_data->t_infomask,
+											  tp.t_data->t_infomask2);
+		xlrec.offnum = ItemPointerGetOffsetNumber(&tp.t_self);
+		xlrec.xmax = xid;
+
+		XLogBeginInsert();
+		XLogRegisterData((char *) &xlrec, SizeOfHeapDelete);
+		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
+
+		/* No replica identity & replication origin logged */
+
+		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_DELETE);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+
+	if (HeapTupleHasExternal(&tp))
+	{
+		Assert(!IsToastRelation(relation));
+		toast_delete(relation, &tp, true);
+	}
+
+	/*
+	 * Never need to mark tuple for invalidation, since catalogs don't support
+	 * speculative insertion
+	 */
+
+	/* Now we can release the buffer */
+	ReleaseBuffer(buffer);
+
+	/* count deletion, as we counted the insertion too */
+	pgstat_count_heap_delete(relation);
+	slot->tts_speculativeToken = 0;
+}
+
+/*
+ *	heapam_multi_insert	- insert multiple tuple into a heap
+ *
+ * This is like heap_insert(), but inserts multiple tuples in one operation.
+ * That's faster than calling heap_insert() in a loop, because when multiple
+ * tuples can be inserted on a single page, we can write just a single WAL
+ * record covering all of them, and only need to lock/unlock the page once.
+ *
+ * Note: this leaks memory into the current memory context. You can create a
+ * temporary context before calling this, if that's a problem.
+ */
+static void
+heapam_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
+					CommandId cid, int options, BulkInsertState bistate)
+{
+	TransactionId xid = GetCurrentTransactionId();
+	HeapTuple  *heaptuples;
+	int			i;
+	int			ndone;
+	char	   *scratch = NULL;
+	Page		page;
+	bool		needwal;
+	Size		saveFreeSpace;
+	bool		need_tuple_data = RelationIsLogicallyLogged(relation);
+	bool		need_cids = RelationIsAccessibleInLogicalDecoding(relation);
+
+	needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation);
+	saveFreeSpace = RelationGetTargetPageFreeSpace(relation,
+												   HEAP_DEFAULT_FILLFACTOR);
+
+	/* Toast and set header data in all the tuples */
+	heaptuples = palloc(ntuples * sizeof(HeapTuple));
+	for (i = 0; i < ntuples; i++)
+		heaptuples[i] = heap_prepare_insert(relation, tuples[i],
+											xid, cid, options);
+
+	/*
+	 * Allocate some memory to use for constructing the WAL record. Using
+	 * palloc() within a critical section is not safe, so we allocate this
+	 * beforehand.
+	 */
+	if (needwal)
+		scratch = palloc(BLCKSZ);
+
+	/*
+	 * We're about to do the actual inserts -- but check for conflict first,
+	 * to minimize the possibility of having to roll back work we've just
+	 * done.
+	 *
+	 * A check here does not definitively prevent a serialization anomaly;
+	 * that check MUST be done at least past the point of acquiring an
+	 * exclusive buffer content lock on every buffer that will be affected,
+	 * and MAY be done after all inserts are reflected in the buffers and
+	 * those locks are released; otherwise there race condition.  Since
+	 * multiple buffers can be locked and unlocked in the loop below, and it
+	 * would not be feasible to identify and lock all of those buffers before
+	 * the loop, we must do a final check at the end.
+	 *
+	 * The check here could be omitted with no loss of correctness; it is
+	 * present strictly as an optimization.
+	 *
+	 * For heap inserts, we only need to check for table-level SSI locks. Our
+	 * new tuples can't possibly conflict with existing tuple locks, and heap
+	 * page locks are only consolidated versions of tuple locks; they do not
+	 * lock "gaps" as index page locks do.  So we don't need to specify a
+	 * buffer when making the call, which makes for a faster check.
+	 */
+	CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
+
+	ndone = 0;
+	while (ndone < ntuples)
+	{
+		Buffer		buffer;
+		Buffer		vmbuffer = InvalidBuffer;
+		bool		all_visible_cleared = false;
+		int			nthispage;
+
+		CHECK_FOR_INTERRUPTS();
+
+		/*
+		 * Find buffer where at least the next tuple will fit.  If the page is
+		 * all-visible, this will also pin the requisite visibility map page.
+		 */
+		buffer = RelationGetBufferForTuple(relation, heaptuples[ndone]->t_len,
+										   InvalidBuffer, options, bistate,
+										   &vmbuffer, NULL);
+		page = BufferGetPage(buffer);
+
+		/* NO EREPORT(ERROR) from here till changes are logged */
+		START_CRIT_SECTION();
+
+		/*
+		 * RelationGetBufferForTuple has ensured that the first tuple fits.
+		 * Put that on the page, and then as many other tuples as fit.
+		 */
+		RelationPutHeapTuple(relation, buffer, heaptuples[ndone], false);
+		for (nthispage = 1; ndone + nthispage < ntuples; nthispage++)
+		{
+			HeapTuple	heaptup = heaptuples[ndone + nthispage];
+
+			if (PageGetHeapFreeSpace(page) < MAXALIGN(heaptup->t_len) + saveFreeSpace)
+				break;
+
+			RelationPutHeapTuple(relation, buffer, heaptup, false);
+
+			/*
+			 * We don't use heap_multi_insert for catalog tuples yet, but
+			 * better be prepared...
+			 */
+			if (needwal && need_cids)
+				log_heap_new_cid(relation, heaptup);
+		}
+
+		if (PageIsAllVisible(page))
+		{
+			all_visible_cleared = true;
+			PageClearAllVisible(page);
+			visibilitymap_clear(relation,
+								BufferGetBlockNumber(buffer),
+								vmbuffer, VISIBILITYMAP_VALID_BITS);
+		}
+
+		/*
+		 * XXX Should we set PageSetPrunable on this page ? See heap_insert()
+		 */
+
+		MarkBufferDirty(buffer);
+
+		/* XLOG stuff */
+		if (needwal)
+		{
+			XLogRecPtr	recptr;
+			xl_heap_multi_insert *xlrec;
+			uint8		info = XLOG_HEAP2_MULTI_INSERT;
+			char	   *tupledata;
+			int			totaldatalen;
+			char	   *scratchptr = scratch;
+			bool		init;
+			int			bufflags = 0;
+
+			/*
+			 * If the page was previously empty, we can reinit the page
+			 * instead of restoring the whole thing.
+			 */
+			init = (ItemPointerGetOffsetNumber(&(heaptuples[ndone]->t_self)) == FirstOffsetNumber &&
+					PageGetMaxOffsetNumber(page) == FirstOffsetNumber + nthispage - 1);
+
+			/* allocate xl_heap_multi_insert struct from the scratch area */
+			xlrec = (xl_heap_multi_insert *) scratchptr;
+			scratchptr += SizeOfHeapMultiInsert;
+
+			/*
+			 * Allocate offsets array. Unless we're reinitializing the page,
+			 * in that case the tuples are stored in order starting at
+			 * FirstOffsetNumber and we don't need to store the offsets
+			 * explicitly.
+			 */
+			if (!init)
+				scratchptr += nthispage * sizeof(OffsetNumber);
+
+			/* the rest of the scratch space is used for tuple data */
+			tupledata = scratchptr;
+
+			xlrec->flags = all_visible_cleared ? XLH_INSERT_ALL_VISIBLE_CLEARED : 0;
+			xlrec->ntuples = nthispage;
+
+			/*
+			 * Write out an xl_multi_insert_tuple and the tuple data itself
+			 * for each tuple.
+			 */
+			for (i = 0; i < nthispage; i++)
+			{
+				HeapTuple	heaptup = heaptuples[ndone + i];
+				xl_multi_insert_tuple *tuphdr;
+				int			datalen;
+
+				if (!init)
+					xlrec->offsets[i] = ItemPointerGetOffsetNumber(&heaptup->t_self);
+				/* xl_multi_insert_tuple needs two-byte alignment. */
+				tuphdr = (xl_multi_insert_tuple *) SHORTALIGN(scratchptr);
+				scratchptr = ((char *) tuphdr) + SizeOfMultiInsertTuple;
+
+				tuphdr->t_infomask2 = heaptup->t_data->t_infomask2;
+				tuphdr->t_infomask = heaptup->t_data->t_infomask;
+				tuphdr->t_hoff = heaptup->t_data->t_hoff;
+
+				/* write bitmap [+ padding] [+ oid] + data */
+				datalen = heaptup->t_len - SizeofHeapTupleHeader;
+				memcpy(scratchptr,
+					   (char *) heaptup->t_data + SizeofHeapTupleHeader,
+					   datalen);
+				tuphdr->datalen = datalen;
+				scratchptr += datalen;
+			}
+			totaldatalen = scratchptr - tupledata;
+			Assert((scratchptr - scratch) < BLCKSZ);
+
+			if (need_tuple_data)
+				xlrec->flags |= XLH_INSERT_CONTAINS_NEW_TUPLE;
+
+			/*
+			 * Signal that this is the last xl_heap_multi_insert record
+			 * emitted by this call to heap_multi_insert(). Needed for logical
+			 * decoding so it knows when to cleanup temporary data.
+			 */
+			if (ndone + nthispage == ntuples)
+				xlrec->flags |= XLH_INSERT_LAST_IN_MULTI;
+
+			if (init)
+			{
+				info |= XLOG_HEAP_INIT_PAGE;
+				bufflags |= REGBUF_WILL_INIT;
+			}
+
+			/*
+			 * If we're doing logical decoding, include the new tuple data
+			 * even if we take a full-page image of the page.
+			 */
+			if (need_tuple_data)
+				bufflags |= REGBUF_KEEP_DATA;
+
+			XLogBeginInsert();
+			XLogRegisterData((char *) xlrec, tupledata - scratch);
+			XLogRegisterBuffer(0, buffer, REGBUF_STANDARD | bufflags);
+
+			XLogRegisterBufData(0, tupledata, totaldatalen);
+
+			/* filtering by origin on a row level is much more efficient */
+			XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
+
+			recptr = XLogInsert(RM_HEAP2_ID, info);
+
+			PageSetLSN(page, recptr);
+		}
+
+		END_CRIT_SECTION();
+
+		UnlockReleaseBuffer(buffer);
+		if (vmbuffer != InvalidBuffer)
+			ReleaseBuffer(vmbuffer);
+
+		ndone += nthispage;
+	}
+
+	/*
+	 * We're done with the actual inserts.  Check for conflicts again, to
+	 * ensure that all rw-conflicts in to these inserts are detected.  Without
+	 * this final check, a sequential scan of the heap may have locked the
+	 * table after the "before" check, missing one opportunity to detect the
+	 * conflict, and then scanned the table before the new tuples were there,
+	 * missing the other chance to detect the conflict.
+	 *
+	 * For heap inserts, we only need to check for table-level SSI locks. Our
+	 * new tuples can't possibly conflict with existing tuple locks, and heap
+	 * page locks are only consolidated versions of tuple locks; they do not
+	 * lock "gaps" as index page locks do.  So we don't need to specify a
+	 * buffer when making the call.
+	 */
+	CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
+
+	/*
+	 * If tuples are cachable, mark them for invalidation from the caches in
+	 * case we abort.  Note it is OK to do this after releasing the buffer,
+	 * because the heaptuples data structure is all in local memory, not in
+	 * the shared buffer.
+	 */
+	if (IsCatalogRelation(relation))
+	{
+		for (i = 0; i < ntuples; i++)
+			CacheInvalidateHeapTuple(relation, heaptuples[i], NULL);
+	}
+
+	/*
+	 * Copy t_self fields back to the caller's original tuples. This does
+	 * nothing for untoasted tuples (tuples[i] == heaptuples[i)], but it's
+	 * probably faster to always copy than check.
+	 */
+	for (i = 0; i < ntuples; i++)
+		tuples[i]->t_self = heaptuples[i]->t_self;
+
+	pgstat_count_heap_insert(relation, ntuples);
+}
+
+/*
+ * Subroutine for heap_lock_updated_tuple_rec.
+ *
+ * Given a hypothetical multixact status held by the transaction identified
+ * with the given xid, does the current transaction need to wait, fail, or can
+ * it continue if it wanted to acquire a lock of the given mode?  "needwait"
+ * is set to true if waiting is necessary; if it can continue, then
+ * HeapTupleMayBeUpdated is returned.  If the lock is already held by the
+ * current transaction, return HeapTupleSelfUpdated.  In case of a conflict
+ * with another transaction, a different HeapTupleSatisfiesUpdate return code
+ * is returned.
+ *
+ * The held status is said to be hypothetical because it might correspond to a
+ * lock held by a single Xid, i.e. not a real MultiXactId; we express it this
+ * way for simplicity of API.
+ */
+static HTSU_Result
+test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid,
+						   LockTupleMode mode, bool *needwait)
+{
+	MultiXactStatus wantedstatus;
+
+	*needwait = false;
+	wantedstatus = get_mxact_status_for_lock(mode, false);
+
+	/*
+	 * Note: we *must* check TransactionIdIsInProgress before
+	 * TransactionIdDidAbort/Commit; see comment at top of tqual.c for an
+	 * explanation.
+	 */
+	if (TransactionIdIsCurrentTransactionId(xid))
+	{
+		/*
+		 * The tuple has already been locked by our own transaction.  This is
+		 * very rare but can happen if multiple transactions are trying to
+		 * lock an ancient version of the same tuple.
+		 */
+		return HeapTupleSelfUpdated;
+	}
+	else if (TransactionIdIsInProgress(xid))
+	{
+		/*
+		 * If the locking transaction is running, what we do depends on
+		 * whether the lock modes conflict: if they do, then we must wait for
+		 * it to finish; otherwise we can fall through to lock this tuple
+		 * version without waiting.
+		 */
+		if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
+								LOCKMODE_from_mxstatus(wantedstatus)))
+		{
+			*needwait = true;
+		}
+
+		/*
+		 * If we set needwait above, then this value doesn't matter;
+		 * otherwise, this value signals to caller that it's okay to proceed.
+		 */
+		return HeapTupleMayBeUpdated;
+	}
+	else if (TransactionIdDidAbort(xid))
+		return HeapTupleMayBeUpdated;
+	else if (TransactionIdDidCommit(xid))
+	{
+		/*
+		 * The other transaction committed.  If it was only a locker, then the
+		 * lock is completely gone now and we can return success; but if it
+		 * was an update, then what we do depends on whether the two lock
+		 * modes conflict.  If they conflict, then we must report error to
+		 * caller. But if they don't, we can fall through to allow the current
+		 * transaction to lock the tuple.
+		 *
+		 * Note: the reason we worry about ISUPDATE here is because as soon as
+		 * a transaction ends, all its locks are gone and meaningless, and
+		 * thus we can ignore them; whereas its updates persist.  In the
+		 * TransactionIdIsInProgress case, above, we don't need to check
+		 * because we know the lock is still "alive" and thus a conflict needs
+		 * always be checked.
+		 */
+		if (!ISUPDATE_from_mxstatus(status))
+			return HeapTupleMayBeUpdated;
+
+		if (DoLockModesConflict(LOCKMODE_from_mxstatus(status),
+								LOCKMODE_from_mxstatus(wantedstatus)))
+			/* bummer */
+			return HeapTupleUpdated;
+
+		return HeapTupleMayBeUpdated;
+	}
+
+	/* Not in progress, not aborted, not committed -- must have crashed */
+	return HeapTupleMayBeUpdated;
+}
+
+
+/*
+ * Recursive part of heap_lock_updated_tuple
+ *
+ * Fetch the tuple pointed to by tid in rel, and mark it as locked by the given
+ * xid with the given mode; if this tuple is updated, recurse to lock the new
+ * version as well.
+ */
+static HTSU_Result
+heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid,
+							LockTupleMode mode)
+{
+	HTSU_Result result;
+	ItemPointerData tupid;
+	HeapTupleData mytup;
+	Buffer		buf;
+	uint16		new_infomask,
+				new_infomask2,
+				old_infomask,
+				old_infomask2;
+	TransactionId xmax,
+				new_xmax;
+	TransactionId priorXmax = InvalidTransactionId;
+	bool		cleared_all_frozen = false;
+	Buffer		vmbuffer = InvalidBuffer;
+	BlockNumber block;
+
+	ItemPointerCopy(tid, &tupid);
+
+	for (;;)
+	{
+		new_infomask = 0;
+		new_xmax = InvalidTransactionId;
+		block = ItemPointerGetBlockNumber(&tupid);
+
+		if (!heap_fetch(rel, &tupid, SnapshotAny, &mytup, &buf, false, NULL))
+		{
+			/*
+			 * if we fail to find the updated version of the tuple, it's
+			 * because it was vacuumed/pruned away after its creator
+			 * transaction aborted.  So behave as if we got to the end of the
+			 * chain, and there's no further tuple to lock: return success to
+			 * caller.
+			 */
+			return HeapTupleMayBeUpdated;
+		}
+
+l4:
+		CHECK_FOR_INTERRUPTS();
+
+		/*
+		 * Before locking the buffer, pin the visibility map page if it
+		 * appears to be necessary.  Since we haven't got the lock yet,
+		 * someone else might be in the middle of changing this, so we'll need
+		 * to recheck after we have the lock.
+		 */
+		if (PageIsAllVisible(BufferGetPage(buf)))
+			visibilitymap_pin(rel, block, &vmbuffer);
+		else
+			vmbuffer = InvalidBuffer;
+
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+		/*
+		 * If we didn't pin the visibility map page and the page has become
+		 * all visible while we were busy locking the buffer, we'll have to
+		 * unlock and re-lock, to avoid holding the buffer lock across I/O.
+		 * That's a bit unfortunate, but hopefully shouldn't happen often.
+		 */
+		if (vmbuffer == InvalidBuffer && PageIsAllVisible(BufferGetPage(buf)))
+		{
+			LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+			visibilitymap_pin(rel, block, &vmbuffer);
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+		}
+
+		/*
+		 * Check the tuple XMIN against prior XMAX, if any.  If we reached the
+		 * end of the chain, we're done, so return success.
+		 */
+		if (TransactionIdIsValid(priorXmax) &&
+				!TransactionIdEquals(HeapTupleHeaderGetXmin(mytup.t_data),
+												 priorXmax))
+		{
+			result = HeapTupleMayBeUpdated;
+			goto out_locked;
+		}
+
+		/*
+		 * Also check Xmin: if this tuple was created by an aborted
+		 * (sub)transaction, then we already locked the last live one in the
+		 * chain, thus we're done, so return success.
+		 */
+		if (TransactionIdDidAbort(HeapTupleHeaderGetXmin(mytup.t_data)))
+		{
+			UnlockReleaseBuffer(buf);
+			return HeapTupleMayBeUpdated;
+		}
+
+		old_infomask = mytup.t_data->t_infomask;
+		old_infomask2 = mytup.t_data->t_infomask2;
+		xmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
+
+		/*
+		 * If this tuple version has been updated or locked by some concurrent
+		 * transaction(s), what we do depends on whether our lock mode
+		 * conflicts with what those other transactions hold, and also on the
+		 * status of them.
+		 */
+		if (!(old_infomask & HEAP_XMAX_INVALID))
+		{
+			TransactionId rawxmax;
+			bool		needwait;
+
+			rawxmax = HeapTupleHeaderGetRawXmax(mytup.t_data);
+			if (old_infomask & HEAP_XMAX_IS_MULTI)
+			{
+				int			nmembers;
+				int			i;
+				MultiXactMember *members;
+
+				/*
+				 * We don't need a test for pg_upgrade'd tuples: this is only
+				 * applied to tuples after the first in an update chain.  Said
+				 * first tuple in the chain may well be locked-in-9.2-and-
+				 * pg_upgraded, but that one was already locked by our caller,
+				 * not us; and any subsequent ones cannot be because our
+				 * caller must necessarily have obtained a snapshot later than
+				 * the pg_upgrade itself.
+				 */
+				Assert(!HEAP_LOCKED_UPGRADED(mytup.t_data->t_infomask));
+
+				nmembers = GetMultiXactIdMembers(rawxmax, &members, false,
+												 HEAP_XMAX_IS_LOCKED_ONLY(old_infomask));
+				for (i = 0; i < nmembers; i++)
+				{
+					result = test_lockmode_for_conflict(members[i].status,
+														members[i].xid,
+														mode, &needwait);
+
+					/*
+					 * If the tuple was already locked by ourselves in a
+					 * previous iteration of this (say heap_lock_tuple was
+					 * forced to restart the locking loop because of a change
+					 * in xmax), then we hold the lock already on this tuple
+					 * version and we don't need to do anything; and this is
+					 * not an error condition either.  We just need to skip
+					 * this tuple and continue locking the next version in the
+					 * update chain.
+					 */
+					if (result == HeapTupleSelfUpdated)
+					{
+						pfree(members);
+						goto next;
+					}
+
+					if (needwait)
+					{
+						LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+						XactLockTableWait(members[i].xid, rel,
+										  &mytup.t_self,
+										  XLTW_LockUpdated);
+						pfree(members);
+						goto l4;
+					}
+					if (result != HeapTupleMayBeUpdated)
+					{
+						pfree(members);
+						goto out_locked;
+					}
+				}
+				if (members)
+					pfree(members);
+			}
+			else
+			{
+				MultiXactStatus status;
+
+				/*
+				 * For a non-multi Xmax, we first need to compute the
+				 * corresponding MultiXactStatus by using the infomask bits.
+				 */
+				if (HEAP_XMAX_IS_LOCKED_ONLY(old_infomask))
+				{
+					if (HEAP_XMAX_IS_KEYSHR_LOCKED(old_infomask))
+						status = MultiXactStatusForKeyShare;
+					else if (HEAP_XMAX_IS_SHR_LOCKED(old_infomask))
+						status = MultiXactStatusForShare;
+					else if (HEAP_XMAX_IS_EXCL_LOCKED(old_infomask))
+					{
+						if (old_infomask2 & HEAP_KEYS_UPDATED)
+							status = MultiXactStatusForUpdate;
+						else
+							status = MultiXactStatusForNoKeyUpdate;
+					}
+					else
+					{
+						/*
+						 * LOCK_ONLY present alone (a pg_upgraded tuple marked
+						 * as share-locked in the old cluster) shouldn't be
+						 * seen in the middle of an update chain.
+						 */
+						elog(ERROR, "invalid lock status in tuple");
+					}
+				}
+				else
+				{
+					/* it's an update, but which kind? */
+					if (old_infomask2 & HEAP_KEYS_UPDATED)
+						status = MultiXactStatusUpdate;
+					else
+						status = MultiXactStatusNoKeyUpdate;
+				}
+
+				result = test_lockmode_for_conflict(status, rawxmax, mode,
+													&needwait);
+
+				/*
+				 * If the tuple was already locked by ourselves in a previous
+				 * iteration of this (say heap_lock_tuple was forced to
+				 * restart the locking loop because of a change in xmax), then
+				 * we hold the lock already on this tuple version and we don't
+				 * need to do anything; and this is not an error condition
+				 * either.  We just need to skip this tuple and continue
+				 * locking the next version in the update chain.
+				 */
+				if (result == HeapTupleSelfUpdated)
+					goto next;
+
+				if (needwait)
+				{
+					LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+					XactLockTableWait(rawxmax, rel, &mytup.t_self,
+									  XLTW_LockUpdated);
+					goto l4;
+				}
+				if (result != HeapTupleMayBeUpdated)
+				{
+					goto out_locked;
+				}
+			}
+		}
+
+		/* compute the new Xmax and infomask values for the tuple ... */
+		compute_new_xmax_infomask(xmax, old_infomask, mytup.t_data->t_infomask2,
+								  xid, mode, false,
+								  &new_xmax, &new_infomask, &new_infomask2);
+
+		if (PageIsAllVisible(BufferGetPage(buf)) &&
+			visibilitymap_clear(rel, block, vmbuffer,
+								VISIBILITYMAP_ALL_FROZEN))
+			cleared_all_frozen = true;
+
+		START_CRIT_SECTION();
+
+		/* ... and set them */
+		HeapTupleHeaderSetXmax(mytup.t_data, new_xmax);
+		mytup.t_data->t_infomask &= ~HEAP_XMAX_BITS;
+		mytup.t_data->t_infomask2 &= ~HEAP_KEYS_UPDATED;
+		mytup.t_data->t_infomask |= new_infomask;
+		mytup.t_data->t_infomask2 |= new_infomask2;
+
+		MarkBufferDirty(buf);
+
+		/* XLOG stuff */
+		if (RelationNeedsWAL(rel))
+		{
+			xl_heap_lock_updated xlrec;
+			XLogRecPtr	recptr;
+			Page		page = BufferGetPage(buf);
+
+			XLogBeginInsert();
+			XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
+
+			xlrec.offnum = ItemPointerGetOffsetNumber(&mytup.t_self);
+			xlrec.xmax = new_xmax;
+			xlrec.infobits_set = compute_infobits(new_infomask, new_infomask2);
+			xlrec.flags =
+				cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
+
+			XLogRegisterData((char *) &xlrec, SizeOfHeapLockUpdated);
+
+			recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_LOCK_UPDATED);
+
+			PageSetLSN(page, recptr);
+		}
+
+		END_CRIT_SECTION();
+
+next:
+		/* if we find the end of update chain, we're done. */
+		if (mytup.t_data->t_infomask & HEAP_XMAX_INVALID ||
+			ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid) ||
+			HeapTupleHeaderIsOnlyLocked(mytup.t_data))
+		{
+			result = HeapTupleMayBeUpdated;
+			goto out_locked;
+		}
+
+		/* tail recursion */
+		priorXmax = HeapTupleHeaderGetUpdateXid(mytup.t_data);
+		ItemPointerCopy(&(mytup.t_data->t_ctid), &tupid);
+		UnlockReleaseBuffer(buf);
+		if (vmbuffer != InvalidBuffer)
+			ReleaseBuffer(vmbuffer);
+	}
+
+	result = HeapTupleMayBeUpdated;
+
+out_locked:
+	UnlockReleaseBuffer(buf);
+
+	if (vmbuffer != InvalidBuffer)
+		ReleaseBuffer(vmbuffer);
+
+	return result;
+
+}
+
+/*
+ * heap_lock_updated_tuple
+ *		Follow update chain when locking an updated tuple, acquiring locks (row
+ *		marks) on the updated versions.
+ *
+ * The initial tuple is assumed to be already locked.
+ *
+ * This function doesn't check visibility, it just unconditionally marks the
+ * tuple(s) as locked.  If any tuple in the updated chain is being deleted
+ * concurrently (or updated with the key being modified), sleep until the
+ * transaction doing it is finished.
+ *
+ * Note that we don't acquire heavyweight tuple locks on the tuples we walk
+ * when we have to wait for other transactions to release them, as opposed to
+ * what heap_lock_tuple does.  The reason is that having more than one
+ * transaction walking the chain is probably uncommon enough that risk of
+ * starvation is not likely: one of the preconditions for being here is that
+ * the snapshot in use predates the update that created this tuple (because we
+ * started at an earlier version of the tuple), but at the same time such a
+ * transaction cannot be using repeatable read or serializable isolation
+ * levels, because that would lead to a serializability failure.
+ */
+static HTSU_Result
+heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid,
+						TransactionId xid, LockTupleMode mode)
+{
+	if (!ItemPointerEquals(&tuple->t_self, ctid))
+	{
+		/*
+		 * If this is the first possibly-multixact-able operation in the
+		 * current transaction, set my per-backend OldestMemberMXactId
+		 * setting. We can be certain that the transaction will never become a
+		 * member of any older MultiXactIds than that.  (We have to do this
+		 * even if we end up just using our own TransactionId below, since
+		 * some other backend could incorporate our XID into a MultiXact
+		 * immediately afterwards.)
+		 */
+		MultiXactIdSetOldestMember();
+
+		return heap_lock_updated_tuple_rec(rel, ctid, xid, mode);
+	}
+
+	/* nothing to lock */
+	return HeapTupleMayBeUpdated;
+}
+
+/*
+ *	heapam_lock_tuple - lock a tuple in shared or exclusive mode
+ *
+ * Note that this acquires a buffer pin, which the caller must release.
+ *
+ * Input parameters:
+ *	relation: relation containing tuple (caller must hold suitable lock)
+ *	tuple->t_self: TID of tuple to lock (rest of struct need not be valid)
+ *	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
+ *	follow_updates: if true, follow the update chain to also lock descendant
+ *		tuples.
+ *
+ * Output parameters:
+ *	*tuple: all fields filled in
+ *	*buffer: set to buffer holding tuple (pinned but not locked at exit)
+ *	*hufd: filled in failure cases (see below)
+ *
+ * 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.
+ *
+ * See README.tuplock for a thorough explanation of this mechanism.
+ */
+static HTSU_Result
+heapam_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;
+	ItemId		lp;
+	Page		page;
+	Buffer		vmbuffer = InvalidBuffer;
+	BlockNumber block;
+	TransactionId xid,
+				xmax;
+	uint16		old_infomask,
+				new_infomask,
+				new_infomask2;
+	bool		first_time = true;
+	bool		have_tuple_lock = false;
+	bool		cleared_all_frozen = false;
+	HeapTupleData tuple;
+	Buffer		buf;
+
+	Assert(stuple != NULL);
+
+	buf = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
+	block = ItemPointerGetBlockNumber(tid);
+	*buffer = buf;
+
+	/*
+	 * Before locking the buffer, pin the visibility map page if it appears to
+	 * be necessary.  Since we haven't got the lock yet, someone else might be
+	 * in the middle of changing this, so we'll need to recheck after we have
+	 * the lock.
+	 */
+	if (PageIsAllVisible(BufferGetPage(buf)))
+		visibilitymap_pin(relation, block, &vmbuffer);
+
+	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+	page = BufferGetPage(buf);
+	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);
+
+l3:
+	result = HeapTupleSatisfiesUpdate(&tuple, cid, buf);
+
+	if (result == HeapTupleInvisible)
+	{
+		/*
+		 * This is possible, but only when locking a tuple for ON CONFLICT
+		 * UPDATE.  We return this value here rather than throwing an error in
+		 * order to give that case the opportunity to throw a more specific
+		 * error.
+		 */
+		result = HeapTupleInvisible;
+		goto out_locked;
+	}
+	else if (result == HeapTupleBeingUpdated || result == HeapTupleUpdated)
+	{
+		TransactionId xwait;
+		uint16		infomask;
+		uint16		infomask2;
+		bool		require_sleep;
+		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);
+
+		LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+
+		/*
+		 * If any subtransaction of the current top transaction already holds
+		 * a lock as strong as or stronger than what we're requesting, we
+		 * effectively hold the desired lock already.  We *must* succeed
+		 * without trying to take the tuple lock, else we will deadlock
+		 * against anyone wanting to acquire a stronger lock.
+		 *
+		 * Note we only do this the first time we loop on the HTSU result;
+		 * there is no point in testing in subsequent passes, because
+		 * evidently our own transaction cannot have acquired a new lock after
+		 * the first time we checked.
+		 */
+		if (first_time)
+		{
+			first_time = false;
+
+			if (infomask & HEAP_XMAX_IS_MULTI)
+			{
+				int			i;
+				int			nmembers;
+				MultiXactMember *members;
+
+				/*
+				 * We don't need to allow old multixacts here; if that had
+				 * been the case, HeapTupleSatisfiesUpdate would have returned
+				 * MayBeUpdated and we wouldn't be here.
+				 */
+				nmembers =
+					GetMultiXactIdMembers(xwait, &members, false,
+										  HEAP_XMAX_IS_LOCKED_ONLY(infomask));
+
+				for (i = 0; i < nmembers; i++)
+				{
+					/* only consider members of our own transaction */
+					if (!TransactionIdIsCurrentTransactionId(members[i].xid))
+						continue;
+
+					if (TUPLOCK_from_mxstatus(members[i].status) >= mode)
+					{
+						pfree(members);
+						result = HeapTupleMayBeUpdated;
+						goto out_unlocked;
+					}
+				}
+
+				if (members)
+					pfree(members);
+			}
+			else if (TransactionIdIsCurrentTransactionId(xwait))
+			{
+				switch (mode)
+				{
+					case LockTupleKeyShare:
+						Assert(HEAP_XMAX_IS_KEYSHR_LOCKED(infomask) ||
+							   HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
+							   HEAP_XMAX_IS_EXCL_LOCKED(infomask));
+						result = HeapTupleMayBeUpdated;
+						goto out_unlocked;
+					case LockTupleShare:
+						if (HEAP_XMAX_IS_SHR_LOCKED(infomask) ||
+							HEAP_XMAX_IS_EXCL_LOCKED(infomask))
+						{
+							result = HeapTupleMayBeUpdated;
+							goto out_unlocked;
+						}
+						break;
+					case LockTupleNoKeyExclusive:
+						if (HEAP_XMAX_IS_EXCL_LOCKED(infomask))
+						{
+							result = HeapTupleMayBeUpdated;
+							goto out_unlocked;
+						}
+						break;
+					case LockTupleExclusive:
+						if (HEAP_XMAX_IS_EXCL_LOCKED(infomask) &&
+							infomask2 & HEAP_KEYS_UPDATED)
+						{
+							result = HeapTupleMayBeUpdated;
+							goto out_unlocked;
+						}
+						break;
+				}
+			}
+		}
+
+		/*
+		 * Initially assume that we will have to wait for the locking
+		 * transaction(s) to finish.  We check various cases below in which
+		 * this can be turned off.
+		 */
+		require_sleep = true;
+		if (mode == LockTupleKeyShare)
+		{
+			/*
+			 * If we're requesting KeyShare, and there's no update present, we
+			 * don't need to wait.  Even if there is an update, we can still
+			 * continue if the key hasn't been modified.
+			 *
+			 * However, if there are updates, we need to walk the update chain
+			 * to mark future versions of the row as locked, too.  That way,
+			 * if somebody deletes that future version, we're protected
+			 * against the key going away.  This locking of future versions
+			 * could block momentarily, if a concurrent transaction is
+			 * deleting a key; or it could return a value to the effect that
+			 * the transaction deleting the key has already committed.  So we
+			 * do this before re-locking the buffer; otherwise this would be
+			 * prone to deadlocks.
+			 *
+			 * Note that the TID we're locking was grabbed before we unlocked
+			 * the buffer.  For it to change while we're not looking, the
+			 * other properties we're testing for below after re-locking the
+			 * buffer would also change, in which case we would restart this
+			 * loop above.
+			 */
+			if (!(infomask2 & HEAP_KEYS_UPDATED))
+			{
+				bool		updated;
+
+				updated = !HEAP_XMAX_IS_LOCKED_ONLY(infomask);
+
+				/*
+				 * If there are updates, follow the update chain; bail out if
+				 * that cannot be done.
+				 */
+				if (follow_updates && updated)
+				{
+					HTSU_Result res;
+
+					res = heap_lock_updated_tuple(relation, &tuple, &t_ctid,
+												  GetCurrentTransactionId(),
+												  mode);
+					if (res != HeapTupleMayBeUpdated)
+					{
+						result = res;
+						/* recovery code expects to have buffer lock held */
+						LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+						goto failed;
+					}
+				}
+
+				LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+				/*
+				 * Make sure it's still an appropriate lock, else start over.
+				 * Also, if it wasn't updated before we released the lock, but
+				 * is updated now, we start over too; the reason is that we
+				 * 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) ||
+					 !updated))
+					goto l3;
+
+				/* Things look okay, so we can skip sleeping */
+				require_sleep = false;
+
+				/*
+				 * Note we allow Xmax to change here; other updaters/lockers
+				 * could have modified it before we grabbed the buffer lock.
+				 * However, this is not a problem, because with the recheck we
+				 * just did we ensure that they still don't conflict with the
+				 * lock we want.
+				 */
+			}
+		}
+		else if (mode == LockTupleShare)
+		{
+			/*
+			 * If we're requesting Share, we can similarly avoid sleeping if
+			 * there's no update and no exclusive lock present.
+			 */
+			if (HEAP_XMAX_IS_LOCKED_ONLY(infomask) &&
+				!HEAP_XMAX_IS_EXCL_LOCKED(infomask))
+			{
+				LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+				/*
+				 * 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))
+					goto l3;
+				require_sleep = false;
+			}
+		}
+		else if (mode == LockTupleNoKeyExclusive)
+		{
+			/*
+			 * If we're requesting NoKeyExclusive, we might also be able to
+			 * avoid sleeping; just ensure that there no conflicting lock
+			 * already acquired.
+			 */
+			if (infomask & HEAP_XMAX_IS_MULTI)
+			{
+				if (!DoesMultiXactIdConflict((MultiXactId) xwait, infomask,
+											 mode))
+				{
+					/*
+					 * No conflict, but if the xmax changed under us in the
+					 * meantime, start over.
+					 */
+					LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+					if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
+						!TransactionIdEquals(HeapTupleHeaderGetRawXmax(tuple.t_data),
+											 xwait))
+						goto l3;
+
+					/* otherwise, we're good */
+					require_sleep = false;
+				}
+			}
+			else if (HEAP_XMAX_IS_KEYSHR_LOCKED(infomask))
+			{
+				LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+				/* if the xmax changed in the meantime, start over */
+				if (xmax_infomask_changed(tuple.t_data->t_infomask, infomask) ||
+					!TransactionIdEquals(
+										 HeapTupleHeaderGetRawXmax(tuple.t_data),
+										 xwait))
+					goto l3;
+				/* otherwise, we're good */
+				require_sleep = false;
+			}
+		}
+
+		/*
+		 * As a check independent from those above, we can also avoid sleeping
+		 * if the current transaction is the sole locker of the tuple.  Note
+		 * that the strength of the lock already held is irrelevant; this is
+		 * not about recording the lock in Xmax (which will be done regardless
+		 * of this optimization, below).  Also, note that the cases where we
+		 * hold a lock stronger than we are requesting are already handled
+		 * above by not doing anything.
+		 *
+		 * Note we only deal with the non-multixact case here; MultiXactIdWait
+		 * is well equipped to deal with this situation on its own.
+		 */
+		if (require_sleep && !(infomask & HEAP_XMAX_IS_MULTI) &&
+			TransactionIdIsCurrentTransactionId(xwait))
+		{
+			/* ... but if the xmax changed in the meantime, start over */
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+			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));
+			require_sleep = false;
+		}
+
+		/*
+		 * Time to sleep on the other transaction/multixact, if necessary.
+		 *
+		 * If the other transaction is an update that's already committed,
+		 * then sleeping cannot possibly do any good: if we're required to
+		 * sleep, get out to raise an error instead.
+		 *
+		 * By here, we either have already acquired the buffer exclusive lock,
+		 * 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)
+		{
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+			goto failed;
+		}
+		else if (require_sleep)
+		{
+			/*
+			 * Acquire tuple lock to establish our priority for the tuple, or
+			 * die trying.  LockTuple will release us when we are next-in-line
+			 * for the tuple.  We must do this even if we are share-locking.
+			 *
+			 * If we are forced to "start over" below, we keep the tuple lock;
+			 * this arranges that we stay at the head of the line while
+			 * rechecking tuple state.
+			 */
+			if (!heap_acquire_tuplock(relation, tid, mode, wait_policy,
+									  &have_tuple_lock))
+			{
+				/*
+				 * This can only happen if wait_policy is Skip and the lock
+				 * couldn't be obtained.
+				 */
+				result = HeapTupleWouldBlock;
+				/* recovery code expects to have buffer lock held */
+				LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+				goto failed;
+			}
+
+			if (infomask & HEAP_XMAX_IS_MULTI)
+			{
+				MultiXactStatus status = get_mxact_status_for_lock(mode, false);
+
+				/* We only ever lock tuples, never update them */
+				if (status >= MultiXactStatusNoKeyUpdate)
+					elog(ERROR, "invalid lock mode in heap_lock_tuple");
+
+				/* wait for multixact to end, or die trying  */
+				switch (wait_policy)
+				{
+					case LockWaitBlock:
+						MultiXactIdWait((MultiXactId) xwait, status, infomask,
+										relation, &tuple.t_self, XLTW_Lock, NULL);
+						break;
+					case LockWaitSkip:
+						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
+														status, infomask, relation,
+														NULL))
+						{
+							result = HeapTupleWouldBlock;
+							/* recovery code expects to have buffer lock held */
+							LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+							goto failed;
+						}
+						break;
+					case LockWaitError:
+						if (!ConditionalMultiXactIdWait((MultiXactId) xwait,
+														status, infomask, relation,
+														NULL))
+							ereport(ERROR,
+									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+									 errmsg("could not obtain lock on row in relation \"%s\"",
+											RelationGetRelationName(relation))));
+
+						break;
+				}
+
+				/*
+				 * Of course, the multixact might not be done here: if we're
+				 * requesting a light lock mode, other transactions with light
+				 * locks could still be alive, as well as locks owned by our
+				 * own xact or other subxacts of this backend.  We need to
+				 * preserve the surviving MultiXact members.  Note that it
+				 * isn't absolutely necessary in the latter case, but doing so
+				 * is simpler.
+				 */
+			}
+			else
+			{
+				/* wait for regular transaction to end, or die trying */
+				switch (wait_policy)
+				{
+					case LockWaitBlock:
+						XactLockTableWait(xwait, relation, &tuple.t_self,
+										  XLTW_Lock);
+						break;
+					case LockWaitSkip:
+						if (!ConditionalXactLockTableWait(xwait))
+						{
+							result = HeapTupleWouldBlock;
+							/* recovery code expects to have buffer lock held */
+							LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+							goto failed;
+						}
+						break;
+					case LockWaitError:
+						if (!ConditionalXactLockTableWait(xwait))
+							ereport(ERROR,
+									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+									 errmsg("could not obtain lock on row in relation \"%s\"",
+											RelationGetRelationName(relation))));
+						break;
+				}
+			}
+
+			/* if there are updates, follow the update chain */
+			if (follow_updates && !HEAP_XMAX_IS_LOCKED_ONLY(infomask))
+			{
+				HTSU_Result res;
+
+				res = heap_lock_updated_tuple(relation, &tuple, &t_ctid,
+											  GetCurrentTransactionId(),
+											  mode);
+				if (res != HeapTupleMayBeUpdated)
+				{
+					result = res;
+					/* recovery code expects to have buffer lock held */
+					LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+					goto failed;
+				}
+			}
+
+			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+			/*
+			 * xwait is done, but if xwait had just locked the tuple then some
+			 * 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),
+									 xwait))
+				goto l3;
+
+			if (!(infomask & HEAP_XMAX_IS_MULTI))
+			{
+				/*
+				 * Otherwise check if it committed or aborted.  Note we cannot
+				 * be here if the tuple was only locked by somebody who didn't
+				 * conflict with us; that would have been handled above.  So
+				 * that transaction must necessarily be gone by now.  But
+				 * don't check for this in the multixact case, because some
+				 * locker transactions might still be running.
+				 */
+				UpdateXmaxHintBits(tuple.t_data, buf, xwait);
+			}
+		}
+
+		/* By here, we're certain that we hold buffer exclusive lock again */
+
+		/*
+		 * We may lock if previous xmax aborted, or if it committed but only
+		 * locked the tuple without updating it; or if we didn't have to wait
+		 * 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))
+			result = HeapTupleMayBeUpdated;
+		else
+			result = HeapTupleUpdated;
+	}
+
+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);
+		if (result == HeapTupleSelfUpdated)
+			hufd->cmax = HeapTupleHeaderGetCmax(tuple.t_data);
+		else
+			hufd->cmax = InvalidCommandId;
+		goto out_locked;
+	}
+
+	/*
+	 * If we didn't pin the visibility map page and the page has become all
+	 * visible while we were busy locking the buffer, or during some
+	 * subsequent window during which we had it unlocked, we'll have to unlock
+	 * and re-lock, to avoid holding the buffer lock across I/O.  That's a bit
+	 * unfortunate, especially since we'll now have to recheck whether the
+	 * tuple has been locked or updated under us, but hopefully it won't
+	 * happen very often.
+	 */
+	if (vmbuffer == InvalidBuffer && PageIsAllVisible(page))
+	{
+		LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+		visibilitymap_pin(relation, block, &vmbuffer);
+		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+		goto l3;
+	}
+
+	xmax = HeapTupleHeaderGetRawXmax(tuple.t_data);
+	old_infomask = tuple.t_data->t_infomask;
+
+	/*
+	 * If this is the first possibly-multixact-able operation in the current
+	 * transaction, set my per-backend OldestMemberMXactId setting. We can be
+	 * certain that the transaction will never become a member of any older
+	 * MultiXactIds than that.  (We have to do this even if we end up just
+	 * using our own TransactionId below, since some other backend could
+	 * incorporate our XID into a MultiXact immediately afterwards.)
+	 */
+	MultiXactIdSetOldestMember();
+
+	/*
+	 * Compute the new xmax and infomask to store into the tuple.  Note we do
+	 * 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,
+							  GetCurrentTransactionId(), mode, false,
+							  &xid, &new_infomask, &new_infomask2);
+
+	START_CRIT_SECTION();
+
+	/*
+	 * Store transaction information of xact locking the tuple.
+	 *
+	 * Note: Cmax is meaningless in this context, so don't set it; this avoids
+	 * possibly generating a useless combo CID.  Moreover, if we're locking a
+	 * previously updated tuple, it's important to preserve the Cmax.
+	 *
+	 * 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;
+	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
+		HeapTupleHeaderClearHotUpdated(tuple.t_data);
+	HeapTupleHeaderSetXmax(tuple.t_data, xid);
+
+	/*
+	 * Make sure there is no forward chain link in t_ctid.  Note that in the
+	 * cases where the tuple has been updated, we must not overwrite t_ctid,
+	 * because it was set by the updater.  Moreover, if the tuple has been
+	 * updated, we need to follow the update chain to lock the new versions of
+	 * the tuple as well.
+	 */
+	if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
+		tuple.t_data->t_ctid = *tid;
+
+	/* Clear only the all-frozen bit on visibility map if needed */
+	if (PageIsAllVisible(page) &&
+		visibilitymap_clear(relation, block, vmbuffer,
+							VISIBILITYMAP_ALL_FROZEN))
+		cleared_all_frozen = true;
+
+
+	MarkBufferDirty(buf);
+
+	/*
+	 * XLOG stuff.  You might think that we don't need an XLOG record because
+	 * there is no state change worth restoring after a crash.  You would be
+	 * wrong however: we have just written either a TransactionId or a
+	 * MultiXactId that may never have been seen on disk before, and we need
+	 * to make sure that there are XLOG entries covering those ID numbers.
+	 * Else the same IDs might be re-used after a crash, which would be
+	 * disastrous if this page made it to disk before the crash.  Essentially
+	 * we have to enforce the WAL log-before-data rule even in this case.
+	 * (Also, in a PITR log-shipping or 2PC environment, we have to have XLOG
+	 * entries for everything anyway.)
+	 */
+	if (RelationNeedsWAL(relation))
+	{
+		xl_heap_lock xlrec;
+		XLogRecPtr	recptr;
+
+		XLogBeginInsert();
+		XLogRegisterBuffer(0, buf, REGBUF_STANDARD);
+
+		xlrec.offnum = ItemPointerGetOffsetNumber(&tuple.t_self);
+		xlrec.locking_xid = xid;
+		xlrec.infobits_set = compute_infobits(new_infomask,
+											  tuple.t_data->t_infomask2);
+		xlrec.flags = cleared_all_frozen ? XLH_LOCK_ALL_FROZEN_CLEARED : 0;
+		XLogRegisterData((char *) &xlrec, SizeOfHeapLock);
+
+		/* we don't decode row locks atm, so no need to log the origin */
+
+		recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_LOCK);
+
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+
+	result = HeapTupleMayBeUpdated;
+
+out_locked:
+	LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+
+out_unlocked:
+	if (BufferIsValid(vmbuffer))
+		ReleaseBuffer(vmbuffer);
+
+	/*
+	 * Don't update the visibility map here. Locking a tuple doesn't change
+	 * visibility info.
+	 */
+
+	/*
+	 * Now that we have successfully marked the tuple as locked, we can
+	 * release the lmgr tuple lock, if we had it.
+	 */
+	if (have_tuple_lock)
+		UnlockTupleTuplock(relation, tid, mode);
+
+	*stuple = heap_copytuple(&tuple);
+	return result;
+}
+
+/*
+ *	heapam_get_latest_tid -  get the latest tid of a specified tuple
+ *
+ * Actually, this gets the latest version that is visible according to
+ * the passed snapshot.  You can pass SnapshotDirty to get the very latest,
+ * possibly uncommitted version.
+ *
+ * *tid is both an input and an output parameter: it is updated to
+ * show the latest version of the row.  Note that it will not be changed
+ * if no version of the row passes the snapshot test.
+ */
+static void
+heapam_get_latest_tid(Relation relation,
+					  Snapshot snapshot,
+					  ItemPointer tid)
+{
+	BlockNumber blk;
+	ItemPointerData ctid;
+	TransactionId priorXmax;
+
+	/* this is to avoid Assert failures on bad input */
+	if (!ItemPointerIsValid(tid))
+		return;
+
+	/*
+	 * Since this can be called with user-supplied TID, don't trust the input
+	 * too much.  (RelationGetNumberOfBlocks is an expensive check, so we
+	 * don't check t_ctid links again this way.  Note that it would not do to
+	 * call it just once and save the result, either.)
+	 */
+	blk = ItemPointerGetBlockNumber(tid);
+	if (blk >= RelationGetNumberOfBlocks(relation))
+		elog(ERROR, "block number %u is out of range for relation \"%s\"",
+			 blk, RelationGetRelationName(relation));
+
+	/*
+	 * Loop to chase down t_ctid links.  At top of loop, ctid is the tuple we
+	 * need to examine, and *tid is the TID we will return if ctid turns out
+	 * to be bogus.
+	 *
+	 * Note that we will loop until we reach the end of the t_ctid chain.
+	 * Depending on the snapshot passed, there might be at most one visible
+	 * version of the row, but we don't try to optimize for that.
+	 */
+	ctid = *tid;
+	priorXmax = InvalidTransactionId;	/* cannot check first XMIN */
+	for (;;)
+	{
+		Buffer		buffer;
+		Page		page;
+		OffsetNumber offnum;
+		ItemId		lp;
+		HeapTupleData tp;
+		bool		valid;
+
+		/*
+		 * Read, pin, and lock the page.
+		 */
+		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&ctid));
+		LockBuffer(buffer, BUFFER_LOCK_SHARE);
+		page = BufferGetPage(buffer);
+		TestForOldSnapshot(snapshot, relation, page);
+
+		/*
+		 * Check for bogus item number.  This is not treated as an error
+		 * condition because it can happen while following a t_ctid link. We
+		 * just assume that the prior tid is OK and return it unchanged.
+		 */
+		offnum = ItemPointerGetOffsetNumber(&ctid);
+		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+		lp = PageGetItemId(page, offnum);
+		if (!ItemIdIsNormal(lp))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+
+		/* OK to access the tuple */
+		tp.t_self = ctid;
+		tp.t_data = (HeapTupleHeader) PageGetItem(page, lp);
+		tp.t_len = ItemIdGetLength(lp);
+		tp.t_tableOid = RelationGetRelid(relation);
+
+		/*
+		 * After following a t_ctid link, we might arrive at an unrelated
+		 * tuple.  Check for XMIN match.
+		 */
+		if (TransactionIdIsValid(priorXmax) &&
+			!TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(tp.t_data)))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+
+		/*
+		 * Check time qualification of tuple; if visible, set it as the new
+		 * result candidate.
+		 */
+		valid = HeapTupleSatisfiesVisibility(relation->rd_stamroutine, &tp, snapshot, buffer);
+		CheckForSerializableConflictOut(valid, relation, &tp, buffer, snapshot);
+		if (valid)
+			*tid = ctid;
+
+		/*
+		 * If there's a valid t_ctid link, follow it, else we're done.
+		 */
+		if ((tp.t_data->t_infomask & HEAP_XMAX_INVALID) ||
+			HeapTupleHeaderIsOnlyLocked(tp.t_data) ||
+			ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid))
+		{
+			UnlockReleaseBuffer(buffer);
+			break;
+		}
+
+		ctid = tp.t_data->t_ctid;
+		priorXmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
+		UnlockReleaseBuffer(buffer);
+	}							/* end of loop */
+}
+
+/*
+ *	heapam_sync		- sync a heap, for use when no WAL has been written
+ *
+ * This forces the heap contents (including TOAST heap if any) down to disk.
+ * If we skipped using WAL, and WAL is otherwise needed, we must force the
+ * relation down to disk before it's safe to commit the transaction.  This
+ * requires writing out any dirty buffers and then doing a forced fsync.
+ *
+ * Indexes are not touched.  (Currently, index operations associated with
+ * the commands that use this are WAL-logged and so do not need fsync.
+ * That behavior might change someday, but in any case it's likely that
+ * any fsync decisions required would be per-index and hence not appropriate
+ * to be done here.)
+ */
+static void
+heapam_sync(Relation rel)
+{
+	/* non-WAL-logged tables never need fsync */
+	if (!RelationNeedsWAL(rel))
+		return;
+
+	/* main heap */
+	FlushRelationBuffers(rel);
+	/* FlushRelationBuffers will have opened rd_smgr */
+	smgrimmedsync(rel->rd_smgr, MAIN_FORKNUM);
+
+	/* FSM is not critical, don't bother syncing it */
+
+	/* toast heap, if any */
+	if (OidIsValid(rel->rd_rel->reltoastrelid))
+	{
+		Relation	toastrel;
+
+		toastrel = heap_open(rel->rd_rel->reltoastrelid, AccessShareLock);
+		FlushRelationBuffers(toastrel);
+		smgrimmedsync(toastrel->rd_smgr, MAIN_FORKNUM);
+		heap_close(toastrel, AccessShareLock);
+	}
+}
+
+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)
+{
+	StorageAmRoutine *amroutine = makeNode(StorageAmRoutine);
+
+	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 = heapam_multi_insert;
+
+	amroutine->speculative_abort = heapam_abort_speculative;
+
+	amroutine->get_tuple_data = heapam_get_tuple_data;
+	amroutine->tuple_from_datum = heapam_form_tuple_by_datum;
+	amroutine->tuple_get_latest_tid = heapam_get_latest_tid;
+
+	amroutine->relation_sync = heapam_sync;
 
 	amroutine->snapshot_satisfies = HeapTupleSatisfies;
 	amroutine->snapshot_satisfiesUpdate = HeapTupleSatisfiesUpdate;
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 3118c2d33f..84ac4bfade 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -111,6 +111,7 @@
 #include "access/heapam_common.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"
@@ -127,13 +128,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
@@ -358,7 +359,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/storageam.c b/src/backend/access/heap/storageam.c
new file mode 100644
index 0000000000..870490d00f
--- /dev/null
+++ b/src/backend/access/heap/storageam.c
@@ -0,0 +1,281 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/heap/storageam.c
+ *
+ *
+ * NOTES
+ *	  This file contains the storage_ routines which implement
+ *	  the POSTGRES storage access method used for all POSTGRES
+ *	  relations.
+ *
+ *-------------------------------------------------------------------------
+ */
+#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 "access/relscan.h"
+#include "access/storageam.h"
+#include "access/storageamapi.h"
+#include "access/tuptoaster.h"
+#include "access/valid.h"
+#include "access/visibilitymap.h"
+#include "access/xloginsert.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/bufmgr.h"
+#include "storage/lmgr.h"
+#include "storage/predicate.h"
+#include "storage/procarray.h"
+#include "storage/smgr.h"
+#include "storage/spin.h"
+#include "utils/builtins.h"
+#include "utils/inval.h"
+#include "utils/rel.h"
+#include "utils/tqual.h"
+
+
+/*
+ *	storage_fetch		- retrieve tuple with given tid
+ *
+ * On entry, tuple->t_self is the TID to fetch.  We pin the buffer holding
+ * the tuple, fill in the remaining fields of *tuple, and check the tuple
+ * against the specified snapshot.
+ *
+ * If successful (tuple found and passes snapshot time qual), then *userbuf
+ * is set to the buffer holding the tuple and TRUE is returned.  The caller
+ * must unpin the buffer when done with the tuple.
+ *
+ * If the tuple is not found (ie, item number references a deleted slot),
+ * then tuple->t_data is set to NULL and FALSE is returned.
+ *
+ * If the tuple is found but fails the time qual check, then FALSE is returned
+ * but tuple->t_data is left pointing to the tuple.
+ *
+ * keep_buf determines what is done with the buffer in the FALSE-result cases.
+ * When the caller specifies keep_buf = true, we retain the pin on the buffer
+ * and return it in *userbuf (so the caller must eventually unpin it); when
+ * keep_buf = false, the pin is released and *userbuf is set to InvalidBuffer.
+ *
+ * stats_relation is the relation to charge the heap_fetch operation against
+ * for statistical purposes.  (This could be the heap rel itself, an
+ * associated index, or NULL to not count the fetch at all.)
+ *
+ * heap_fetch does not follow HOT chains: only the exact TID requested will
+ * be fetched.
+ *
+ * It is somewhat inconsistent that we ereport() on invalid block number but
+ * return false on invalid item number.  There are a couple of reasons though.
+ * One is that the caller can relatively easily check the block number for
+ * validity, but cannot check the item number without reading the page
+ * himself.  Another is that when we are following a t_ctid link, we can be
+ * reasonably confident that the page number is valid (since VACUUM shouldn't
+ * truncate off the destination page without having killed the referencing
+ * tuple first), but the item number might well not be good.
+ */
+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
+ *
+ * Note that this acquires a buffer pin, which the caller must release.
+ *
+ * Input parameters:
+ *	relation: relation containing tuple (caller must hold suitable lock)
+ *	tuple->t_self: TID of tuple to lock (rest of struct need not be valid)
+ *	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
+ *	follow_updates: if true, follow the update chain to also lock descendant
+ *		tuples.
+ *
+ * Output parameters:
+ *	*tuple: all fields filled in
+ *	*buffer: set to buffer holding tuple (pinned but not locked at exit)
+ *	*hufd: filled in failure cases (see below)
+ *
+ * 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.
+ *
+ * See README.tuplock for a thorough explanation of this mechanism.
+ */
+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
+ *
+ * This is like heap_insert(), but inserts multiple tuples in one operation.
+ * That's faster than calling heap_insert() in a loop, because when multiple
+ * tuples can be inserted on a single page, we can write just a single WAL
+ * record covering all of them, and only need to lock/unlock the page once.
+ *
+ * Note: this leaks memory into the current memory context. You can create a
+ * temporary context before calling this, if that's a problem.
+ */
+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
+ *
+ * Marks a tuple that was speculatively inserted in the same command as dead,
+ * by setting its xmin as invalid.  That makes it immediately appear as dead
+ * to all transactions, including our own.  In particular, it makes
+ * HeapTupleSatisfiesDirty() regard the tuple as dead, so that another backend
+ * inserting a duplicate key value won't unnecessarily wait for our whole
+ * transaction to finish (it'll just wait for our speculative insertion to
+ * finish).
+ *
+ * Killing the tuple prevents "unprincipled deadlocks", which are deadlocks
+ * that arise due to a mutual dependency that is not user visible.  By
+ * definition, unprincipled deadlocks cannot be prevented by the user
+ * reordering lock acquisition in client code, because the implementation level
+ * lock acquisitions are not under the user's direct control.  If speculative
+ * inserters did not take this precaution, then under high concurrency they
+ * could deadlock with each other, which would not be acceptable.
+ *
+ * This is somewhat redundant with heap_delete, but we prefer to have a
+ * dedicated routine with stripped down requirements.  Note that this is also
+ * used to delete the TOAST tuples created during speculative insertion.
+ *
+ * This routine does not affect logical decoding as it only looks at
+ * confirmation records.
+ */
+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
+ *
+ * This forces the heap contents (including TOAST heap if any) down to disk.
+ * If we skipped using WAL, and WAL is otherwise needed, we must force the
+ * relation down to disk before it's safe to commit the transaction.  This
+ * requires writing out any dirty buffers and then doing a forced fsync.
+ *
+ * Indexes are not touched.  (Currently, index operations associated with
+ * the commands that use this are WAL-logged and so do not need fsync.
+ * That behavior might change someday, but in any case it's likely that
+ * any fsync decisions required would be per-index and hence not appropriate
+ * to be done here.)
+ */
+void
+storage_sync(Relation rel)
+{
+	rel->rd_stamroutine->relation_sync(rel);
+}
diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index 5a8f1dab83..ba9592d055 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/commands/copy.c b/src/backend/commands/copy.c
index 96cda7091b..ad28b3af3e 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"
@@ -2716,8 +2717,6 @@ CopyFrom(CopyState cstate)
 
 			if (slot == NULL)	/* "do nothing" */
 				skip_tuple = true;
-			else				/* trigger might have changed tuple */
-				tuple = ExecHeapifySlot(slot);
 		}
 
 		if (!skip_tuple)
@@ -2780,19 +2779,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);
@@ -2888,7 +2879,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;
 }
@@ -2921,12 +2912,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);
 
 	/*
@@ -2942,10 +2933,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);
 		}
@@ -2962,8 +2952,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 c902293741..458c897741 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 005bdbd023..680a65cad5 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2352,17 +2352,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 +3016,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 +3035,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 +3071,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 +3118,7 @@ ltrmark:;
 	{
 		Page		page;
 		ItemId		lp;
+		HeapTupleData tupledata;
 
 		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 
@@ -3130,17 +3137,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 +3953,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 +4027,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 +4043,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 2e8aca59a7..569566cc92 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1894,7 +1894,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 +1974,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				 */
 				if (resultRelInfo->ri_PartitionRoot)
 				{
-					HeapTuple	tuple = ExecFetchSlotTuple(slot);
+					StorageTuple tuple = ExecFetchSlotTuple(slot);
 					TupleConversionMap *map;
 
 					rel = resultRelInfo->ri_PartitionRoot;
@@ -2021,7 +2021,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 +2480,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 +2498,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 +2531,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 +2564,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 +2579,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 +2598,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 +2620,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 +2650,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 +2702,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 +2732,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 +2740,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 +2749,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 +2768,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 +2778,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 +2836,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 +2855,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 +2883,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 +2914,6 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 
 		if (erm->markType == ROW_MARK_REFERENCE)
 		{
-			HeapTuple	copyTuple;
-
 			Assert(erm->relation != NULL);
 
 			/* fetch the tuple's ctid */
@@ -2929,11 +2937,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 +2955,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 +2976,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);
+
 		}
 	}
 }
@@ -3151,8 +3147,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 07640a9992..24b5ff7298 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/execTuples.c b/src/backend/executor/execTuples.c
index 93aeb3eb6e..d618b3d903 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -83,6 +83,7 @@
 
 #include "access/heapam_common.h"
 #include "access/htup_details.h"
+#include "access/storageamapi.h"
 #include "access/tuptoaster.h"
 #include "funcapi.h"
 #include "catalog/pg_type.h"
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 ec5c543bdd..965c629870 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/executor.h"
 #include "executor/nodeModifyTable.h"
@@ -164,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;
 
@@ -191,7 +192,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
 static void
 ExecCheckHeapTupleVisible(EState *estate,
 						  Relation rel,
-						  HeapTuple tuple,
+						  StorageTuple tuple,
 						  Buffer buffer)
 {
 	if (!IsolationUsesXactSnapshot())
@@ -204,13 +205,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")));
@@ -226,19 +229,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);
 }
 
 /* ----------------------------------------------------------------
@@ -259,7 +263,7 @@ ExecInsert(ModifyTableState *mtstate,
 		   EState *estate,
 		   bool canSetTag)
 {
-	HeapTuple	tuple;
+	StorageTuple tuple;
 	ResultRelInfo *resultRelInfo;
 	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
@@ -267,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
 	 */
@@ -284,6 +282,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.
@@ -374,19 +374,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.
@@ -404,9 +416,6 @@ ExecInsert(ModifyTableState *mtstate,
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
-
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -418,9 +427,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)
@@ -436,14 +442,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;
 	}
@@ -463,7 +467,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
@@ -493,7 +498,6 @@ ExecInsert(ModifyTableState *mtstate,
 			/* Perform a speculative insertion. */
 			uint32		specToken;
 			ItemPointerData conflictTid;
-			bool		specConflict;
 
 			/*
 			 * Do a non-conclusive check for conflicts first.
@@ -508,7 +512,7 @@ ExecInsert(ModifyTableState *mtstate,
 			 * speculatively.
 			 */
 	vlock:
-			specConflict = false;
+			slot->tts_specConflict = false;
 			if (!ExecCheckIndexConstraints(slot, estate, &conflictTid,
 										   arbiterIndexes))
 			{
@@ -554,24 +558,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
@@ -587,7 +584,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;
@@ -599,19 +596,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);
 		}
 	}
 
@@ -619,11 +611,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);
@@ -674,7 +666,7 @@ ExecInsert(ModifyTableState *mtstate,
 static TupleTableSlot *
 ExecDelete(ModifyTableState *mtstate,
 		   ItemPointer tupleid,
-		   HeapTuple oldtuple,
+		   StorageTuple oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate,
@@ -719,8 +711,6 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
-		HeapTuple	tuple;
-
 		/*
 		 * delete from foreign table: let the FDW do it
 		 *
@@ -746,8 +736,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
 	{
@@ -761,11 +753,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:
@@ -861,7 +853,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)
@@ -875,20 +867,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);
@@ -897,7 +888,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))
@@ -934,14 +925,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;
@@ -1006,14 +997,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
 	{
@@ -1023,7 +1014,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
@@ -1059,11 +1050,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:
@@ -1134,26 +1128,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)
@@ -1211,11 +1185,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);
@@ -1226,10 +1201,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:
@@ -1254,7 +1227,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"),
@@ -1285,7 +1259,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:
@@ -1313,10 +1289,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
@@ -1331,7 +1307,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 */
 	}
@@ -1371,12 +1349,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;
 }
 
@@ -1579,7 +1559,7 @@ ExecModifyTable(PlanState *pstate)
 	ItemPointer tupleid = NULL;
 	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..cdd45ef313 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -133,40 +133,19 @@ 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,
-		   HeapTuple tuple, Buffer *userbuf, bool keep_buf,
-		   Relation stats_relation);
+extern Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid,
+			int options, BulkInsertState bistate);
 extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
 					   Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
 					   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);
 
 extern BulkInsertState GetBulkInsertState(void);
 extern void FreeBulkInsertState(BulkInsertState);
 extern void ReleaseBulkInsertStatePin(BulkInsertState bistate);
 
-extern Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid,
-			int options, BulkInsertState bistate);
-extern void heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
-				  CommandId cid, int options, BulkInsertState bistate);
-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 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,
-				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);
@@ -179,7 +158,6 @@ extern void simple_heap_delete(Relation relation, ItemPointer tid);
 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);
 
 /* in heap/pruneheap.c */
diff --git a/src/include/access/heapam_common.h b/src/include/access/heapam_common.h
index feb35c5024..14aad60546 100644
--- a/src/include/access/heapam_common.h
+++ b/src/include/access/heapam_common.h
@@ -34,6 +34,111 @@
 #include "utils/relcache.h"
 #include "utils/snapshot.h"
 
+/*
+ * Each tuple lock mode has a corresponding heavyweight lock, and one or two
+ * corresponding MultiXactStatuses (one to merely lock tuples, another one to
+ * update them).  This table (and the macros below) helps us determine the
+ * heavyweight lock mode and MultiXactStatus values to use for any particular
+ * tuple lock strength.
+ *
+ * Don't look at lockstatus/updstatus directly!  Use get_mxact_status_for_lock
+ * instead.
+ */
+static const struct
+{
+	LOCKMODE	hwlock;
+	int			lockstatus;
+	int			updstatus;
+}
+
+			tupleLockExtraInfo[MaxLockTupleMode + 1] =
+{
+	{							/* LockTupleKeyShare */
+		AccessShareLock,
+		MultiXactStatusForKeyShare,
+		-1						/* KeyShare does not allow updating tuples */
+	},
+	{							/* LockTupleShare */
+		RowShareLock,
+		MultiXactStatusForShare,
+		-1						/* Share does not allow updating tuples */
+	},
+	{							/* LockTupleNoKeyExclusive */
+		ExclusiveLock,
+		MultiXactStatusForNoKeyUpdate,
+		MultiXactStatusNoKeyUpdate
+	},
+	{							/* LockTupleExclusive */
+		AccessExclusiveLock,
+		MultiXactStatusForUpdate,
+		MultiXactStatusUpdate
+	}
+};
+
+/*
+ * This table maps tuple lock strength values for each particular
+ * MultiXactStatus value.
+ */
+static const int MultiXactStatusLock[MaxMultiXactStatus + 1] =
+{
+	LockTupleKeyShare,			/* ForKeyShare */
+	LockTupleShare,				/* ForShare */
+	LockTupleNoKeyExclusive,	/* ForNoKeyUpdate */
+	LockTupleExclusive,			/* ForUpdate */
+	LockTupleNoKeyExclusive,	/* NoKeyUpdate */
+	LockTupleExclusive			/* Update */
+};
+
+/* Get the LockTupleMode for a given MultiXactStatus */
+#define TUPLOCK_from_mxstatus(status) \
+			(MultiXactStatusLock[(status)])
+
+/*
+ * Acquire heavyweight locks on tuples, using a LockTupleMode strength value.
+ * This is more readable than having every caller translate it to lock.h's
+ * LOCKMODE.
+ */
+#define LockTupleTuplock(rel, tup, mode) \
+	LockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
+#define UnlockTupleTuplock(rel, tup, mode) \
+	UnlockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
+#define ConditionalLockTupleTuplock(rel, tup, mode) \
+	ConditionalLockTuple((rel), (tup), tupleLockExtraInfo[mode].hwlock)
+/* Get the LOCKMODE for a given MultiXactStatus */
+#define LOCKMODE_from_mxstatus(status) \
+			(tupleLockExtraInfo[TUPLOCK_from_mxstatus((status))].hwlock)
+extern HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
+					TransactionId xid, CommandId cid, int options);
+
+extern HTSU_Result heap_delete(Relation relation, ItemPointer tid,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			HeapUpdateFailureData *hufd);
+extern HTSU_Result heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
+			CommandId cid, Snapshot crosscheck, bool wait,
+			HeapUpdateFailureData *hufd, LockTupleMode *lockmode);
+
+extern XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
+extern uint8 compute_infobits(uint16 infomask, uint16 infomask2);
+extern void compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
+						  uint16 old_infomask2, TransactionId add_to_xmax,
+						  LockTupleMode mode, bool is_update,
+						  TransactionId *result_xmax, uint16 *result_infomask,
+						  uint16 *result_infomask2);
+extern void UpdateXmaxHintBits(HeapTupleHeader tuple, Buffer buffer, TransactionId xid);
+extern bool DoesMultiXactIdConflict(MultiXactId multi, uint16 infomask,
+						LockTupleMode lockmode);
+extern bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
+						   uint16 infomask, Relation rel, int *remaining);
+
+extern void MultiXactIdWait(MultiXactId multi, MultiXactStatus status, uint16 infomask,
+				Relation rel, ItemPointer ctid, XLTW_Oper oper,
+				int *remaining);
+extern MultiXactStatus get_mxact_status_for_lock(LockTupleMode mode, bool is_update);
+
+extern void heap_inplace_update(Relation relation, HeapTuple tuple);
+extern bool heap_acquire_tuplock(Relation relation, ItemPointer tid,
+					 LockTupleMode mode, LockWaitPolicy wait_policy,
+					 bool *have_tuple_lock);
 
 /* in heap/heapam_common.c */
 extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
@@ -43,6 +148,28 @@ extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin);
 typedef struct StorageSlotAmRoutine StorageSlotAmRoutine;
 extern StorageSlotAmRoutine * heapam_storage_slot_handler(void);
 
+
+/*
+ * Given two versions of the same t_infomask for a tuple, compare them and
+ * return whether the relevant status for a tuple Xmax has changed.  This is
+ * used after a buffer lock has been released and reacquired: we want to ensure
+ * that the tuple state continues to be the same it was when we previously
+ * examined it.
+ *
+ * Note the Xmax field itself must be compared separately.
+ */
+static inline bool
+xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask)
+{
+	const uint16 interesting =
+	HEAP_XMAX_IS_MULTI | HEAP_XMAX_LOCK_ONLY | HEAP_LOCK_MASK;
+
+	if ((new_infomask & interesting) != (old_infomask & interesting))
+		return true;
+
+	return false;
+}
+
 /*
  * SetHintBits()
  *
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/storageam.h b/src/include/access/storageam.h
new file mode 100644
index 0000000000..447935ba10
--- /dev/null
+++ b/src/include/access/storageam.h
@@ -0,0 +1,86 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "executor/tuptable.h"
+#include "nodes/execnodes.h"
+
+/* A physical tuple coming from a storage AM scan */
+typedef void *StorageTuple;
+
+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 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 90b16ccd0a..6e790bb21a 100644
--- a/src/include/access/storageamapi.h
+++ b/src/include/access/storageamapi.h
@@ -15,30 +15,11 @@
 #include "access/heapam.h"
 #include "access/sdir.h"
 #include "access/skey.h"
+#include "access/storageam.h"
 #include "executor/tuptable.h"
 #include "utils/relcache.h"
 #include "utils/snapshot.h"
 
-/* A physical tuple coming from a storage AM scan */
-typedef void *StorageTuple;
-
-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;
-
-
 typedef HeapScanDesc (*ScanBegin_function) (Relation relation,
 											Snapshot snapshot,
 											int nkeys, ScanKey key,
@@ -70,11 +51,9 @@ typedef bool (*HotSearchBuffer_function) (ItemPointer tid, Relation relation,
 										  Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
 										  bool *all_dead, bool first_call);
 
-typedef Oid (*TupleInsert_function) (Relation relation,
-									 TupleTableSlot *tupslot,
-									 CommandId cid,
-									 int options,
-									 BulkInsertState bistate);
+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,
@@ -86,11 +65,14 @@ typedef HTSU_Result (*TupleDelete_function) (Relation relation,
 typedef HTSU_Result (*TupleUpdate_function) (Relation relation,
 											 ItemPointer otid,
 											 TupleTableSlot *slot,
+											 EState *estate,
 											 CommandId cid,
 											 Snapshot crosscheck,
 											 bool wait,
 											 HeapUpdateFailureData *hufd,
-											 LockTupleMode *lockmode);
+											 LockTupleMode *lockmode,
+											 InsertIndexTuples IndexFunc,
+											 List **recheckIndexes);
 
 typedef bool (*TupleFetch_function) (Relation relation,
 									 ItemPointer tid,
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 c4ecf0d50f..85b1e15093 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -196,16 +196,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);
 extern void ExecSetupPartitionTupleRouting(Relation rel,
 							   Index resultRTindex,
 							   EState *estate,
@@ -528,9 +528,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 a730f265dd..630899ea22 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 d209ec012c..9d1549115d 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -503,7 +503,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 */
 
@@ -1751,7 +1751,7 @@ typedef struct SharedSortInfo
 {
 	int			num_workers;
 	TuplesortInstrumentation sinstrument[FLEXIBLE_ARRAY_MEMBER];
-} SharedSortInfo;
+}			SharedSortInfo;
 
 /* ----------------
  *	 SortState information
@@ -2033,7 +2033,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;
 
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index b1c94de683..1a403b8e21 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -14,7 +14,6 @@
 #ifndef BUFMGR_H
 #define BUFMGR_H
 
-#include "access/storageamapi.h"
 #include "storage/block.h"
 #include "storage/buf.h"
 #include "storage/bufpage.h"
-- 
2.14.2.windows.1

#108Michael Paquier
michael.paquier@gmail.com
In reply to: Haribabu Kommi (#107)
Re: [HACKERS] Pluggable storage

On Tue, Nov 7, 2017 at 6:34 PM, Haribabu Kommi <kommi.haribabu@gmail.com> wrote:

On Tue, Oct 31, 2017 at 8:59 PM, Haribabu Kommi <kommi.haribabu@gmail.com>
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.
--
Michael

#109Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Robert Haas (#105)
Re: [HACKERS] Pluggable storage

Hmm. Am I reading it right that this discussion led to moving
essentially all code from tqual.c to heapam? Given the hard time we've
had to get tqual.c right, it seems fundamentally misguided to me to
require that every single storage AM reimplements all the visibility
routines.

I think that changing tqual's API (such as not passing HeapTuples
anymore but some other more general representation) would be okay and
should be sufficient, but this wholesale movement of code seems
dangerous and wasteful in terms of future reimplementations that will be
necessary.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#110Amit Kapila
amit.kapila16@gmail.com
In reply to: Alvaro Herrera (#109)
Re: [HACKERS] Pluggable storage

On Tue, Nov 14, 2017 at 4:12 PM, Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:

Hmm. Am I reading it right that this discussion led to moving
essentially all code from tqual.c to heapam? Given the hard time we've
had to get tqual.c right, it seems fundamentally misguided to me to
require that every single storage AM reimplements all the visibility
routines.

I think that changing tqual's API (such as not passing HeapTuples
anymore but some other more general representation) would be okay and
should be sufficient, but this wholesale movement of code seems
dangerous and wasteful in terms of future reimplementations that will be
necessary.

I don't think the idea is to touch existing tqual.c in any significant
way. However, some other storage engine might need a different way to
check the visibility of tuples so we need provision for that. I think
for storage engine where tuple headers no longer contain transaction
information and or the old versions of tuples are chained in separate
storage (say undo storage), current visibility routines can be used.
I think the current tqual.c is quite tightly coupled with the
HeapTuple representation, so changing that or adding more code to it
for another storage engine with different tuple format won't be of
much use.

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

#111Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#108)
Re: [HACKERS] Pluggable storage

On Tue, Nov 14, 2017 at 5:09 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Tue, Nov 7, 2017 at 6:34 PM, Haribabu Kommi <kommi.haribabu@gmail.com> wrote:

On Tue, Oct 31, 2017 at 8:59 PM, Haribabu Kommi <kommi.haribabu@gmail.com>
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.
--
Michael

#112Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Michael Paquier (#111)
8 attachment(s)
Re: [HACKERS] Pluggable storage

On Wed, Nov 29, 2017 at 3:50 PM, Michael Paquier <michael.paquier@gmail.com>
wrote:

On Tue, Nov 14, 2017 at 5:09 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Tue, Nov 7, 2017 at 6:34 PM, Haribabu Kommi <kommi.haribabu@gmail.com>

wrote:

On Tue, Oct 31, 2017 at 8:59 PM, Haribabu Kommi <

kommi.haribabu@gmail.com>

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:

0008-Remove-HeapScanDesc-usage-outside-heap.patchapplication/octet-stream; name=0008-Remove-HeapScanDesc-usage-outside-heap.patchDownload
From 50490821517164ac55699789df7e733f8ffb95c3 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0001-Change-Create-Access-method-to-include-storage-handl.patchapplication/octet-stream; name=0001-Change-Create-Access-method-to-include-storage-handl.patchDownload
From 61f7fa4ee40432796985ff9b7ea554c009336034 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
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

0002-Storage-AM-folder-and-init-functions.patchapplication/octet-stream; name=0002-Storage-AM-folder-and-init-functions.patchDownload
From faa7a8c667484ee8841b131e6b6b59a24be21d23 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
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

0003-Adding-storageam-hanlder-to-relation-structure.patchapplication/octet-stream; name=0003-Adding-storageam-hanlder-to-relation-structure.patchDownload
From 4166f6f7423bb5a0aa712d47669a4bbe0da9f083 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
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

0004-Adding-tuple-visibility-function-to-storage-AM.patchapplication/octet-stream; name=0004-Adding-tuple-visibility-function-to-storage-AM.patchDownload
From 662700ceb5bbe8f0c46448fdddbc18a603d54a90 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0005-slot-hooks-are-added-to-storage-AM.patchapplication/octet-stream; name=0005-slot-hooks-are-added-to-storage-AM.patchDownload
From 477e34008a735e9f340fe4913847d6f6117614c0 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0006-Tuple-Insert-API-is-added-to-Storage-AM.patchapplication/octet-stream; name=0006-Tuple-Insert-API-is-added-to-Storage-AM.patchDownload
From 4abb8b578a22b3ee46d608af41ec52b50bf072af Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0007-Scan-functions-are-added-to-storage-AM.patchapplication/octet-stream; name=0007-Scan-functions-are-added-to-storage-AM.patchDownload
From 6df7050db6b88afe01893f7473ed7ff4892fff24 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

#113Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Haribabu Kommi (#112)
11 attachment(s)
Re: [HACKERS] Pluggable storage

On Tue, Dec 12, 2017 at 3:06 PM, Haribabu Kommi <kommi.haribabu@gmail.com>
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:

0010-storage-rewrite-functionality.patchapplication/octet-stream; name=0010-storage-rewrite-functionality.patchDownload
From 2caf22da57885cdd96929cfa02b5c8910c58baad Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0011-Improve-tuple-locking-interface.patchapplication/octet-stream; name=0011-Improve-tuple-locking-interface.patchDownload
From 9b573b0ecd59086916e8af822234c3c640904c49 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0001-Change-Create-Access-method-to-include-storage-handl.patchapplication/octet-stream; name=0001-Change-Create-Access-method-to-include-storage-handl.patchDownload
From 88ab3906d59cb28eda0bfb97f9ea3f05976c20fd Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
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

0002-Storage-AM-folder-and-init-functions.patchapplication/octet-stream; name=0002-Storage-AM-folder-and-init-functions.patchDownload
From a6fa7e7a1d31b31d66ec8f2722f18a2d80aa1353 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
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

0003-Adding-storageam-hanlder-to-relation-structure.patchapplication/octet-stream; name=0003-Adding-storageam-hanlder-to-relation-structure.patchDownload
From 2273ccbaf5733227da7e0a1582992e99d7c8a5a9 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
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

0004-Adding-tuple-visibility-function-to-storage-AM.patchapplication/octet-stream; name=0004-Adding-tuple-visibility-function-to-storage-AM.patchDownload
From 088073894d888c363c71d92614ec6be675ed2bd0 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0005-slot-hooks-are-added-to-storage-AM.patchapplication/octet-stream; name=0005-slot-hooks-are-added-to-storage-AM.patchDownload
From 7a8e4e7aaa199a31b355f3149385428e8cf8135f Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0006-Tuple-Insert-API-is-added-to-Storage-AM.patchapplication/octet-stream; name=0006-Tuple-Insert-API-is-added-to-Storage-AM.patchDownload
From 7ad4a052339ff64ee459105f53293a4541283897 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0007-Scan-functions-are-added-to-storage-AM.patchapplication/octet-stream; name=0007-Scan-functions-are-added-to-storage-AM.patchDownload
From ae2e9564a60334ac004838db7ad06b57eb57f1a1 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0008-Remove-HeapScanDesc-usage-outside-heap.patchapplication/octet-stream; name=0008-Remove-HeapScanDesc-usage-outside-heap.patchDownload
From b6d92d8a8d0fc0826de0c1618540e04b4255989d Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0009-BulkInsertState-is-moved-into-storage.patchapplication/octet-stream; name=0009-BulkInsertState-is-moved-into-storage.patchDownload
From ca3e363fdd064f7b2ea667aee79f038fa9143510 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

#114Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Haribabu Kommi (#113)
Re: [HACKERS] Pluggable storage

Hi!

On Wed, Dec 27, 2017 at 6:54 AM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:

On Tue, Dec 12, 2017 at 3:06 PM, Haribabu Kommi <kommi.haribabu@gmail.com>
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

#115Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Alexander Korotkov (#114)
12 attachment(s)
Re: [HACKERS] Pluggable storage

On Wed, Dec 27, 2017 at 11:33 PM, Alexander Korotkov <
a.korotkov@postgrespro.ru> 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:

0012-Storage-shared-memory-API.patchapplication/octet-stream; name=0012-Storage-shared-memory-API.patchDownload
From 245c40a2ac2e44e039d8657590873515d25793db Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0001-Change-Create-Access-method-to-include-storage-handl.patchapplication/octet-stream; name=0001-Change-Create-Access-method-to-include-storage-handl.patchDownload
From 55947e29755bc3b6fad0fa7cb1e0001d32c95d49 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
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

0002-Storage-AM-folder-and-init-functions.patchapplication/octet-stream; name=0002-Storage-AM-folder-and-init-functions.patchDownload
From 1ca8f8f2f92a3d7b4605e4b47e50e245d3f10265 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
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

0003-Adding-storageam-hanlder-to-relation-structure.patchapplication/octet-stream; name=0003-Adding-storageam-hanlder-to-relation-structure.patchDownload
From 70e05d3117240a1df2dbc12b0ede9bd9aa2d8b46 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
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

0004-Adding-tuple-visibility-function-to-storage-AM.patchapplication/octet-stream; name=0004-Adding-tuple-visibility-function-to-storage-AM.patchDownload
From 6abc4434300f239594abb4105819701cdcc792e3 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0005-slot-hooks-are-added-to-storage-AM.patchapplication/octet-stream; name=0005-slot-hooks-are-added-to-storage-AM.patchDownload
From bec00da92fd32c42e99f5166b71ff7d93ac3bd5a Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0006-Tuple-Insert-API-is-added-to-Storage-AM.patchapplication/octet-stream; name=0006-Tuple-Insert-API-is-added-to-Storage-AM.patchDownload
From b80931c9e7c73daf109199c1dfecf636e21a26c7 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0007-Scan-functions-are-added-to-storage-AM.patchapplication/octet-stream; name=0007-Scan-functions-are-added-to-storage-AM.patchDownload
From c3b7d4d6b528a7c6f4eb4566fcd5f9633d0b69b6 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0008-Remove-HeapScanDesc-usage-outside-heap.patchapplication/octet-stream; name=0008-Remove-HeapScanDesc-usage-outside-heap.patchDownload
From 51af500f18c9a06b11b77f747bb6f0cb02f7c665 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0009-BulkInsertState-is-moved-into-storage.patchapplication/octet-stream; name=0009-BulkInsertState-is-moved-into-storage.patchDownload
From 8baa289995861beb23c930f782377b8aaeaf1906 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0010-storage-rewrite-functionality.patchapplication/octet-stream; name=0010-storage-rewrite-functionality.patchDownload
From 940342d009a753251d2c94ed5e594062b5cd925e Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0011-Improve-tuple-locking-interface.patchapplication/octet-stream; name=0011-Improve-tuple-locking-interface.patchDownload
From ef7898c6512c91352c68eaca7b10e7811449861c Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

#116Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Haribabu Kommi (#115)
Re: [HACKERS] Pluggable storage

On Wed, Jan 3, 2018 at 10:08 AM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:

On Wed, Dec 27, 2017 at 11:33 PM, Alexander Korotkov <
a.korotkov@postgrespro.ru> 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

#117Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Alexander Korotkov (#116)
Re: [HACKERS] Pluggable storage

On Thu, Jan 4, 2018 at 10:00 AM, Alexander Korotkov <
a.korotkov@postgrespro.ru> wrote:

On Wed, Jan 3, 2018 at 10:08 AM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:

On Wed, Dec 27, 2017 at 11:33 PM, Alexander Korotkov <
a.korotkov@postgrespro.ru> 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

#118Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Haribabu Kommi (#117)
Re: [HACKERS] Pluggable storage

On Thu, Jan 4, 2018 at 8:03 AM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:

On Thu, Jan 4, 2018 at 10:00 AM, Alexander Korotkov <
a.korotkov@postgrespro.ru> wrote:

On Wed, Jan 3, 2018 at 10:08 AM, Haribabu Kommi <kommi.haribabu@gmail.com

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

#119Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Alexander Korotkov (#118)
Re: [HACKERS] Pluggable storage

On Fri, Jan 5, 2018 at 9:55 AM, Alexander Korotkov <
a.korotkov@postgrespro.ru> wrote:

On Thu, Jan 4, 2018 at 8:03 AM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:

On Thu, Jan 4, 2018 at 10:00 AM, Alexander Korotkov <
a.korotkov@postgrespro.ru> wrote:

On Wed, Jan 3, 2018 at 10:08 AM, Haribabu Kommi <
kommi.haribabu@gmail.com> 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

#120Robert Haas
robertmhaas@gmail.com
In reply to: Haribabu Kommi (#115)
Re: [HACKERS] Pluggable storage

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

#121Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Robert Haas (#120)
Re: [HACKERS] Pluggable storage

On Fri, Jan 5, 2018 at 7:20 PM, Robert Haas <robertmhaas@gmail.com> 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

#122Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Alexander Korotkov (#121)
12 attachment(s)
Re: [HACKERS] Pluggable storage

On Sat, Jan 6, 2018 at 6:31 AM, Alexander Korotkov <
a.korotkov@postgrespro.ru> wrote:

On Fri, Jan 5, 2018 at 7:20 PM, Robert Haas <robertmhaas@gmail.com> 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:

0010-table-rewrite-functionality.patchapplication/octet-stream; name=0010-table-rewrite-functionality.patchDownload
From 3240f10aa16ff60c5d8b4137be7ca859f9f0611e Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0011-Improve-tuple-locking-interface.patchapplication/octet-stream; name=0011-Improve-tuple-locking-interface.patchDownload
From 2e460d5bab7a712b3b55c88ba0a6f2023dc474b0 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0012-Table-AM-shared-memory-API.patchapplication/octet-stream; name=0012-Table-AM-shared-memory-API.patchDownload
From 8051be7f253b920b79e0279688245e0ac437a0a3 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0001-Change-Create-Access-method-to-include-table-handler.patchapplication/octet-stream; name=0001-Change-Create-Access-method-to-include-table-handler.patchDownload
From ab02ba63031a6233860737c29bb66eeb0066f9eb Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
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

0002-Table-AM-API-init-functions.patchapplication/octet-stream; name=0002-Table-AM-API-init-functions.patchDownload
From 55d055bcd61d0ecfc5fc8aee39ab2380ed934f51 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
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

0003-Adding-tableam-hanlder-to-relation-structure.patchapplication/octet-stream; name=0003-Adding-tableam-hanlder-to-relation-structure.patchDownload
From 956b15b97fc113ce902a4fe1bd0bbacca11e762f Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
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

0004-Adding-tuple-visibility-functions-to-table-AM.patchapplication/octet-stream; name=0004-Adding-tuple-visibility-functions-to-table-AM.patchDownload
From 94a99d600c2e72b61baaf660d756517c719df94a Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0005-slot-hooks-are-added-to-table-AM.patchapplication/octet-stream; name=0005-slot-hooks-are-added-to-table-AM.patchDownload
From 6dae70eae95f1f2a1acfcaee7d16a6ef4b603703 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0006-Tuple-Insert-API-is-added-to-table-AM.patchapplication/octet-stream; name=0006-Tuple-Insert-API-is-added-to-table-AM.patchDownload
From e7b0bde744f63350675e5801f390c0f5041bcf61 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0007-Scan-functions-are-added-to-table-AM.patchapplication/octet-stream; name=0007-Scan-functions-are-added-to-table-AM.patchDownload
From c8823a3da61731a204c5620dab728dd0d007843f Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0008-Remove-HeapScanDesc-usage-outside-heap.patchapplication/octet-stream; name=0008-Remove-HeapScanDesc-usage-outside-heap.patchDownload
From e244c06c9987f5e866a8e132091fa04434f94166 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

0009-BulkInsertState-is-added-into-table-AM.patchapplication/octet-stream; name=0009-BulkInsertState-is-added-into-table-AM.patchDownload
From 5b93c2ba9d11664b840df0507911630eab0424da Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
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

#123Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Haribabu Kommi (#122)
Re: [HACKERS] Pluggable storage

On Tue, Jan 9, 2018 at 11:42 PM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:

Updated patches are attached.

To integrate the columnar store with the pluggable storage API, I found that
there are couple of other things also that needs to be supported.

1. Choosing the right table access method for a particular table?

I am thinking of adding a new table option to let the user select the
correct table
access method that the user wants for the table. HEAP is the default access
method. This approach may be simple and doesn't need any syntax changes.

Or Is it fine to add syntax "USING method" to CREATE TABLE similar like
CREATE INDEX?

comments?

2. As the columnar storage needs many other relations that are needs to be
created along with main relation.

As these extra relations are internal to the storage and shouldn't be
visible
directly from pg_class and these will be stored in the storage specific
catalog tables. A dependency is created for the original table as these
storage
specific tables must be created/dropped/altered whenever there is a change
with the original table.

Is it fine to add new API while creating/altering/drop the table to get the
control?
or to use only exiting processutility hook?

Regards,
Hari Babu
Fujitsu Australia

#124Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Haribabu Kommi (#123)
Re: [HACKERS] Pluggable storage

Hi, Haribabu!

On Mon, Feb 5, 2018 at 2:22 PM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:

On Tue, Jan 9, 2018 at 11:42 PM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:

Updated patches are attached.

To integrate the columnar store with the pluggable storage API, I found
that
there are couple of other things also that needs to be supported.

1. Choosing the right table access method for a particular table?

I am thinking of adding a new table option to let the user select the
correct table
access method that the user wants for the table. HEAP is the default access
method. This approach may be simple and doesn't need any syntax changes.

Or Is it fine to add syntax "USING method" to CREATE TABLE similar like
CREATE INDEX?

comments?

Sure, user needs to specify particular table access method when creating a
table. "USING method" is good for me. I think it's better to reuse
already existing syntax rather than reinventing something new.

2. As the columnar storage needs many other relations that are needs to be
created along with main relation.

That's an interesting point. During experimenting on some new index access
methods I also have to define some kind of "internal" relations invisible
for user, but essential for main relation functionality. I've made them in
hackery manner, and I think legal mechanism for them would be very good.

As these extra relations are internal to the storage and shouldn't be
visible
directly from pg_class and these will be stored in the storage specific
catalog tables. A dependency is created for the original table as these
storage
specific tables must be created/dropped/altered whenever there is a change
with the original table.

I think that internal relations should be visible from pg_class, otherwise
it wouldn't be possible to define dependencies over them. But they should
have different relkind, so they wouldn't be messed up with regular
relations.

Is it fine to add new API while creating/altering/drop the table to get the

control?
or to use only exiting processutility hook?

I believe that we need some generic way for defining internal relations,
not hooks. For me it seems like useful feature for further development of
both index access methods and table access methods.

During developer meeting [1], I've proposed to reorder patches so that
refactoring patches go first and API introduction patches go afterwards.
That would make possible to commit some of refactoring patches earlier
without necessary committing API in the same release cycle. If no
objection, I would do that reordering.

BTW, EnterpriseDB announces zheap table access method (heap with undo log)
[2]: . I think this is great, and I'm looking forward for publishing zheap in mailing lists. But I'm concerning about its compatibility with pluggable table access methods API. Does zheap use table AM API from this thread? Or does it just override current heap and needs to be adopted to use table AM API? Or does it implements own API?
in mailing lists. But I'm concerning about its compatibility with
pluggable table access methods API. Does zheap use table AM API from this
thread? Or does it just override current heap and needs to be adopted to
use table AM API? Or does it implements own API?

*Links*

1. https://wiki.postgresql.org/wiki/FOSDEM/PGDay_2018_
Developer_Meeting#Minutes
2. http://rhaas.blogspot.com.by/2018/01/do-or-undo-there-is-no-vacuum.html

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

#125Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Alexander Korotkov (#124)
14 attachment(s)
Re: [HACKERS] Pluggable storage

On Fri, Feb 16, 2018 at 9:56 PM, Alexander Korotkov <
a.korotkov@postgrespro.ru> wrote:

Hi, Haribabu!

On Mon, Feb 5, 2018 at 2:22 PM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:

To integrate the columnar store with the pluggable storage API, I found
that
there are couple of other things also that needs to be supported.

1. Choosing the right table access method for a particular table?

I am thinking of adding a new table option to let the user select the
correct table
access method that the user wants for the table. HEAP is the default
access
method. This approach may be simple and doesn't need any syntax changes.

Or Is it fine to add syntax "USING method" to CREATE TABLE similar like
CREATE INDEX?

comments?

Sure, user needs to specify particular table access method when creating a
table. "USING method" is good for me. I think it's better to reuse
already existing syntax rather than reinventing something new.

Yes, I also felt the same. Updated patches included this implementation.

2. As the columnar storage needs many other relations that are needs to be
created along with main relation.

That's an interesting point. During experimenting on some new index
access methods I also have to define some kind of "internal" relations
invisible for user, but essential for main relation functionality. I've
made them in hackery manner, and I think legal mechanism for them would be
very good.

As these extra relations are internal to the storage and shouldn't be
visible
directly from pg_class and these will be stored in the storage specific
catalog tables. A dependency is created for the original table as these
storage
specific tables must be created/dropped/altered whenever there is a change
with the original table.

I think that internal relations should be visible from pg_class, otherwise
it wouldn't be possible to define dependencies over them. But they should
have different relkind, so they wouldn't be messed up with regular
relations.

OK.

Is it fine to add new API while creating/altering/drop the table to get

the control?
or to use only exiting processutility hook?

I believe that we need some generic way for defining internal relations,
not hooks. For me it seems like useful feature for further development of
both index access methods and table access methods.

How about a new relkind of RELKIND_EXTERNAL ('e') or something similar
types and these can be created internally by the extensions that needs
extra relations apart
from main relation. These type of relations can have storage, but these
cannot be selected directly by the user using SQL commands.

I will try the above approach.

During developer meeting [1], I've proposed to reorder patches so that
refactoring patches go first and API introduction patches go afterwards.
That would make possible to commit some of refactoring patches earlier
without necessary committing API in the same release cycle. If no
objection, I would do that reordering.

Yes, that will be easy to review the API patches once the refactoring
patches went in.

BTW, EnterpriseDB announces zheap table access method (heap with undo log)
[2]. I think this is great, and I'm looking forward for publishing zheap
in mailing lists. But I'm concerning about its compatibility with
pluggable table access methods API. Does zheap use table AM API from this
thread? Or does it just override current heap and needs to be adopted to
use table AM API? Or does it implements own API?

I am also not sure about on which API the zheap is implemented. But it will
be good if we all come up with table AM API's that works for all the
external storage methods.

Latest rebased patch series are attached.

Regards,
Hari Babu
Fujitsu Australia

Attachments:

0014-ExecARUpdateTriggers-is-updated-to-accept-slot-inste.patchapplication/octet-stream; name=0014-ExecARUpdateTriggers-is-updated-to-accept-slot-inste.patchDownload
From 5a2ca9baf3852eb422e016e6deae9c166664bf7b Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Wed, 14 Feb 2018 16:21:10 +1100
Subject: [PATCH 14/14] ExecARUpdateTriggers is updated to accept slot instead
 of tuple

The After record update trigger function is changed to accept
slot instead of newtuple, thus is reduces the need of TableTuple
variable in the callers.
---
 src/backend/commands/trigger.c         |  4 ++--
 src/backend/executor/execReplication.c |  5 +----
 src/backend/executor/nodeModifyTable.c | 21 ++++++---------------
 src/include/commands/trigger.h         |  2 +-
 src/pl/tcl/pltcl.c                     |  2 +-
 5 files changed, 11 insertions(+), 23 deletions(-)

diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 2322040919..3df9ce12b9 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2846,7 +2846,7 @@ void
 ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 					 ItemPointer tupleid,
 					 HeapTuple fdw_trigtuple,
-					 HeapTuple newtuple,
+					 TupleTableSlot *slot,
 					 List *recheckIndexes,
 					 TransitionCaptureState *transition_capture)
 {
@@ -2858,7 +2858,7 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 		  transition_capture->tcs_update_new_table)))
 	{
 		HeapTuple	trigtuple;
-
+		HeapTuple	newtuple = slot ? ExecHeapifySlot(slot) : NULL;
 		/*
 		 * Note: if the UPDATE is converted into a DELETE+INSERT as part of
 		 * update-partition-key operation, then this function is also called
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 3ab6db5902..48661eb2d6 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -391,7 +391,6 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 						 TupleTableSlot *searchslot, TupleTableSlot *slot)
 {
 	bool		skip_tuple = false;
-	TableTuple tuple;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	ItemPointer tid = &(searchslot->tts_tid);
@@ -427,12 +426,10 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 		table_update(rel, tid, slot, estate, GetCurrentCommandId(true), InvalidSnapshot,
 					   true, &hufd, &lockmode, IndexFunc, &recheckIndexes);
 
-		tuple = ExecHeapifySlot(slot);
-
 		/* AFTER ROW UPDATE Triggers */
 		ExecARUpdateTriggers(estate, resultRelInfo,
 							 tid,
-							 NULL, tuple, recheckIndexes, NULL);
+							 NULL, slot, recheckIndexes, NULL);
 
 		list_free(recheckIndexes);
 	}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index b6b4b153b0..a0d050d19e 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -628,10 +628,9 @@ ExecInsert(ModifyTableState *mtstate,
 	if (mtstate->operation == CMD_UPDATE && mtstate->mt_transition_capture
 		&& mtstate->mt_transition_capture->tcs_update_new_table)
 	{
-		tuple = ExecHeapifySlot(slot);
 		ExecARUpdateTriggers(estate, resultRelInfo, NULL,
 							 NULL,
-							 tuple,
+							 slot,
 							 NULL,
 							 mtstate->mt_transition_capture);
 
@@ -1018,7 +1017,6 @@ ExecUpdate(ModifyTableState *mtstate,
 		   EState *estate,
 		   bool canSetTag)
 {
-	TableTuple tuple;
 	ResultRelInfo *resultRelInfo;
 	Relation	resultRelationDesc;
 	HTSU_Result result;
@@ -1036,7 +1034,7 @@ ExecUpdate(ModifyTableState *mtstate,
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecHeapifySlot(slot);
+	ExecMaterializeSlot(slot);
 
 	/*
 	 * get information on the (current) result relation
@@ -1053,9 +1051,6 @@ ExecUpdate(ModifyTableState *mtstate,
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
-
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW UPDATE Triggers */
@@ -1067,9 +1062,6 @@ ExecUpdate(ModifyTableState *mtstate,
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
-
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
@@ -1089,9 +1081,6 @@ ExecUpdate(ModifyTableState *mtstate,
 		 * tableoid column, so initialize t_tableOid before evaluating them.
 		 */
 		ExecSlotUpdateTupleTableoid(slot, RelationGetRelid(resultRelationDesc));
-
-		/* FDW might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 	else
 	{
@@ -1148,6 +1137,7 @@ lreplace:;
 			PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
 			int			map_index;
 			TupleConversionMap *tupconv_map;
+			HeapTuple	tuple = ExecHeapifySlot(slot);
 
 			/*
 			 * When an UPDATE is run on a leaf partition, we will not have
@@ -1269,6 +1259,8 @@ lreplace:;
 
 		if (result == HeapTupleUpdated && !IsolationUsesXactSnapshot())
 		{
+			TableTuple tuple;
+
 			result = table_lock_tuple(resultRelationDesc, tupleid,
 										estate->es_snapshot,
 										&tuple, estate->es_output_cid,
@@ -1292,7 +1284,6 @@ lreplace:;
 					return NULL;
 				}
 				slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
-				tuple = ExecHeapifySlot(slot);
 				goto lreplace;
 			}
 		}
@@ -1364,7 +1355,7 @@ lreplace:;
 		(estate->es_processed)++;
 
 	/* AFTER ROW UPDATE Triggers */
-	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldtuple, tuple,
+	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldtuple, slot,
 						 recheckIndexes,
 						 mtstate->operation == CMD_INSERT ?
 						 mtstate->mt_oc_transition_capture :
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 093d1ae112..fe67d72084 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -229,7 +229,7 @@ extern void ExecARUpdateTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
 					 ItemPointer tupleid,
 					 HeapTuple fdw_trigtuple,
-					 HeapTuple newtuple,
+					 TupleTableSlot *slot,
 					 List *recheckIndexes,
 					 TransitionCaptureState *transition_capture);
 extern TupleTableSlot *ExecIRUpdateTriggers(EState *estate,
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index 5df4dfdf55..ba703e20f0 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -2438,7 +2438,7 @@ pltcl_process_SPI_result(Tcl_Interp *interp,
 {
 	int			my_rc = TCL_OK;
 	int			loop_rc;
-	HeapTuple  *tuples;
+	TableTuple *tuples;
 	TupleDesc	tupdesc;
 
 	switch (spi_rc)
-- 
2.16.1.windows.4

0001-Change-Create-Access-method-to-include-table-handler.patchapplication/octet-stream; name=0001-Change-Create-Access-method-to-include-table-handler.patchDownload
From 77406290c4645605a91fe2d7630547f1345d9b63 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Tue, 29 Aug 2017 19:45:30 +1000
Subject: [PATCH 01/14] 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 d99f2be2c9..7da347e6bb 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
@@ -5308,16 +5310,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 2a5321315a..5530f4bdeb 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3905,6 +3905,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.16.1.windows.4

0002-Table-AM-API-init-functions.patchapplication/octet-stream; name=0002-Table-AM-API-init-functions.patchDownload
From 8d5156d809255da48f651b989eaf4a78fa13c037 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Wed, 30 Aug 2017 12:41:15 +1000
Subject: [PATCH 02/14] 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 5530f4bdeb..995b8629ca 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 74b094a9c3..f0c4e50b8c 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 */
 	T_CallContext				/* in nodes/parsenodes.h */
-- 
2.16.1.windows.4

0003-Adding-tableam-hanlder-to-relation-structure.patchapplication/octet-stream; name=0003-Adding-tableam-hanlder-to-relation-structure.patchDownload
From 32d94bfcc3c8ddec89ee85d799e4d0a28a1cbb41 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Wed, 30 Aug 2017 12:49:46 +1000
Subject: [PATCH 03/14] Adding tableam hanlder to relation structure

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

diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 1ebf9c4ed2..9281860ff1 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"
@@ -1335,10 +1336,28 @@ 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:
+		case RELKIND_PARTITIONED_INDEX:
+			Assert(relation->rd_rel->relam != 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);
@@ -1836,6 +1855,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
@@ -1994,6 +2078,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
 	 */
@@ -2324,6 +2413,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)
@@ -3340,6 +3431,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.
 	 *
@@ -3861,6 +3960,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);
 
@@ -5584,6 +5695,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.16.1.windows.4

0004-Adding-tuple-visibility-functions-to-table-AM.patchapplication/octet-stream; name=0004-Adding-tuple-visibility-functions-to-table-AM.patchDownload
From 5eb5e9e179ce5bbabcd8e224926ad72a49b2f62f Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Tue, 9 Jan 2018 16:01:32 +1100
Subject: [PATCH 04/14] 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/access/nbtree/nbtsort.c                |   2 +-
 src/backend/catalog/index.c                        |   7 +-
 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 +----
 29 files changed, 329 insertions(+), 220 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 94e051d642..b0ed27e883 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 8a846e7dba..bf1b24b2ca 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;
@@ -1854,7 +1873,7 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 
 	pgstat_count_heap_getnext(scan->rs_rd);
 
-	return &(scan->rs_ctup);
+	return heap_copytuple(&(scan->rs_ctup));
 }
 
 /*
@@ -1973,7 +1992,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);
@@ -2120,7 +2139,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 */
@@ -2294,7 +2313,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;
@@ -3120,7 +3139,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)
 	{
@@ -3231,7 +3250,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;
 	}
 
@@ -3691,7 +3710,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);
@@ -3872,7 +3891,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;
 	}
 
@@ -4623,7 +4642,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/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c
index 521ae6e5f7..fd517de809 100644
--- a/src/backend/access/nbtree/nbtsort.c
+++ b/src/backend/access/nbtree/nbtsort.c
@@ -80,7 +80,7 @@
 #include "utils/rel.h"
 #include "utils/sortsupport.h"
 #include "utils/tuplesort.h"
-
+#include "utils/tqual.h"
 
 /* Magic numbers for parallel state sharing */
 #define PARALLEL_KEY_BTREE_SHARED		UINT64CONST(0xA000000000000001)
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 564f2069cf..93d16cd108 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2460,6 +2460,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	TransactionId OldestXmin;
 	BlockNumber root_blkno = InvalidBlockNumber;
 	OffsetNumber root_offsets[MaxHeapTuplesPerPage];
+	TableAmRoutine *method;
 
 	/*
 	 * sanity checks
@@ -2553,6 +2554,8 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	Assert(snapshot == SnapshotAny ? TransactionIdIsValid(OldestXmin) :
 		   !TransactionIdIsValid(OldestXmin));
 	Assert(snapshot == SnapshotAny || !anyvisible);
+	
+	method = heapRelation->rd_tableamroutine;
 
 	/* set our scan endpoints */
 	if (!allow_sync)
@@ -2627,8 +2630,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 5d481dd50d..d6f9efc87a 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"
@@ -986,7 +987,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 3e1c9e0714..bdb82db149 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -459,7 +459,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 93c03cfb07..8a48a26643 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -195,6 +195,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
  */
 static void
 ExecCheckHeapTupleVisible(EState *estate,
+						  Relation rel,
 						  HeapTuple tuple,
 						  Buffer buffer)
 {
@@ -206,7 +207,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
@@ -241,7 +242,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);
 }
 
@@ -1500,7 +1501,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 872d6e5735..9d7872b439 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -566,7 +566,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 4123cdebcf..250aa92e80 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.16.1.windows.4

0005-slot-hooks-are-added-to-table-AM.patchapplication/octet-stream; name=0005-slot-hooks-are-added-to-table-AM.patchDownload
From 84f43452810a8f91fa1f07b2fc3ad8300768e16f Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Tue, 9 Jan 2018 16:17:54 +1100
Subject: [PATCH 05/14] 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.
---
 contrib/postgres_fdw/postgres_fdw.c       |   2 +-
 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         | 266 +++++++++---------
 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 ++--
 20 files changed, 719 insertions(+), 575 deletions(-)
 create mode 100644 src/backend/access/table/tableam_common.c

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 941a2e75a5..30e14ecd52 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -3753,7 +3753,7 @@ apply_returning_filter(PgFdwDirectModifyState *dmstate,
 	 */
 	if (dmstate->hasSystemCols)
 	{
-		HeapTuple	resultTup = ExecMaterializeSlot(resultSlot);
+		HeapTuple	resultTup = ExecHeapifySlot(resultSlot);
 
 		/* ctid */
 		if (dmstate->ctidAttno)
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 d5883c98d1..1765b5294d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2672,7 +2672,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 fffc0095a7..f78ae2c895 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;
 	}
 
@@ -2884,7 +2884,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;
@@ -4013,14 +4013,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 771b7e3945..cd006e3cd3 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -494,13 +494,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();
@@ -512,13 +514,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();
@@ -530,13 +533,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 c46d65cf93..93754420d0 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"
@@ -131,15 +132,16 @@ MakeTupleTableSlot(TupleDesc tupleDesc)
 	slot->tts_isempty = true;
 	slot->tts_shouldFree = false;
 	slot->tts_shouldFreeMin = false;
-	slot->tts_tuple = NULL;
 	slot->tts_fixedTupleDescriptor = tupleDesc != NULL;
 	slot->tts_tupleDescriptor = tupleDesc;
 	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;
 
 	if (tupleDesc != NULL)
 	{
@@ -236,6 +238,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
  *
@@ -353,7 +403,7 @@ ExecSetSlotDescriptor(TupleTableSlot *slot, /* slot to change */
  * --------------------------------
  */
 TupleTableSlot *
-ExecStoreTuple(HeapTuple tuple,
+ExecStoreTuple(void *tuple,
 			   TupleTableSlot *slot,
 			   Buffer buffer,
 			   bool shouldFree)
@@ -364,47 +414,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;
 }
 
@@ -431,31 +461,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;
@@ -480,25 +498,9 @@ ExecClearTuple(TupleTableSlot *slot)	/* slot in which to store tuple */
 	Assert(slot != NULL);
 
 	/*
-	 * Free the old physical tuple if necessary.
-	 */
-	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.
+	 * Tell the table AM to release any resource associated with the slot.
 	 */
-	if (BufferIsValid(slot->tts_buffer))
-		ReleaseBuffer(slot->tts_buffer);
-
-	slot->tts_buffer = InvalidBuffer;
+	slot->tts_slottableam->slot_clear_tuple(slot);
 
 	/*
 	 * Mark it empty.
@@ -577,7 +579,7 @@ ExecStoreAllNullTuple(TupleTableSlot *slot)
  *		however the "system columns" of the result will not be meaningful.
  * --------------------------------
  */
-HeapTuple
+TableTuple
 ExecCopySlotTuple(TupleTableSlot *slot)
 {
 	/*
@@ -586,20 +588,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);
 }
 
 /* --------------------------------
@@ -618,21 +607,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);
 }
 
 /* --------------------------------
@@ -650,25 +637,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;
 }
 
 /* --------------------------------
@@ -688,6 +684,7 @@ MinimalTuple
 ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 {
 	MemoryContext oldContext;
+	MinimalTuple tup;
 
 	/*
 	 * sanity checks
@@ -695,11 +692,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.
@@ -709,18 +703,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;
 }
 
 /* --------------------------------
@@ -749,18 +736,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
@@ -768,12 +756,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.
@@ -783,18 +767,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
@@ -804,17 +780,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 0084234b35..22716070f3 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 8a48a26643..2fe76b0fdc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -176,7 +176,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;
@@ -277,7 +277,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
@@ -404,7 +404,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -417,7 +417,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		newId = InvalidOid;
 	}
@@ -435,7 +435,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
@@ -784,7 +784,7 @@ ExecDelete(ModifyTableState *mtstate,
 		 */
 		if (slot->tts_isempty)
 			ExecStoreAllNullTuple(slot);
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
 	}
 	else
@@ -963,7 +963,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))
@@ -1025,7 +1025,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
@@ -1044,7 +1044,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW UPDATE Triggers */
@@ -1058,7 +1058,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
@@ -1074,7 +1074,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
@@ -1311,7 +1311,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 04985c9f91..ecd125e47d 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 8be0d5edc2..d4741790fb 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,25 +126,22 @@ 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 */
 	bool		tts_fixedTupleDescriptor; /* descriptor can't be changed */
+	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?
  */
@@ -144,9 +153,10 @@ extern TupleTableSlot *MakeTupleTableSlot(TupleDesc desc);
 extern TupleTableSlot *ExecAllocTableSlot(List **tupleTable, TupleDesc desc);
 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);
@@ -156,12 +166,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.16.1.windows.4

0006-Tuple-Insert-API-is-added-to-table-AM.patchapplication/octet-stream; name=0006-Tuple-Insert-API-is-added-to-table-AM.patchDownload
From 60140c0b6cca88edf65d392b6fe416f17f174ad5 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Tue, 9 Jan 2018 16:40:34 +1100
Subject: [PATCH 06/14] 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      | 239 +++++++++++------------
 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, 966 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 bf1b24b2ca..5fdeb7a29b 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -1916,13 +1916,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;
@@ -1956,7 +1956,6 @@ heap_fetch(Relation relation,
 			ReleaseBuffer(buffer);
 			*userbuf = InvalidBuffer;
 		}
-		tuple->t_data = NULL;
 		return false;
 	}
 
@@ -1978,13 +1977,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);
@@ -2335,7 +2334,6 @@ heap_get_latest_tid(Relation relation,
 	}							/* end of loop */
 }
 
-
 /*
  * UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends
  *
@@ -4642,7 +4640,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)
 	{
@@ -4935,7 +4933,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;
@@ -5706,9 +5704,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
@@ -6055,14 +6052,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);
@@ -6117,6 +6118,7 @@ heap_finish_speculative(Relation relation, HeapTuple tuple)
 	END_CRIT_SECTION();
 
 	UnlockReleaseBuffer(buffer);
+	slot->tts_speculativeToken = 0;
 }
 
 /*
@@ -6146,8 +6148,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;
@@ -6156,6 +6160,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);
@@ -6269,6 +6277,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 1765b5294d..9bcfee32a1 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"
@@ -2671,8 +2672,6 @@ CopyFrom(CopyState cstate)
 
 			if (slot == NULL)	/* "do nothing" */
 				skip_tuple = true;
-			else				/* trigger might have changed tuple */
-				tuple = ExecHeapifySlot(slot);
 		}
 
 		if (!skip_tuple)
@@ -2735,19 +2734,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);
@@ -2817,7 +2808,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;
 }
@@ -2850,12 +2841,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);
 
 	/*
@@ -2871,10 +2862,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);
 		}
@@ -2891,8 +2881,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 74e020bffc..30d55c00ae 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"
@@ -4730,7 +4731,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);
 
@@ -4754,7 +4756,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 f78ae2c895..2d787536de 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 *
@@ -3017,9 +3022,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)
 	{
@@ -3035,11 +3041,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:
@@ -3071,7 +3077,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;
@@ -3117,6 +3124,7 @@ ltrmark:;
 	{
 		Page		page;
 		ItemId		lp;
+		HeapTupleData tupledata;
 
 		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 
@@ -3135,17 +3143,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;
 }
 
@@ -3953,8 +3961,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;
@@ -4027,10 +4035,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
@@ -4044,10 +4051,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 91ba939bdc..3a1a9f6a4a 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"
@@ -1911,7 +1912,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 	 */
 	if (resultRelInfo->ri_PartitionRoot)
 	{
-		HeapTuple	tuple = ExecFetchSlotTuple(slot);
+		TableTuple	tuple = ExecFetchSlotTuple(slot);
 		TupleDesc	old_tupdesc = RelationGetDescr(rel);
 		TupleConversionMap *map;
 
@@ -1991,7 +1992,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				 */
 				if (resultRelInfo->ri_PartitionRoot)
 				{
-					HeapTuple	tuple = ExecFetchSlotTuple(slot);
+					TableTuple tuple = ExecFetchSlotTuple(slot);
 					TupleConversionMap *map;
 
 					rel = resultRelInfo->ri_PartitionRoot;
@@ -2038,7 +2039,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;
 
@@ -2498,7 +2499,8 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
 			 ItemPointer tid, TransactionId priorXmax)
 {
 	TupleTableSlot *slot;
-	HeapTuple	copyTuple;
+	TableTuple copyTuple;
+	tuple_data	t_data;
 
 	Assert(rti > 0);
 
@@ -2515,7 +2517,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.
@@ -2546,7 +2550,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
@@ -2579,14 +2583,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
@@ -2594,12 +2598,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;
@@ -2613,7 +2617,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);
@@ -2635,7 +2639,8 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 				{
 					case LockWaitBlock:
 						XactLockTableWait(SnapshotDirty.xmax,
-										  relation, &tuple.t_self,
+										  relation,
+										  tid,
 										  XLTW_FetchUpdated);
 						break;
 					case LockWaitSkip:
@@ -2664,20 +2669,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);
 
@@ -2713,12 +2721,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;
@@ -2740,10 +2751,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;
 		}
@@ -2752,7 +2759,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;
@@ -2761,7 +2768,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);
@@ -2780,7 +2787,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);
@@ -2788,17 +2797,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;
 }
 
 /*
@@ -2844,7 +2855,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;
 
@@ -2863,7 +2874,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;
@@ -2891,7 +2902,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");
@@ -2922,8 +2933,6 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 
 		if (erm->markType == ROW_MARK_REFERENCE)
 		{
-			HeapTuple	copyTuple;
-
 			Assert(erm->relation != NULL);
 
 			/* fetch the tuple's ctid */
@@ -2947,11 +2956,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");
 
 				/*
@@ -2965,23 +2974,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 */
@@ -2991,19 +2995,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);
+
 		}
 	}
 }
@@ -3172,8 +3169,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 b39ccf7dc1..f258848991 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);
 		}
 
@@ -395,8 +400,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 2fe76b0fdc..aa877fb273 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"
@@ -169,15 +171,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;
 
@@ -196,7 +196,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
 static void
 ExecCheckHeapTupleVisible(EState *estate,
 						  Relation rel,
-						  HeapTuple tuple,
+						  TableTuple tuple,
 						  Buffer buffer)
 {
 	if (!IsolationUsesXactSnapshot())
@@ -209,13 +209,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")));
@@ -231,19 +233,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);
 }
 
 /* ----------------------------------------------------------------
@@ -264,7 +267,7 @@ ExecInsert(ModifyTableState *mtstate,
 		   EState *estate,
 		   bool canSetTag)
 {
-	HeapTuple	tuple;
+	TableTuple tuple;
 	ResultRelInfo *resultRelInfo;
 	ResultRelInfo *saved_resultRelInfo = NULL;
 	Relation	resultRelationDesc;
@@ -273,12 +276,6 @@ ExecInsert(ModifyTableState *mtstate,
 	TupleTableSlot *result = NULL;
 	TransitionCaptureState *ar_insert_trig_tcs;
 
-	/*
-	 * 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
 	 */
@@ -290,6 +287,8 @@ ExecInsert(ModifyTableState *mtstate,
 		int			leaf_part_index;
 		PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
 
+		tuple = ExecHeapifySlot(slot);
+
 		/*
 		 * Away we go ... If we end up not finding a partition after all,
 		 * ExecFindPartition() does not return and errors out instead.
@@ -371,20 +370,26 @@ ExecInsert(ModifyTableState *mtstate,
 
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
+
+	ExecMaterializeSlot(slot);
+
 	/*
-	 * 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.
+	 * 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.
@@ -402,9 +407,6 @@ ExecInsert(ModifyTableState *mtstate,
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
-
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -416,9 +418,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)
@@ -434,14 +433,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;
 	}
@@ -463,7 +460,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 WITH CHECK policies.
@@ -502,7 +500,6 @@ ExecInsert(ModifyTableState *mtstate,
 			/* Perform a speculative insertion. */
 			uint32		specToken;
 			ItemPointerData conflictTid;
-			bool		specConflict;
 
 			/*
 			 * Do a non-conclusive check for conflicts first.
@@ -517,7 +514,7 @@ ExecInsert(ModifyTableState *mtstate,
 			 * speculatively.
 			 */
 	vlock:
-			specConflict = false;
+			slot->tts_specConflict = false;
 			if (!ExecCheckIndexConstraints(slot, estate, &conflictTid,
 										   arbiterIndexes))
 			{
@@ -563,24 +560,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
@@ -596,7 +586,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;
@@ -608,19 +598,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);
 		}
 	}
 
@@ -628,7 +613,7 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		(estate->es_processed)++;
 		estate->es_lastoid = newId;
-		setLastTid(&(tuple->t_self));
+		setLastTid(&(slot->tts_tid));
 	}
 
 	/*
@@ -641,6 +626,7 @@ ExecInsert(ModifyTableState *mtstate,
 	if (mtstate->operation == CMD_UPDATE && mtstate->mt_transition_capture
 		&& mtstate->mt_transition_capture->tcs_update_new_table)
 	{
+		tuple = ExecHeapifySlot(slot);
 		ExecARUpdateTriggers(estate, resultRelInfo, NULL,
 							 NULL,
 							 tuple,
@@ -655,7 +641,7 @@ ExecInsert(ModifyTableState *mtstate,
 	}
 
 	/* AFTER ROW INSERT Triggers */
-	ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes,
+	ExecARInsertTriggers(estate, resultRelInfo, slot, recheckIndexes,
 						 ar_insert_trig_tcs);
 
 	list_free(recheckIndexes);
@@ -706,7 +692,7 @@ ExecInsert(ModifyTableState *mtstate,
 static TupleTableSlot *
 ExecDelete(ModifyTableState *mtstate,
 		   ItemPointer tupleid,
-		   HeapTuple oldtuple,
+		   TableTuple oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate,
@@ -757,8 +743,6 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
-		HeapTuple	tuple;
-
 		/*
 		 * delete from foreign table: let the FDW do it
 		 *
@@ -784,8 +768,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
 	{
@@ -799,11 +785,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:
@@ -927,7 +914,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)
@@ -941,20 +928,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);
@@ -963,7 +949,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))
@@ -1000,14 +986,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;
@@ -1073,14 +1059,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
 	{
@@ -1091,7 +1077,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
@@ -1247,11 +1233,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:
@@ -1322,26 +1311,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)
@@ -1399,11 +1368,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);
@@ -1414,10 +1384,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:
@@ -1442,7 +1410,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"),
@@ -1473,7 +1442,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:
@@ -1501,10 +1472,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
@@ -1519,7 +1490,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 */
 	}
@@ -1559,12 +1532,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;
 }
 
@@ -1850,7 +1825,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 e207b1ffb5..3d4e8d0093 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 41d540b46e..bb8a683b44 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 45a077a949..fd706f8fee 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -195,16 +195,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);
@@ -541,9 +541,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 d4741790fb..733ebe25a7 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 a953820f43..1758396261 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -520,7 +520,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 */
 
@@ -2037,7 +2037,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
@@ -2047,7 +2047,7 @@ typedef struct SharedHashInfo
 {
 	int			num_workers;
 	HashInstrumentation hinstrument[FLEXIBLE_ARRAY_MEMBER];
-} SharedHashInfo;
+}			SharedHashInfo;
 
 /* ----------------
  *	 HashState information
@@ -2108,7 +2108,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.16.1.windows.4

0007-Scan-functions-are-added-to-table-AM.patchapplication/octet-stream; name=0007-Scan-functions-are-added-to-table-AM.patchDownload
From b6517f3503beeba1cac8a72ab74b3f4b91fff949 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Tue, 9 Jan 2018 16:59:20 +1100
Subject: [PATCH 07/14] 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           | 223 ++++++++++-----------------
 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         | 233 +++++++++++++++++++++++++++++
 src/backend/bootstrap/bootstrap.c          |  25 ++--
 src/backend/catalog/aclchk.c               |  13 +-
 src/backend/catalog/index.c                |  49 +++---
 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, 716 insertions(+), 451 deletions(-)

diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index b0ed27e883..6d47a446ea 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -125,7 +125,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
 			aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
 						   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 5fdeb7a29b..89bf44134c 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,
@@ -1390,87 +1379,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;
 
@@ -1540,9 +1458,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
 	 */
@@ -1553,27 +1478,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);
+	}
 }
 
 /* ----------------
@@ -1671,36 +1590,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);
-
-	if (!parallel_scan->phs_snapshot_any)
-	{
-		/* Snapshot was serialized -- restore it */
-		snapshot = RestoreSnapshot(parallel_scan->phs_snapshot_data);
-		RegisterSnapshot(snapshot);
-	}
-	else
-	{
-		/* SnapshotAny passed by caller (not serialized) */
-		snapshot = SnapshotAny;
-	}
-
-	return heap_beginscan_internal(relation, snapshot, 0, NULL, parallel_scan,
-								   true, true, true, false, false,
-								   !parallel_scan->phs_snapshot_any);
-}
-
 /* ----------------
  *		heap_parallelscan_startblock_init - find and set the scan's startblock
  *
@@ -1845,8 +1734,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 */
@@ -1876,6 +1764,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 91247f0fa5..2f737a30ed 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"
@@ -606,12 +607,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..142d5a18f9 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,184 @@ 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);
+
+	if (!parallel_scan->phs_snapshot_any)
+	{
+		/* Snapshot was serialized -- restore it */
+		snapshot = RestoreSnapshot(parallel_scan->phs_snapshot_data);
+		RegisterSnapshot(snapshot);
+	}
+	else
+	{
+		/* SnapshotAny passed by caller (not serialized) */
+		snapshot = SnapshotAny;
+	}
+
+	return relation->rd_tableamroutine->scan_begin(relation, snapshot, 0, NULL, parallel_scan,
+												true, true, true, false, false, !parallel_scan->phs_snapshot_any);
+}
+
+/*
+ * 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 +267,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 28ff2f0979..7fdcd31f47 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 1156627b9e..6bb0e44760 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(ObjectType 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 93d16cd108..088df73a9b 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"
@@ -2114,10 +2115,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
 	{
@@ -2523,7 +2524,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		else
 			snapshot = SnapshotAny;
 
-		scan = heap_beginscan_strat(heapRelation,	/* relation */
+		scan = table_beginscan_strat(heapRelation,	/* relation */
 									snapshot,	/* snapshot */
 									0,	/* number of keys */
 									NULL,	/* scan key */
@@ -2559,7 +2560,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 
 	/* 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 */
@@ -2572,7 +2573,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;
 
@@ -2884,7 +2885,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		}
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 
 	/* we can now forget our snapshot, if set and registered by us */
 	if (need_unregister_snapshot)
@@ -2955,14 +2956,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();
 
@@ -2998,7 +2999,7 @@ IndexCheckExclusion(Relation heapRelation,
 								   estate, true);
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	UnregisterSnapshot(snapshot);
 
 	ExecDropSingleTupleTableSlot(slot);
@@ -3275,17 +3276,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;
@@ -3442,7 +3443,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 b1c7cd6c72..6049ced410 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"
@@ -1334,7 +1335,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);
 
 		/*
@@ -1343,7 +1344,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;
@@ -1359,7 +1360,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 d6f9efc87a..8cab9208f2 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -928,7 +928,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;
 	}
 
@@ -978,7 +978,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;
 
@@ -1064,7 +1064,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
@@ -1701,8 +1701,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);
 
@@ -1722,7 +1722,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 9bcfee32a1..b5bb03e496 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2033,10 +2033,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();
 
@@ -2048,7 +2048,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 d1718f04ee..78a103db0c 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 3e48a58dcb..fd8dc1a82c 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"
@@ -2282,8 +2283,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);
@@ -2329,7 +2330,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 30d55c00ae..fcf5cbd48d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4618,7 +4618,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
@@ -4626,7 +4626,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)
 			{
@@ -4740,7 +4740,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 
 		MemoryContextSwitchTo(oldCxt);
-		heap_endscan(scan);
+		table_endscan(scan);
 		UnregisterSnapshot(snapshot);
 
 		ExecDropSingleTupleTableSlot(oldslot);
@@ -5146,9 +5146,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,
@@ -5160,7 +5160,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;
@@ -8301,7 +8301,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
@@ -8309,7 +8309,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);
 
@@ -8324,7 +8324,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	}
 
 	MemoryContextSwitchTo(oldcxt);
-	heap_endscan(scan);
+	table_endscan(scan);
 	UnregisterSnapshot(snapshot);
 	ExecDropSingleTupleTableSlot(slot);
 	FreeExecutorState(estate);
@@ -8379,9 +8379,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;
@@ -8410,7 +8410,7 @@ validateForeignKeyConstraint(char *conname,
 		RI_FKey_check_ins(&fcinfo);
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	UnregisterSnapshot(snapshot);
 }
 
@@ -10931,8 +10931,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;
@@ -10992,7 +10992,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 5c450caa4e..b743dedf33 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 899a5c4cd4..123f53b8c3 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 93754420d0..1adfa59d1d 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -720,7 +720,7 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 Datum
 ExecFetchSlotTupleDatum(TupleTableSlot *slot)
 {
-	HeapTuple	tup;
+	TableTuple tup;
 	TupleDesc	tupdesc;
 
 	/* Fetch slot's contents in regular-physical-tuple form */
@@ -804,7 +804,7 @@ ExecHeapifySlot(TupleTableSlot *slot)
 TupleTableSlot *
 ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
 {
-	HeapTuple	newTuple;
+	TableTuple newTuple;
 	MemoryContext oldContext;
 
 	/*
@@ -1144,7 +1144,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;
@@ -1152,7 +1152,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 1b1334006f..bcb09bc00e 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -2517,7 +2517,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,
@@ -2642,7 +2642,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 bdb82db149..45c9baf6c8 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"
@@ -431,8 +432,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);
 		}
 	}
@@ -742,7 +743,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)
@@ -832,7 +833,7 @@ ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 	/*
 	 * close heap scan
 	 */
-	heap_endscan(scanDesc);
+	table_endscan(scanDesc);
 
 	/*
 	 * close the heap relation.
@@ -963,10 +964,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);
 
 	/*
 	 * all done.
@@ -1114,5 +1115,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 22716070f3..3500a490d1 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 eaf7d2d563..d6b5540a7a 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);
 
 
@@ -245,7 +245,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)
 	{
@@ -289,7 +289,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;
@@ -297,7 +297,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 83221cdbae..237498c3bc 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);
@@ -399,7 +399,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] =
@@ -616,7 +616,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
@@ -691,12 +691,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 ddc0ae9061..8aaf0123de 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 01c9de88f4..a62b916b00 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;
 
 	/*
@@ -184,7 +184,7 @@ IndexNextWithReorder(IndexScanState *node)
 	EState	   *estate;
 	ExprContext *econtext;
 	IndexScanDesc scandesc;
-	HeapTuple	tuple;
+	TableTuple tuple;
 	TupleTableSlot *slot;
 	ReorderTuple *topmost = NULL;
 	bool		was_exact;
@@ -478,7 +478,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;
@@ -511,10 +511,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 9d7872b439..0a55c3b6a8 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -28,10 +28,12 @@
 
 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
  * ----------------------------------------------------------------
@@ -46,7 +48,7 @@ static bool SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset,
 static TupleTableSlot *
 SampleNext(SampleScanState *node)
 {
-	HeapTuple	tuple;
+	TableTuple tuple;
 	TupleTableSlot *slot;
 
 	/*
@@ -222,7 +224,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.
@@ -327,19 +329,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);
@@ -354,7 +356,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;
@@ -532,7 +534,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 9db368922a..758dbeb9c7 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"
@@ -48,8 +49,7 @@ static TupleTableSlot *SeqNext(SeqScanState *node);
 static TupleTableSlot *
 SeqNext(SeqScanState *node)
 {
-	HeapTuple	tuple;
-	HeapScanDesc scandesc;
+	TableScanDesc scandesc;
 	EState	   *estate;
 	ScanDirection direction;
 	TupleTableSlot *slot;
@@ -68,35 +68,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);
 }
 
 /*
@@ -203,7 +184,7 @@ void
 ExecEndSeqScan(SeqScanState *node)
 {
 	Relation	relation;
-	HeapScanDesc scanDesc;
+	TableScanDesc scanDesc;
 
 	/*
 	 * get information from node
@@ -226,7 +207,7 @@ ExecEndSeqScan(SeqScanState *node)
 	 * close heap scan
 	 */
 	if (scanDesc != NULL)
-		heap_endscan(scanDesc);
+		table_endscan(scanDesc);
 
 	/*
 	 * close the heap relation.
@@ -248,13 +229,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);
 }
@@ -301,7 +282,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);
 }
 
 /* ----------------------------------------------------------------
@@ -333,5 +314,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 a56c3e89fd..384bf89f7e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -2588,7 +2588,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;
@@ -2671,7 +2671,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 9fc4431b80..850535ff4c 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -721,11 +721,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)
 	{
@@ -749,7 +749,7 @@ SPI_copytuple(HeapTuple tuple)
 }
 
 HeapTupleHeader
-SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc)
+SPI_returntuple(TableTuple tuple, TupleDesc tupdesc)
 {
 	MemoryContext oldcxt;
 	HeapTupleHeader dtup;
@@ -780,7 +780,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)
 {
@@ -948,7 +948,7 @@ char *
 SPI_gettype(TupleDesc tupdesc, int fnumber)
 {
 	Oid			typoid;
-	HeapTuple	typeTuple;
+	TableTuple typeTuple;
 	char	   *result;
 
 	SPI_result = 0;
@@ -1056,7 +1056,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);
@@ -1777,7 +1777,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);
@@ -1808,8 +1808,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 702f8d8188..a2787e366e 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1882,9 +1882,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;
@@ -1911,7 +1911,7 @@ get_database_list(void)
 		MemoryContextSwitchTo(oldcxt);
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	CommitTransactionCommand();
@@ -2042,13 +2042,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;
@@ -2134,7 +2134,7 @@ do_autovacuum(void)
 		}
 	}
 
-	heap_endscan(relScan);
+	table_endscan(relScan);
 
 	/* second pass: check TOAST tables */
 	ScanKeyInit(&key,
@@ -2142,8 +2142,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;
@@ -2189,7 +2189,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 96ba216387..be7350bdd2 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 f3a9b639a8..2dfced7b22 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 484628987f..0f2cae090d 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"
@@ -1218,10 +1219,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 e5bdaecc4e..e75ac2746e 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;
@@ -120,10 +120,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);
@@ -136,7 +136,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.16.1.windows.4

0008-Remove-HeapScanDesc-usage-outside-heap.patchapplication/octet-stream; name=0008-Remove-HeapScanDesc-usage-outside-heap.patchDownload
From bcc01ae945f7e6103ecaf51f32397e6843f84c34 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Sun, 4 Feb 2018 17:04:47 +1100
Subject: [PATCH 08/14] 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/nbtree/nbtsort.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                |  10 +-
 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/catalog/index.h                |   4 +-
 src/include/nodes/execnodes.h              |   4 +-
 42 files changed, 523 insertions(+), 404 deletions(-)

diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index 6d47a446ea..cba2e63f13 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 89bf44134c..6fedf96fd8 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;
@@ -1379,7 +1383,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,
@@ -1406,12 +1410,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;
@@ -1420,7 +1424,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
@@ -1444,13 +1448,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;
 }
 
 /* ----------------
@@ -1458,21 +1462,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
@@ -1503,29 +1509,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);
 }
@@ -1630,7 +1638,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;
 		}
 	}
@@ -1672,10 +1680,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.
@@ -1684,12 +1692,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;
@@ -1702,12 +1710,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;
 }
 
@@ -1735,17 +1745,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)
 	{
@@ -1759,7 +1771,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));
 }
@@ -1767,7 +1779,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 \
@@ -1779,7 +1791,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;
 
@@ -1787,11 +1799,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)
 	{
@@ -1806,7 +1818,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/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c
index fd517de809..271de13a79 100644
--- a/src/backend/access/nbtree/nbtsort.c
+++ b/src/backend/access/nbtree/nbtsort.c
@@ -1558,7 +1558,7 @@ _bt_parallel_scan_and_sort(BTSpool *btspool, BTSpool *btspool2,
 {
 	SortCoordinate coordinate;
 	BTBuildState buildstate;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	double		reltuples;
 	IndexInfo  *indexInfo;
 
@@ -1611,7 +1611,7 @@ _bt_parallel_scan_and_sort(BTSpool *btspool, BTSpool *btspool2,
 	/* Join parallel scan */
 	indexInfo = BuildIndexInfo(btspool->index);
 	indexInfo->ii_Concurrent = btshared->isconcurrent;
-	scan = heap_beginscan_parallel(btspool->heap, &btshared->heapdesc);
+	scan = table_beginscan_parallel(btspool->heap, &btshared->heapdesc);
 	reltuples = IndexBuildHeapScan(btspool->heap, btspool->index, indexInfo,
 								   true, _bt_build_callback,
 								   (void *) &buildstate, scan);
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index 142d5a18f9..4991f62e1e 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;
@@ -79,6 +79,25 @@ table_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan)
 												true, true, true, false, false, !parallel_scan->phs_snapshot_any);
 }
 
+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
  *
@@ -86,7 +105,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);
 }
@@ -105,18 +124,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)
 {
@@ -124,7 +143,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);
@@ -134,7 +153,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)
@@ -144,7 +163,7 @@ table_beginscan_strat(Relation relation, Snapshot snapshot,
 												false, false, false);
 }
 
-HeapScanDesc
+TableScanDesc
 table_beginscan_bm(Relation relation, Snapshot snapshot,
 					 int nkeys, ScanKey key)
 {
@@ -152,7 +171,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)
@@ -167,7 +186,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);
@@ -183,7 +202,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,
@@ -198,7 +217,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);
 }
@@ -211,23 +230,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 7fdcd31f47..c442ad88a9 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 6bb0e44760..e89396edb7 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -822,7 +822,7 @@ objectsInSchemaToOids(ObjectType 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 088df73a9b..c8b889c54e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2107,7 +2107,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],
@@ -2415,7 +2415,7 @@ IndexBuildHeapScan(Relation heapRelation,
 				   bool allow_sync,
 				   IndexBuildCallback callback,
 				   void *callback_state,
-				   HeapScanDesc scan)
+				   TableScanDesc scan)
 {
 	return IndexBuildHeapRangeScan(heapRelation, indexRelation,
 								   indexInfo, allow_sync,
@@ -2444,7 +2444,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 						BlockNumber numblocks,
 						IndexBuildCallback callback,
 						void *callback_state,
-						HeapScanDesc scan)
+						TableScanDesc scan)
 {
 	bool		is_system_catalog;
 	bool		checking_uniqueness;
@@ -2920,7 +2920,7 @@ IndexCheckExclusion(Relation heapRelation,
 					Relation indexRelation,
 					IndexInfo *indexInfo)
 {
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
@@ -3234,7 +3234,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 6049ced410..b9cf03c2e8 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1276,7 +1276,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 8cab9208f2..2b88acb02a 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -762,7 +762,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;
@@ -1682,7 +1682,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 b5bb03e496..bfbda3e493 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2027,7 +2027,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 78a103db0c..353402fc52 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 fd8dc1a82c..0d080ea684 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2209,7 +2209,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 fcf5cbd48d..37d03f26a5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4559,7 +4559,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;
@@ -5135,7 +5135,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;
 
@@ -8260,7 +8260,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	Expr	   *origexpr;
 	ExprState  *exprstate;
 	TupleDesc	tupdesc;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tuple;
 	ExprContext *econtext;
 	MemoryContext oldcxt;
@@ -8343,7 +8343,7 @@ validateForeignKeyConstraint(char *conname,
 							 Oid pkindOid,
 							 Oid constraintOid)
 {
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tuple;
 	Trigger		trig;
 	Snapshot	snapshot;
@@ -10866,7 +10866,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 b743dedf33..f9c045efc6 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 123f53b8c3..fea082f10f 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 45c9baf6c8..7e29030dfd 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);
@@ -381,8 +373,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;
@@ -391,7 +385,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,
@@ -434,7 +428,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
@@ -450,23 +444,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);
 		}
 	}
@@ -474,7 +466,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 
 	Assert(ntup <= MaxHeapTuplesPerPage);
-	scan->rs_ntuples = ntup;
+	pagescan->rs_ntuples = ntup;
 }
 
 /*
@@ -600,7 +592,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;
@@ -788,7 +780,7 @@ void
 ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 {
 	Relation	relation;
-	HeapScanDesc scanDesc;
+	TableScanDesc scanDesc;
 
 	/*
 	 * extract information from the node
@@ -969,6 +961,8 @@ ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags)
 															0,
 															NULL);
 
+	scanstate->pagescan = tableam_get_heappagescandesc(scanstate->ss.ss_currentScanDesc);
+
 	/*
 	 * all done.
 	 */
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index 0a55c3b6a8..be207765b6 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -30,9 +30,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
@@ -335,6 +334,7 @@ tablesample_init(SampleScanState *scanstate)
 									   scanstate->use_bulkread,
 									   allow_sync,
 									   scanstate->use_pagemode);
+		scanstate->pagescan = tableam_get_heappagescandesc(scanstate->ss.ss_currentScanDesc);
 	}
 	else
 	{
@@ -360,10 +360,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;
@@ -374,10 +375,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)
@@ -385,13 +385,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;
 	}
@@ -434,14 +433,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)
@@ -472,14 +469,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;
 
 			/*
@@ -492,10 +489,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);
 		}
 
 		/*
@@ -507,12 +504,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 */
@@ -527,16 +523,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,
@@ -548,12 +547,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 758dbeb9c7..b2b0a30343 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -295,9 +295,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 60f21711f4..224612d67a 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"
@@ -263,7 +264,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 a2787e366e..49c29218f7 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1864,7 +1864,7 @@ get_database_list(void)
 {
 	List	   *dblist = NIL;
 	Relation	rel;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tup;
 	MemoryContext resultcxt;
 
@@ -1930,7 +1930,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 be7350bdd2..074a3ce70d 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 2dfced7b22..8f70819dd6 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 0f2cae090d..72b7030257 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -1214,7 +1214,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 18c7dedd5d..3d29ea4ee4 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"
@@ -43,40 +44,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;
 
 /*
@@ -150,12 +165,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/catalog/index.h b/src/include/catalog/index.h
index f20c5f789b..6cab64df10 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -116,7 +116,7 @@ extern double IndexBuildHeapScan(Relation heapRelation,
 				   bool allow_sync,
 				   IndexBuildCallback callback,
 				   void *callback_state,
-				   HeapScanDesc scan);
+				   TableScanDesc scan);
 extern double IndexBuildHeapRangeScan(Relation heapRelation,
 						Relation indexRelation,
 						IndexInfo *indexInfo,
@@ -126,7 +126,7 @@ extern double IndexBuildHeapRangeScan(Relation heapRelation,
 						BlockNumber end_blockno,
 						IndexBuildCallback callback,
 						void *callback_state,
-						HeapScanDesc scan);
+						TableScanDesc scan);
 
 extern void validate_index(Oid heapId, Oid indexId, Snapshot snapshot);
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 1758396261..0e723210b3 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1123,7 +1123,7 @@ typedef struct ScanState
 {
 	PlanState	ps;				/* its first field is NodeTag */
 	Relation	ss_currentRelation;
-	HeapScanDesc ss_currentScanDesc;
+	TableScanDesc ss_currentScanDesc;
 	TupleTableSlot *ss_ScanTupleSlot;
 } ScanState;
 
@@ -1144,6 +1144,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 */
@@ -1372,6 +1373,7 @@ typedef struct ParallelBitmapHeapState
 typedef struct BitmapHeapScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
+	HeapPageScanDesc pagescan;
 	ExprState  *bitmapqualorig;
 	TIDBitmap  *tbm;
 	TBMIterator *tbmiterator;
-- 
2.16.1.windows.4

0009-BulkInsertState-is-added-into-table-AM.patchapplication/octet-stream; name=0009-BulkInsertState-is-added-into-table-AM.patchDownload
From 33ef817742235cecb34e735cdd16ccfc5915a67b Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Tue, 9 Jan 2018 18:04:50 +1100
Subject: [PATCH 09/14] 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 6fedf96fd8..3b5b9313a6 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2327,7 +2327,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 4991f62e1e..25db521dca 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -389,3 +389,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 bfbda3e493..802ac0ad5d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2292,7 +2292,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;
@@ -2517,7 +2517,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 */
@@ -2597,7 +2597,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;
 			}
 
@@ -2770,7 +2770,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 37d03f26a5..d3f4ba3c1c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4451,7 +4451,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	ListCell   *l;
 	EState	   *estate;
 	CommandId	mycid;
-	BulkInsertState bistate;
+	void       *bistate;
 	int			hi_options;
 	ExprState  *partqualstate = NULL;
 
@@ -4477,7 +4477,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())
@@ -4752,7 +4752,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.16.1.windows.4

0010-table-rewrite-functionality.patchapplication/octet-stream; name=0010-table-rewrite-functionality.patchDownload
From bd3bf642a098e4d1d288ac2746c2b622fb84cc14 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Tue, 9 Jan 2018 18:29:56 +1100
Subject: [PATCH 10/14] 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 25db521dca..f1ba658f17 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -412,3 +412,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 2b88acb02a..ae953d90ff 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);
 
 
 /*---------------------------------------------------------------------------
@@ -892,7 +891,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);
 
 	/*
@@ -1042,7 +1041,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;
@@ -1056,9 +1055,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)
@@ -1085,16 +1083,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;
@@ -1748,10 +1745,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;
 
@@ -1767,11 +1765,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.16.1.windows.4

0011-Improve-tuple-locking-interface.patchapplication/octet-stream; name=0011-Improve-tuple-locking-interface.patchDownload
From 839737aeddf7e20182ef5f0e46f80c4200d6b3e8 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Tue, 9 Jan 2018 22:14:20 +1100
Subject: [PATCH 11/14] 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 3b5b9313a6..45965a7dd6 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3203,6 +3203,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;
@@ -3216,6 +3217,8 @@ l1:
 			UnlockTupleTuplock(relation, &(tp.t_self), LockTupleExclusive);
 		if (vmbuffer != InvalidBuffer)
 			ReleaseBuffer(vmbuffer);
+		if (result == HeapTupleUpdated && ItemPointerEquals(tid, &hufd->ctid))
+			result = HeapTupleDeleted;
 		return result;
 	}
 
@@ -3425,6 +3428,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;
@@ -3844,6 +3851,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;
@@ -3862,6 +3870,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;
 	}
 
@@ -4480,6 +4490,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;
@@ -4532,6 +4546,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
@@ -4600,7 +4615,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;
@@ -4880,7 +4895,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;
@@ -5040,6 +5055,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;
 	}
@@ -5048,7 +5065,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);
@@ -5927,6 +5944,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 f1ba658f17..6e6059e9ed 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 2d787536de..2322040919 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3024,8 +3024,6 @@ GetTupleForTrigger(EState *estate,
 	Relation	relation = relinfo->ri_RelationDesc;
 	TableTuple tuple;
 	HeapTuple	result;
-	Buffer		buffer;
-	tuple_data	t_data;
 
 	if (newSlot != NULL)
 	{
@@ -3040,11 +3038,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)
 		{
@@ -3065,63 +3063,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;
@@ -3151,9 +3141,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 3a1a9f6a4a..265bf8c3f3 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -2480,9 +2480,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)
@@ -2495,32 +2493,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.
 	 */
@@ -2530,7 +2508,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
@@ -2562,256 +2540,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 f258848991..f5417c1db0 100644
--- a/src/backend/executor/nodeLockRows.c
+++ b/src/backend/executor/nodeLockRows.c
@@ -79,13 +79,11 @@ lnext:
 		Datum		datum;
 		bool		isNull;
 		TableTuple tuple;
-		Buffer		buffer;
 		HeapUpdateFailureData hufd;
 		LockTupleMode lockmode;
 		HTSU_Result test;
 		TableTuple copyTuple;
 		ItemPointerData tid;
-		tuple_data	t_data;
 
 		/* clear any leftover test tuple for this rel */
 		testTuple = (TableTuple) (&(node->lr_curtuples[erm->rti - 1]));
@@ -183,12 +181,12 @@ lnext:
 				break;
 		}
 
-		test = table_lock_tuple(erm->relation, &tid, &tuple,
-								  estate->es_output_cid,
-								  lockmode, erm->waitPolicy, true,
-								  &buffer, &hufd);
-		if (BufferIsValid(buffer))
-			ReleaseBuffer(buffer);
+		test = table_lock_tuple(erm->relation, &tid, estate->es_snapshot,
+								  &tuple, estate->es_output_cid,
+								  lockmode, erm->waitPolicy,
+								  (IsolationUsesXactSnapshot() ? 0 : TUPLE_LOCK_FLAG_FIND_LAST_VERSION)
+								  | TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS,
+								  &hufd);
 
 		switch (test)
 		{
@@ -216,6 +214,16 @@ lnext:
 
 			case HeapTupleMayBeUpdated:
 				/* got the lock successfully */
+				if (hufd.traversed)
+				{
+					/* Save locked tuple for EvalPlanQual testing below */
+					*testTuple = tuple;
+
+					/* Remember we need to do EPQ testing */
+					epq_needed = true;
+
+					/* Continue loop until we have all target tuples */
+				}
 				break;
 
 			case HeapTupleUpdated:
@@ -223,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 aa877fb273..b6b4b153b0 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -206,7 +206,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);
@@ -222,7 +223,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);
 }
 
 /*
@@ -706,6 +708,7 @@ ExecDelete(ModifyTableState *mtstate,
 	HeapUpdateFailureData hufd;
 	TupleTableSlot *slot = NULL;
 	TransitionCaptureState *ar_delete_trig_tcs;
+	TableTuple	tuple;
 
 	if (tupleDeleted)
 		*tupleDeleted = false;
@@ -791,6 +794,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:
@@ -836,23 +868,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;
 
@@ -1241,6 +1266,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:
@@ -1285,25 +1341,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;
 
@@ -1372,8 +1419,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);
@@ -1384,8 +1431,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:
@@ -1442,8 +1493,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;
 
@@ -1472,10 +1530,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
@@ -1490,8 +1548,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 */
@@ -1537,8 +1593,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 fd706f8fee..61e3b0bf42 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -193,11 +193,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.16.1.windows.4

0012-Table-AM-shared-memory-API.patchapplication/octet-stream; name=0012-Table-AM-shared-memory-API.patchDownload
From 2460019e66cc9920fae48d94de2b89c61cb49680 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Tue, 9 Jan 2018 22:33:01 +1100
Subject: [PATCH 12/14] 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 6e6059e9ed..96df355b1a 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
  */
@@ -99,6 +100,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 be207765b6..e92f5cfe2c 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -490,7 +490,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.16.1.windows.4

0013-Using-access-method-syntax-addition-to-create-table.patchapplication/octet-stream; name=0013-Using-access-method-syntax-addition-to-create-table.patchDownload
From a26fcd6cf1edfc91bd8d798ecc322abb683e883a Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Wed, 7 Feb 2018 11:59:55 +1100
Subject: [PATCH 13/14] Using "access method" syntax addition to create table

With the pluggable storage support, user can select the
table access method that he needs for the corresponding table.
The syntax addition is similar like CREATE INDEX.

CREATE TABLE ... [USING ACCESSMETHOD] ...

All the catalog relations are by default uses the HEAP method.
Currently the access method syntax support is added only for
main relations.

Currently Default access method HEAP is used for TOAST, VIEW,
SEQUENCE AND MATERIALIZED VIEWS.

Pending items: support of displaying access method with \d commands
---
 src/backend/bootstrap/bootparse.y         |  2 +
 src/backend/catalog/heap.c                |  4 ++
 src/backend/catalog/index.c               |  2 +-
 src/backend/catalog/toasting.c            |  1 +
 src/backend/commands/cluster.c            |  1 +
 src/backend/commands/tablecmds.c          | 28 ++++++++++++
 src/backend/nodes/copyfuncs.c             |  1 +
 src/backend/parser/gram.y                 | 71 ++++++++++++++++++-------------
 src/backend/utils/cache/relcache.c        | 12 +++---
 src/include/catalog/heap.h                |  2 +
 src/include/catalog/pg_am.h               |  1 +
 src/include/nodes/parsenodes.h            |  1 +
 src/include/utils/relcache.h              |  1 +
 src/test/regress/expected/type_sanity.out | 16 ++++---
 src/test/regress/sql/type_sanity.sql      |  6 +--
 15 files changed, 105 insertions(+), 44 deletions(-)

diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 9e81f9514d..9a4e895da8 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -225,6 +225,7 @@ Boot_CreateStmt:
 												   shared_relation ? GLOBALTABLESPACE_OID : 0,
 												   $3,
 												   InvalidOid,
+												   HEAP_TABLE_AM_OID,
 												   tupdesc,
 												   RELKIND_RELATION,
 												   RELPERSISTENCE_PERMANENT,
@@ -244,6 +245,7 @@ Boot_CreateStmt:
 													  $7,
 													  InvalidOid,
 													  BOOTSTRAP_SUPERUSERID,
+													  HEAP_TABLE_AM_OID,
 													  tupdesc,
 													  NIL,
 													  RELKIND_RELATION,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index cf36ce4add..d9227effc8 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -252,6 +252,7 @@ heap_create(const char *relname,
 			Oid reltablespace,
 			Oid relid,
 			Oid relfilenode,
+			Oid accessmtd,
 			TupleDesc tupDesc,
 			char relkind,
 			char relpersistence,
@@ -346,6 +347,7 @@ heap_create(const char *relname,
 									 relnamespace,
 									 tupDesc,
 									 relid,
+									 accessmtd,
 									 relfilenode,
 									 reltablespace,
 									 shared_relation,
@@ -1026,6 +1028,7 @@ heap_create_with_catalog(const char *relname,
 						 Oid reltypeid,
 						 Oid reloftypeid,
 						 Oid ownerid,
+						 Oid accessmtd,
 						 TupleDesc tupdesc,
 						 List *cooked_constraints,
 						 char relkind,
@@ -1168,6 +1171,7 @@ heap_create_with_catalog(const char *relname,
 							   reltablespace,
 							   relid,
 							   InvalidOid,
+							   accessmtd,
 							   tupdesc,
 							   relkind,
 							   relpersistence,
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index c8b889c54e..74ff6541c8 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -899,6 +899,7 @@ index_create(Relation heapRelation,
 								tableSpaceId,
 								indexRelationId,
 								relFileNode,
+								accessMethodObjectId,
 								indexTupDesc,
 								relkind,
 								relpersistence,
@@ -922,7 +923,6 @@ index_create(Relation heapRelation,
 	 * XXX should have a cleaner way to create cataloged indexes
 	 */
 	indexRelation->rd_rel->relowner = heapRelation->rd_rel->relowner;
-	indexRelation->rd_rel->relam = accessMethodObjectId;
 	indexRelation->rd_rel->relhasoids = false;
 
 	/*
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 8bf2698545..6e1affd75a 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -266,6 +266,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 										   toast_typid,
 										   InvalidOid,
 										   rel->rd_rel->relowner,
+										   rel->rd_rel->relam,
 										   tupdesc,
 										   NIL,
 										   RELKIND_TOASTVALUE,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index ae953d90ff..65c7fef6ef 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -679,6 +679,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
 										  InvalidOid,
 										  InvalidOid,
 										  OldHeap->rd_rel->relowner,
+										  OldHeap->rd_rel->relam,
 										  OldHeapDesc,
 										  NIL,
 										  RELKIND_RELATION,
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d3f4ba3c1c..b237a26b88 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -536,6 +536,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
 	Oid			ofTypeId;
 	ObjectAddress address;
+	Oid			accessMethodId = InvalidOid;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -739,6 +740,32 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			attr->attidentity = colDef->identity;
 	}
 
+	/*
+	 * look up the access method, verify it can handle the requested features
+	 */
+	if (stmt->accessMethod != NULL)
+	{
+		HeapTuple	tuple;
+
+		tuple = SearchSysCache1(AMNAME, PointerGetDatum(stmt->accessMethod));
+		if (!HeapTupleIsValid(tuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("table access method \"%s\" does not exist",
+								 stmt->accessMethod)));
+		accessMethodId = HeapTupleGetOid(tuple);
+		ReleaseSysCache(tuple);
+	}
+	else if (relkind == RELKIND_RELATION ||
+			relkind == RELKIND_SEQUENCE ||
+			relkind == RELKIND_TOASTVALUE ||
+			relkind == RELKIND_VIEW ||
+			relkind == RELKIND_MATVIEW ||
+			relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		accessMethodId = HEAP_TABLE_AM_OID;
+	}
+
 	/*
 	 * Create the relation.  Inherited defaults and constraints are passed in
 	 * for immediate handling --- since they don't need parsing, they can be
@@ -751,6 +778,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 										  InvalidOid,
 										  ofTypeId,
 										  ownerId,
+										  accessMethodId,
 										  descriptor,
 										  list_concat(cookedDefaults,
 													  old_constraints),
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 82255b0d1d..96130de7e2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3282,6 +3282,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(options);
 	COPY_SCALAR_FIELD(oncommit);
 	COPY_STRING_FIELD(tablespacename);
+	COPY_STRING_FIELD(accessMethod);
 	COPY_SCALAR_FIELD(if_not_exists);
 }
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7da347e6bb..ee3907fccf 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -338,7 +338,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <str>		copy_file_name
 				database_name access_method_clause access_method attr_name
-				name cursor_name file_name
+				table_access_method_clause name cursor_name file_name
 				index_name opt_index_name cluster_index_specification
 
 %type <list>	func_name handler_name qual_Op qual_all_Op subquery_Op
@@ -3190,7 +3190,8 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionSpec table_access_method_clause OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
@@ -3200,15 +3201,16 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $10;
-					n->oncommit = $11;
-					n->tablespacename = $12;
+					n->accessMethod = $10;
+					n->options = $11;
+					n->oncommit = $12;
+					n->tablespacename = $13;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
-			OptTableElementList ')' OptInherit OptPartitionSpec OptWith
-			OnCommitOption OptTableSpace
+			OptTableElementList ')' OptInherit OptPartitionSpec table_access_method_clause
+			OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
@@ -3218,15 +3220,16 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $13;
-					n->oncommit = $14;
-					n->tablespacename = $15;
+					n->accessMethod = $13;
+					n->options = $14;
+					n->oncommit = $15;
+					n->tablespacename = $16;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name OF any_name
-			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
-			OptTableSpace
+			OptTypedTableElementList OptPartitionSpec table_access_method_clause
+			OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
@@ -3237,15 +3240,16 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $9;
-					n->oncommit = $10;
-					n->tablespacename = $11;
+					n->accessMethod = $9;
+					n->options = $10;
+					n->oncommit = $11;
+					n->tablespacename = $12;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
-			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
-			OptTableSpace
+			OptTypedTableElementList OptPartitionSpec table_access_method_clause
+			OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
@@ -3256,15 +3260,16 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
-					n->options = $12;
-					n->oncommit = $13;
-					n->tablespacename = $14;
+					n->accessMethod = $12;
+					n->options = $13;
+					n->oncommit = $14;
+					n->tablespacename = $15;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
-			OptTypedTableElementList PartitionBoundSpec OptPartitionSpec OptWith
-			OnCommitOption OptTableSpace
+			OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
+			table_access_method_clause OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
@@ -3275,15 +3280,16 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->partspec = $10;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $11;
-					n->oncommit = $12;
-					n->tablespacename = $13;
+					n->accessMethod = $11;
+					n->options = $12;
+					n->oncommit = $13;
+					n->tablespacename = $14;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
 			qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
-			OptWith OnCommitOption OptTableSpace
+			table_access_method_clause OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
@@ -3294,9 +3300,10 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->partspec = $13;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $14;
-					n->oncommit = $15;
-					n->tablespacename = $16;
+					n->accessMethod = $14;
+					n->options = $15;
+					n->oncommit = $16;
+					n->tablespacename = $17;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
@@ -3934,6 +3941,12 @@ part_elem: ColId opt_collate opt_class
 					$$ = n;
 				}
 		;
+
+table_access_method_clause:
+			USING access_method					{ $$ = $2; }
+			| /*EMPTY*/							{ $$ = DEFAULT_TABLE_ACCESS_METHOD; }
+		;
+
 /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
 OptWith:
 			WITH reloptions				{ $$ = $2; }
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9281860ff1..98579cc22f 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1891,11 +1891,8 @@ 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))
+	if (IsCatalogRelation(relation) ||
+			!OidIsValid(relation->rd_rel->relam))
 	{
 		relation->rd_tableamhandler = HEAP_TABLE_AM_HANDLER_OID;
 	}
@@ -2081,6 +2078,7 @@ formrdesc(const char *relationName, Oid relationReltype,
 	/*
 	 * initialize the table am handler
 	 */
+	relation->rd_rel->relam = HEAP_TABLE_AM_OID;
 	relation->rd_tableamroutine = GetHeapamTableAmRoutine();
 
 	/*
@@ -3251,6 +3249,7 @@ RelationBuildLocalRelation(const char *relname,
 						   Oid relnamespace,
 						   TupleDesc tupDesc,
 						   Oid relid,
+						   Oid accessmtd,
 						   Oid relfilenode,
 						   Oid reltablespace,
 						   bool shared_relation,
@@ -3431,6 +3430,8 @@ RelationBuildLocalRelation(const char *relname,
 
 	RelationInitPhysicalAddr(rel);
 
+	rel->rd_rel->relam = accessmtd;
+
 	if (relkind == RELKIND_RELATION ||
 		relkind == RELKIND_MATVIEW ||
 		relkind == RELKIND_VIEW ||	/* Not exactly the storage, but underlying
@@ -3963,6 +3964,7 @@ RelationCacheInitializePhase3(void)
 		if (relation->rd_tableamroutine == NULL &&
 			(relation->rd_rel->relkind == RELKIND_RELATION ||
 			 relation->rd_rel->relkind == RELKIND_MATVIEW ||
+			 relation->rd_rel->relkind == RELKIND_VIEW ||
 			 relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			 relation->rd_rel->relkind == RELKIND_TOASTVALUE))
 		{
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 9bdc63ceb5..e1376ee4d4 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -44,6 +44,7 @@ extern Relation heap_create(const char *relname,
 			Oid reltablespace,
 			Oid relid,
 			Oid relfilenode,
+			Oid accessmtd,
 			TupleDesc tupDesc,
 			char relkind,
 			char relpersistence,
@@ -58,6 +59,7 @@ extern Oid heap_create_with_catalog(const char *relname,
 						 Oid reltypeid,
 						 Oid reloftypeid,
 						 Oid ownerid,
+						 Oid accessmtd,
 						 TupleDesc tupdesc,
 						 List *cooked_constraints,
 						 char relkind,
diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h
index 127973dc84..675a929dd3 100644
--- a/src/include/catalog/pg_am.h
+++ b/src/include/catalog/pg_am.h
@@ -87,5 +87,6 @@ DESCR("block range index (BRIN) access method");
 DATA(insert OID = 4001 (  heap_tableam         heap_tableam_handler s ));
 DESCR("heap table access method");
 #define HEAP_TABLE_AM_OID 4001
+#define DEFAULT_TABLE_ACCESS_METHOD "heap_tableam"
 
 #endif							/* PG_AM_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index c7a43b8933..6df183a9ff 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2019,6 +2019,7 @@ typedef struct CreateStmt
 	List	   *options;		/* options from WITH clause */
 	OnCommitAction oncommit;	/* what do we do at COMMIT? */
 	char	   *tablespacename; /* table space to use, or NULL */
+	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
 } CreateStmt;
 
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 0c500729c0..3668b35c58 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -100,6 +100,7 @@ extern Relation RelationBuildLocalRelation(const char *relname,
 						   Oid relnamespace,
 						   TupleDesc tupDesc,
 						   Oid relid,
+						   Oid accessmtd,
 						   Oid relfilenode,
 						   Oid reltablespace,
 						   bool shared_relation,
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index b1419d4bc2..ccd88c0260 100644
--- a/src/test/regress/expected/type_sanity.out
+++ b/src/test/regress/expected/type_sanity.out
@@ -502,14 +502,18 @@ WHERE relkind NOT IN ('r', 'i', 'S', 't', 'v', 'm', 'c', 'f', 'p') OR
 -----+---------
 (0 rows)
 
--- Indexes should have an access method, others not.
+-- Except default tables, others should have an access method.
 SELECT p1.oid, p1.relname
 FROM pg_class as p1
-WHERE (p1.relkind = 'i' AND p1.relam = 0) OR
-    (p1.relkind != 'i' AND p1.relam != 0);
- oid | relname 
------+---------
-(0 rows)
+WHERE p1.relkind IN ('r', 'i', 'S', 't', 'v', 'm', 'p', 'i', 'I') and
+    p1.relam = 0;
+ oid  |   relname    
+------+--------------
+ 1247 | pg_type
+ 1249 | pg_attribute
+ 1255 | pg_proc
+ 1259 | pg_class
+(4 rows)
 
 -- **************** pg_attribute ****************
 -- Look for illegal values in pg_attribute fields
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index f9aeea3214..3ed0b9efcb 100644
--- a/src/test/regress/sql/type_sanity.sql
+++ b/src/test/regress/sql/type_sanity.sql
@@ -367,12 +367,12 @@ WHERE relkind NOT IN ('r', 'i', 'S', 't', 'v', 'm', 'c', 'f', 'p') OR
     relpersistence NOT IN ('p', 'u', 't') OR
     relreplident NOT IN ('d', 'n', 'f', 'i');
 
--- Indexes should have an access method, others not.
+-- Except default tables, others should have an access method.
 
 SELECT p1.oid, p1.relname
 FROM pg_class as p1
-WHERE (p1.relkind = 'i' AND p1.relam = 0) OR
-    (p1.relkind != 'i' AND p1.relam != 0);
+WHERE p1.relkind IN ('r', 'i', 'S', 't', 'v', 'm', 'p', 'i', 'I') and
+    p1.relam = 0;
 
 -- **************** pg_attribute ****************
 
-- 
2.16.1.windows.4

#126Robert Haas
robertmhaas@gmail.com
In reply to: Alexander Korotkov (#124)
Re: [HACKERS] Pluggable storage

On Fri, Feb 16, 2018 at 5:56 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

BTW, EnterpriseDB announces zheap table access method (heap with undo log)
[2]. I think this is great, and I'm looking forward for publishing zheap in
mailing lists. But I'm concerning about its compatibility with pluggable
table access methods API. Does zheap use table AM API from this thread? Or
does it just override current heap and needs to be adopted to use table AM
API? Or does it implements own API?

Right now it just hacks the code. The plan is to adapt it to whatever
API we settle on in this thread.

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

#127Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Robert Haas (#126)
Re: [HACKERS] Pluggable storage

On Fri, Feb 23, 2018 at 2:20 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Feb 16, 2018 at 5:56 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

BTW, EnterpriseDB announces zheap table access method (heap with undo

log)

[2]. I think this is great, and I'm looking forward for publishing

zheap in

mailing lists. But I'm concerning about its compatibility with pluggable
table access methods API. Does zheap use table AM API from this

thread? Or

does it just override current heap and needs to be adopted to use table

AM

API? Or does it implements own API?

Right now it just hacks the code. The plan is to adapt it to whatever
API we settle on in this thread.

Great, thank you for clarification. I'm looking forward reviewing zheap :)

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

#128David Steele
david@pgmasters.net
In reply to: Alexander Korotkov (#127)
Re: [HACKERS] Pluggable storage

On 2/26/18 3:19 AM, Alexander Korotkov wrote:

On Fri, Feb 23, 2018 at 2:20 AM, Robert Haas <robertmhaas@gmail.com
<mailto:robertmhaas@gmail.com>> wrote:

On Fri, Feb 16, 2018 at 5:56 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru <mailto:a.korotkov@postgrespro.ru>> wrote:

BTW, EnterpriseDB announces zheap table access method (heap with undo log)
[2].  I think this is great, and I'm looking forward for publishing zheap in
mailing lists.  But I'm concerning about its compatibility with pluggable
table access methods API.  Does zheap use table AM API from this thread?  Or
does it just override current heap and needs to be adopted to use table AM
API?  Or does it implements own API?

Right now it just hacks the code.  The plan is to adapt it to whatever
API we settle on in this thread.

Great, thank you for clarification.  I'm looking forward reviewing zheap :)

I think this entry should be moved the the next CF. I'll do that
tomorrow unless there are objections.

Regards,
--
-David
david@pgmasters.net

#129Michael Paquier
michael@paquier.xyz
In reply to: David Steele (#128)
Re: [HACKERS] Pluggable storage

On Wed, Mar 28, 2018 at 12:23:56PM -0400, David Steele wrote:

I think this entry should be moved the the next CF. I'll do that
tomorrow unless there are objections.

Instead of moving things to the next CF by default, perhaps it would
make more sense to mark things as reviewed with feedback as this is the
last CF? There is a 5-month gap between this commit fest and the next
one, I am getting afraid of flooding the beginning of v12 development
cycle with entries which keep rotting over time. If the author(s) claim
that they will be able to work on it, then that's of course fine.

Sorry for the digression, patches ignored across CFs contribute to the
bloat we see, and those eat the time of the CFM.
--
Michael

#130Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Michael Paquier (#129)
14 attachment(s)
Re: [HACKERS] Pluggable storage

On Thu, Mar 29, 2018 at 11:39 AM, Michael Paquier <michael@paquier.xyz>
wrote:

On Wed, Mar 28, 2018 at 12:23:56PM -0400, David Steele wrote:

I think this entry should be moved the the next CF. I'll do that
tomorrow unless there are objections.

Instead of moving things to the next CF by default, perhaps it would
make more sense to mark things as reviewed with feedback as this is the
last CF? There is a 5-month gap between this commit fest and the next
one, I am getting afraid of flooding the beginning of v12 development
cycle with entries which keep rotting over time.

Yes, especially I observed many of the "ready of committer" patches are just
moving from past commitfests without a review from committer.

If the author(s) claim
that they will be able to work on it, then that's of course fine.

But in this case, I am planning to work on it. Here I attached rebased
patches
on the latest master for review. I will move the patch to next commitfest
in the
commitfest app.

The attached patches doesn't work with recent JIT changes that are gone in
master, because of removal many of the members from TupleTableSlot structure
and it effects the JIT tuple deforming. This is yet to fixed.

There is an another thread proposed by Andres in abstracting the
TupleTableslot
dependency from HeapTuple in [1]/messages/by-id/20180220224318.gw4oe5jadhpmcdnm@alap3.anarazel.de. Based on the output of that thread, these
patches needs an update.

[1]: /messages/by-id/20180220224318.gw4oe5jadhpmcdnm@alap3.anarazel.de
/messages/by-id/20180220224318.gw4oe5jadhpmcdnm@alap3.anarazel.de

Regards,
Hari Babu
Fujitsu Australia

Attachments:

0013-Using-access-method-syntax-addition-to-create-table.patchapplication/octet-stream; name=0013-Using-access-method-syntax-addition-to-create-table.patchDownload
From 365f40e1d94341d4427a47005ceb84c927346ea5 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:17:54 +1100
Subject: [PATCH 13/14] Using "access method" syntax addition to create table

With the pluggable storage support, user can select the
table access method that he needs for the corresponding table.
The syntax addition is similar like CREATE INDEX.

CREATE TABLE ... [USING ACCESSMETHOD] ...

All the catalog relations are by default uses the HEAP method.
Currently the access method syntax support is added only for
main relations.

Currently Default access method HEAP is used for TOAST, VIEW,
SEQUENCE AND MATERIALIZED VIEWS.

Pending items: support of displaying access method with \d commands
---
 src/backend/bootstrap/bootparse.y         |  2 +
 src/backend/catalog/heap.c                |  4 ++
 src/backend/catalog/index.c               |  2 +-
 src/backend/catalog/toasting.c            |  1 +
 src/backend/commands/cluster.c            |  1 +
 src/backend/commands/tablecmds.c          | 28 ++++++++++++
 src/backend/nodes/copyfuncs.c             |  1 +
 src/backend/parser/gram.y                 | 71 ++++++++++++++++++-------------
 src/backend/utils/cache/relcache.c        | 12 +++---
 src/include/catalog/heap.h                |  2 +
 src/include/catalog/pg_am.h               |  1 +
 src/include/nodes/parsenodes.h            |  1 +
 src/include/utils/relcache.h              |  1 +
 src/test/regress/expected/type_sanity.out | 16 ++++---
 src/test/regress/sql/type_sanity.sql      |  6 +--
 15 files changed, 105 insertions(+), 44 deletions(-)

diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 4ea3aa97cf..8996a2fecd 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -225,6 +225,7 @@ Boot_CreateStmt:
 												   shared_relation ? GLOBALTABLESPACE_OID : 0,
 												   $3,
 												   InvalidOid,
+												   HEAP_TABLE_AM_OID,
 												   tupdesc,
 												   RELKIND_RELATION,
 												   RELPERSISTENCE_PERMANENT,
@@ -244,6 +245,7 @@ Boot_CreateStmt:
 													  $7,
 													  InvalidOid,
 													  BOOTSTRAP_SUPERUSERID,
+													  HEAP_TABLE_AM_OID,
 													  tupdesc,
 													  NIL,
 													  RELKIND_RELATION,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index a1def77944..0ad60e8b3b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -256,6 +256,7 @@ heap_create(const char *relname,
 			Oid reltablespace,
 			Oid relid,
 			Oid relfilenode,
+			Oid accessmtd,
 			TupleDesc tupDesc,
 			char relkind,
 			char relpersistence,
@@ -350,6 +351,7 @@ heap_create(const char *relname,
 									 relnamespace,
 									 tupDesc,
 									 relid,
+									 accessmtd,
 									 relfilenode,
 									 reltablespace,
 									 shared_relation,
@@ -1032,6 +1034,7 @@ heap_create_with_catalog(const char *relname,
 						 Oid reltypeid,
 						 Oid reloftypeid,
 						 Oid ownerid,
+						 Oid accessmtd,
 						 TupleDesc tupdesc,
 						 List *cooked_constraints,
 						 char relkind,
@@ -1175,6 +1178,7 @@ heap_create_with_catalog(const char *relname,
 							   reltablespace,
 							   relid,
 							   InvalidOid,
+							   accessmtd,
 							   tupdesc,
 							   relkind,
 							   relpersistence,
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 5d7336d77f..e62786b5bf 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -901,6 +901,7 @@ index_create(Relation heapRelation,
 								tableSpaceId,
 								indexRelationId,
 								relFileNode,
+								accessMethodObjectId,
 								indexTupDesc,
 								relkind,
 								relpersistence,
@@ -924,7 +925,6 @@ index_create(Relation heapRelation,
 	 * XXX should have a cleaner way to create cataloged indexes
 	 */
 	indexRelation->rd_rel->relowner = heapRelation->rd_rel->relowner;
-	indexRelation->rd_rel->relam = accessMethodObjectId;
 	indexRelation->rd_rel->relhasoids = false;
 
 	/*
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 9007dc6ebe..377d534dc0 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -266,6 +266,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 										   toast_typid,
 										   InvalidOid,
 										   rel->rd_rel->relowner,
+										   rel->rd_rel->relam,
 										   tupdesc,
 										   NIL,
 										   RELKIND_TOASTVALUE,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 556f745012..045b790b93 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -679,6 +679,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
 										  InvalidOid,
 										  InvalidOid,
 										  OldHeap->rd_rel->relowner,
+										  OldHeap->rd_rel->relam,
 										  OldHeapDesc,
 										  NIL,
 										  RELKIND_RELATION,
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index fd197d9042..cc98ba488b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -537,6 +537,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
 	Oid			ofTypeId;
 	ObjectAddress address;
+	Oid			accessMethodId = InvalidOid;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -741,6 +742,32 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			attr->attidentity = colDef->identity;
 	}
 
+	/*
+	 * look up the access method, verify it can handle the requested features
+	 */
+	if (stmt->accessMethod != NULL)
+	{
+		HeapTuple	tuple;
+
+		tuple = SearchSysCache1(AMNAME, PointerGetDatum(stmt->accessMethod));
+		if (!HeapTupleIsValid(tuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("table access method \"%s\" does not exist",
+								 stmt->accessMethod)));
+		accessMethodId = HeapTupleGetOid(tuple);
+		ReleaseSysCache(tuple);
+	}
+	else if (relkind == RELKIND_RELATION ||
+			relkind == RELKIND_SEQUENCE ||
+			relkind == RELKIND_TOASTVALUE ||
+			relkind == RELKIND_VIEW ||
+			relkind == RELKIND_MATVIEW ||
+			relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		accessMethodId = HEAP_TABLE_AM_OID;
+	}
+
 	/*
 	 * Create the relation.  Inherited defaults and constraints are passed in
 	 * for immediate handling --- since they don't need parsing, they can be
@@ -753,6 +780,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 										  InvalidOid,
 										  ofTypeId,
 										  ownerId,
+										  accessMethodId,
 										  descriptor,
 										  list_concat(cookedDefaults,
 													  old_constraints),
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c7293a60d7..261582abf7 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3284,6 +3284,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(options);
 	COPY_SCALAR_FIELD(oncommit);
 	COPY_STRING_FIELD(tablespacename);
+	COPY_STRING_FIELD(accessMethod);
 	COPY_SCALAR_FIELD(if_not_exists);
 }
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 71b5854447..9b4553730c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -339,7 +339,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <str>		copy_file_name
 				database_name access_method_clause access_method attr_name
-				name cursor_name file_name
+				table_access_method_clause name cursor_name file_name
 				index_name opt_index_name cluster_index_specification
 
 %type <list>	func_name handler_name qual_Op qual_all_Op subquery_Op
@@ -3191,7 +3191,8 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionSpec table_access_method_clause OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
@@ -3201,15 +3202,16 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $10;
-					n->oncommit = $11;
-					n->tablespacename = $12;
+					n->accessMethod = $10;
+					n->options = $11;
+					n->oncommit = $12;
+					n->tablespacename = $13;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
-			OptTableElementList ')' OptInherit OptPartitionSpec OptWith
-			OnCommitOption OptTableSpace
+			OptTableElementList ')' OptInherit OptPartitionSpec table_access_method_clause
+			OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
@@ -3219,15 +3221,16 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $13;
-					n->oncommit = $14;
-					n->tablespacename = $15;
+					n->accessMethod = $13;
+					n->options = $14;
+					n->oncommit = $15;
+					n->tablespacename = $16;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name OF any_name
-			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
-			OptTableSpace
+			OptTypedTableElementList OptPartitionSpec table_access_method_clause
+			OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
@@ -3238,15 +3241,16 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $9;
-					n->oncommit = $10;
-					n->tablespacename = $11;
+					n->accessMethod = $9;
+					n->options = $10;
+					n->oncommit = $11;
+					n->tablespacename = $12;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
-			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
-			OptTableSpace
+			OptTypedTableElementList OptPartitionSpec table_access_method_clause
+			OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
@@ -3257,15 +3261,16 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
-					n->options = $12;
-					n->oncommit = $13;
-					n->tablespacename = $14;
+					n->accessMethod = $12;
+					n->options = $13;
+					n->oncommit = $14;
+					n->tablespacename = $15;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
-			OptTypedTableElementList PartitionBoundSpec OptPartitionSpec OptWith
-			OnCommitOption OptTableSpace
+			OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
+			table_access_method_clause OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
@@ -3276,15 +3281,16 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->partspec = $10;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $11;
-					n->oncommit = $12;
-					n->tablespacename = $13;
+					n->accessMethod = $11;
+					n->options = $12;
+					n->oncommit = $13;
+					n->tablespacename = $14;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
 			qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
-			OptWith OnCommitOption OptTableSpace
+			table_access_method_clause OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
@@ -3295,9 +3301,10 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->partspec = $13;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $14;
-					n->oncommit = $15;
-					n->tablespacename = $16;
+					n->accessMethod = $14;
+					n->options = $15;
+					n->oncommit = $16;
+					n->tablespacename = $17;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
@@ -3936,6 +3943,12 @@ part_elem: ColId opt_collate opt_class
 					$$ = n;
 				}
 		;
+
+table_access_method_clause:
+			USING access_method					{ $$ = $2; }
+			| /*EMPTY*/							{ $$ = DEFAULT_TABLE_ACCESS_METHOD; }
+		;
+
 /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
 OptWith:
 			WITH reloptions				{ $$ = $2; }
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 94bca41432..c99602c74f 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1959,11 +1959,8 @@ 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))
+	if (IsCatalogRelation(relation) ||
+			!OidIsValid(relation->rd_rel->relam))
 	{
 		relation->rd_tableamhandler = HEAP_TABLE_AM_HANDLER_OID;
 	}
@@ -2145,6 +2142,7 @@ formrdesc(const char *relationName, Oid relationReltype,
 	/*
 	 * initialize the table am handler
 	 */
+	relation->rd_rel->relam = HEAP_TABLE_AM_OID;
 	relation->rd_tableamroutine = GetHeapamTableAmRoutine();
 
 	/*
@@ -3317,6 +3315,7 @@ RelationBuildLocalRelation(const char *relname,
 						   Oid relnamespace,
 						   TupleDesc tupDesc,
 						   Oid relid,
+						   Oid accessmtd,
 						   Oid relfilenode,
 						   Oid reltablespace,
 						   bool shared_relation,
@@ -3497,6 +3496,8 @@ RelationBuildLocalRelation(const char *relname,
 
 	RelationInitPhysicalAddr(rel);
 
+	rel->rd_rel->relam = accessmtd;
+
 	if (relkind == RELKIND_RELATION ||
 		relkind == RELKIND_MATVIEW ||
 		relkind == RELKIND_VIEW ||	/* Not exactly the storage, but underlying
@@ -4029,6 +4030,7 @@ RelationCacheInitializePhase3(void)
 		if (relation->rd_tableamroutine == NULL &&
 			(relation->rd_rel->relkind == RELKIND_RELATION ||
 			 relation->rd_rel->relkind == RELKIND_MATVIEW ||
+			 relation->rd_rel->relkind == RELKIND_VIEW ||
 			 relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			 relation->rd_rel->relkind == RELKIND_TOASTVALUE))
 		{
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 59fc052494..00688d4718 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -45,6 +45,7 @@ extern Relation heap_create(const char *relname,
 			Oid reltablespace,
 			Oid relid,
 			Oid relfilenode,
+			Oid accessmtd,
 			TupleDesc tupDesc,
 			char relkind,
 			char relpersistence,
@@ -59,6 +60,7 @@ extern Oid heap_create_with_catalog(const char *relname,
 						 Oid reltypeid,
 						 Oid reloftypeid,
 						 Oid ownerid,
+						 Oid accessmtd,
 						 TupleDesc tupdesc,
 						 List *cooked_constraints,
 						 char relkind,
diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h
index 127973dc84..675a929dd3 100644
--- a/src/include/catalog/pg_am.h
+++ b/src/include/catalog/pg_am.h
@@ -87,5 +87,6 @@ DESCR("block range index (BRIN) access method");
 DATA(insert OID = 4001 (  heap_tableam         heap_tableam_handler s ));
 DESCR("heap table access method");
 #define HEAP_TABLE_AM_OID 4001
+#define DEFAULT_TABLE_ACCESS_METHOD "heap_tableam"
 
 #endif							/* PG_AM_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 92082b3a7a..277f6b4485 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2020,6 +2020,7 @@ typedef struct CreateStmt
 	List	   *options;		/* options from WITH clause */
 	OnCommitAction oncommit;	/* what do we do at COMMIT? */
 	char	   *tablespacename; /* table space to use, or NULL */
+	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
 } CreateStmt;
 
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index df16b4ca98..858a7b30d2 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -101,6 +101,7 @@ extern Relation RelationBuildLocalRelation(const char *relname,
 						   Oid relnamespace,
 						   TupleDesc tupDesc,
 						   Oid relid,
+						   Oid accessmtd,
 						   Oid relfilenode,
 						   Oid reltablespace,
 						   bool shared_relation,
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index b1419d4bc2..ccd88c0260 100644
--- a/src/test/regress/expected/type_sanity.out
+++ b/src/test/regress/expected/type_sanity.out
@@ -502,14 +502,18 @@ WHERE relkind NOT IN ('r', 'i', 'S', 't', 'v', 'm', 'c', 'f', 'p') OR
 -----+---------
 (0 rows)
 
--- Indexes should have an access method, others not.
+-- Except default tables, others should have an access method.
 SELECT p1.oid, p1.relname
 FROM pg_class as p1
-WHERE (p1.relkind = 'i' AND p1.relam = 0) OR
-    (p1.relkind != 'i' AND p1.relam != 0);
- oid | relname 
------+---------
-(0 rows)
+WHERE p1.relkind IN ('r', 'i', 'S', 't', 'v', 'm', 'p', 'i', 'I') and
+    p1.relam = 0;
+ oid  |   relname    
+------+--------------
+ 1247 | pg_type
+ 1249 | pg_attribute
+ 1255 | pg_proc
+ 1259 | pg_class
+(4 rows)
 
 -- **************** pg_attribute ****************
 -- Look for illegal values in pg_attribute fields
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index f9aeea3214..3ed0b9efcb 100644
--- a/src/test/regress/sql/type_sanity.sql
+++ b/src/test/regress/sql/type_sanity.sql
@@ -367,12 +367,12 @@ WHERE relkind NOT IN ('r', 'i', 'S', 't', 'v', 'm', 'c', 'f', 'p') OR
     relpersistence NOT IN ('p', 'u', 't') OR
     relreplident NOT IN ('d', 'n', 'f', 'i');
 
--- Indexes should have an access method, others not.
+-- Except default tables, others should have an access method.
 
 SELECT p1.oid, p1.relname
 FROM pg_class as p1
-WHERE (p1.relkind = 'i' AND p1.relam = 0) OR
-    (p1.relkind != 'i' AND p1.relam != 0);
+WHERE p1.relkind IN ('r', 'i', 'S', 't', 'v', 'm', 'p', 'i', 'I') and
+    p1.relam = 0;
 
 -- **************** pg_attribute ****************
 
-- 
2.16.1.windows.4

0014-ExecARUpdateTriggers-is-updated-to-accept-slot-inste.patchapplication/octet-stream; name=0014-ExecARUpdateTriggers-is-updated-to-accept-slot-inste.patchDownload
From de19ba885126c63980970007fa789d266c0fa7e3 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:17:54 +1100
Subject: [PATCH 14/14] ExecARUpdateTriggers is updated to accept slot instead
 of tuple

The After record update trigger function is changed to accept
slot instead of newtuple, thus is reduces the need of TableTuple
variable in the callers.
---
 src/backend/commands/trigger.c         |  4 ++--
 src/backend/executor/execReplication.c |  5 +----
 src/backend/executor/nodeModifyTable.c | 22 ++++++----------------
 src/include/commands/trigger.h         |  2 +-
 src/pl/tcl/pltcl.c                     |  2 +-
 5 files changed, 11 insertions(+), 24 deletions(-)

diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 9a3f19a79a..33e1712bbd 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3072,7 +3072,7 @@ void
 ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 					 ItemPointer tupleid,
 					 HeapTuple fdw_trigtuple,
-					 HeapTuple newtuple,
+					 TupleTableSlot *slot,
 					 List *recheckIndexes,
 					 TransitionCaptureState *transition_capture)
 {
@@ -3084,7 +3084,7 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 		  transition_capture->tcs_update_new_table)))
 	{
 		HeapTuple	trigtuple;
-
+		HeapTuple	newtuple = slot ? ExecHeapifySlot(slot) : NULL;
 		/*
 		 * Note: if the UPDATE is converted into a DELETE+INSERT as part of
 		 * update-partition-key operation, then this function is also called
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 3ab6db5902..48661eb2d6 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -391,7 +391,6 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 						 TupleTableSlot *searchslot, TupleTableSlot *slot)
 {
 	bool		skip_tuple = false;
-	TableTuple tuple;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	ItemPointer tid = &(searchslot->tts_tid);
@@ -427,12 +426,10 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 		table_update(rel, tid, slot, estate, GetCurrentCommandId(true), InvalidSnapshot,
 					   true, &hufd, &lockmode, IndexFunc, &recheckIndexes);
 
-		tuple = ExecHeapifySlot(slot);
-
 		/* AFTER ROW UPDATE Triggers */
 		ExecARUpdateTriggers(estate, resultRelInfo,
 							 tid,
-							 NULL, tuple, recheckIndexes, NULL);
+							 NULL, slot, recheckIndexes, NULL);
 
 		list_free(recheckIndexes);
 	}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 2b84654248..880b7ad6a1 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -272,7 +272,6 @@ ExecInsert(ModifyTableState *mtstate,
 		   EState *estate,
 		   bool canSetTag)
 {
-	TableTuple tuple;
 	ResultRelInfo *resultRelInfo;
 	Relation	resultRelationDesc;
 	Oid			newId;
@@ -547,10 +546,9 @@ ExecInsert(ModifyTableState *mtstate,
 	if (mtstate->operation == CMD_UPDATE && mtstate->mt_transition_capture
 		&& mtstate->mt_transition_capture->tcs_update_new_table)
 	{
-		tuple = ExecHeapifySlot(slot);
 		ExecARUpdateTriggers(estate, resultRelInfo, NULL,
 							 NULL,
-							 tuple,
+							 slot,
 							 NULL,
 							 mtstate->mt_transition_capture);
 
@@ -934,7 +932,6 @@ ExecUpdate(ModifyTableState *mtstate,
 		   EState *estate,
 		   bool canSetTag)
 {
-	TableTuple tuple;
 	ResultRelInfo *resultRelInfo;
 	Relation	resultRelationDesc;
 	HTSU_Result result;
@@ -952,7 +949,7 @@ ExecUpdate(ModifyTableState *mtstate,
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecHeapifySlot(slot);
+	ExecMaterializeSlot(slot);
 
 	/*
 	 * get information on the (current) result relation
@@ -969,9 +966,6 @@ ExecUpdate(ModifyTableState *mtstate,
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
-
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW UPDATE Triggers */
@@ -983,9 +977,6 @@ ExecUpdate(ModifyTableState *mtstate,
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
-
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
@@ -1005,9 +996,6 @@ ExecUpdate(ModifyTableState *mtstate,
 		 * tableoid column, so initialize t_tableOid before evaluating them.
 		 */
 		ExecSlotUpdateTupleTableoid(slot, RelationGetRelid(resultRelationDesc));
-
-		/* FDW might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 	else
 	{
@@ -1064,6 +1052,7 @@ lreplace:;
 			PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
 			int			map_index;
 			TupleConversionMap *tupconv_map;
+			HeapTuple	tuple = ExecHeapifySlot(slot);
 
 			/*
 			 * Disallow an INSERT ON CONFLICT DO UPDATE that causes the
@@ -1195,6 +1184,8 @@ lreplace:;
 
 		if (result == HeapTupleUpdated && !IsolationUsesXactSnapshot())
 		{
+			TableTuple tuple;
+
 			result = table_lock_tuple(resultRelationDesc, tupleid,
 										estate->es_snapshot,
 										&tuple, estate->es_output_cid,
@@ -1218,7 +1209,6 @@ lreplace:;
 					return NULL;
 				}
 				slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
-				tuple = ExecHeapifySlot(slot);
 				goto lreplace;
 			}
 		}
@@ -1290,7 +1280,7 @@ lreplace:;
 		(estate->es_processed)++;
 
 	/* AFTER ROW UPDATE Triggers */
-	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldtuple, tuple,
+	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldtuple, slot,
 						 recheckIndexes,
 						 mtstate->operation == CMD_INSERT ?
 						 mtstate->mt_oc_transition_capture :
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 2fe7ed33a5..7cac03d469 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -230,7 +230,7 @@ extern void ExecARUpdateTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
 					 ItemPointer tupleid,
 					 HeapTuple fdw_trigtuple,
-					 HeapTuple newtuple,
+					 TupleTableSlot *slot,
 					 List *recheckIndexes,
 					 TransitionCaptureState *transition_capture);
 extern TupleTableSlot *ExecIRUpdateTriggers(EState *estate,
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index 558cabc949..0d9a40f711 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -2432,7 +2432,7 @@ pltcl_process_SPI_result(Tcl_Interp *interp,
 {
 	int			my_rc = TCL_OK;
 	int			loop_rc;
-	HeapTuple  *tuples;
+	TableTuple *tuples;
 	TupleDesc	tupdesc;
 
 	switch (spi_rc)
-- 
2.16.1.windows.4

0001-Change-Create-Access-method-to-include-table-handler.patchapplication/octet-stream; name=0001-Change-Create-Access-method-to-include-table-handler.patchDownload
From c73be665a9b3f6e0785333764af1ab119538dcf3 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Thu, 29 Mar 2018 16:17:54 +1100
Subject: [PATCH 01/14] 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 cd5ba2d4d8..71b5854447 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -322,6 +322,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
@@ -5312,16 +5314,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 bfc90098f8..82b3ae8c03 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3915,6 +3915,10 @@ DATA(insert OID = 3311 (  tsm_handler_in	PGNSP PGUID 12 1 0 0 0 f f f f f i s 1
 DESCR("I/O");
 DATA(insert OID = 3312 (  tsm_handler_out	PGNSP PGUID 12 1 0 0 0 f f f t f i s 1 0 2275 "3310" _null_ _null_ _null_ _null_ _null_ tsm_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 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 t f i s 1 0 2275 "3998" _null_ _null_ _null_ _null_ _null_ table_am_handler_out _null_ _null_ _null_ ));
+DESCR("I/O");
 
 /* tablesample method handlers */
 DATA(insert OID = 3313 (  bernoulli			PGNSP PGUID 12 1 0 0 0 f f f t f v s 1 0 3310 "2281" _null_ _null_ _null_ _null_ _null_ tsm_bernoulli_handler _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 01608d2c04..3a0772f46c 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1720,11 +1720,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 a593d37643..20bba26052 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1156,15 +1156,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.16.1.windows.4

0002-Table-AM-API-init-functions.patchapplication/octet-stream; name=0002-Table-AM-API-init-functions.patchDownload
From 0cc2da2fe582d53c649cfcf0b3d3c2ee1fd1e6e1 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:17:54 +1100
Subject: [PATCH 02/14] 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 82b3ae8c03..d15f62421a 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -556,6 +556,11 @@ DESCR("convert int4 to float4");
 DATA(insert OID = 319 (  int4			   PGNSP PGUID 12 1 0 0 0 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 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 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 443de22704..8867e1d3f2 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -500,6 +500,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 */
 	T_CallContext				/* in nodes/parsenodes.h */
-- 
2.16.1.windows.4

0003-Adding-tableam-hanlder-to-relation-structure.patchapplication/octet-stream; name=0003-Adding-tableam-hanlder-to-relation-structure.patchDownload
From 6c0da2ff819d41fb41c9fd07fa03e0ca668c9381 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:59:20 +1100
Subject: [PATCH 03/14] Adding tableam hanlder to relation structure

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

diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 48f92dc430..94bca41432 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/tupdesc_details.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -1402,10 +1403,28 @@ 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:
+		case RELKIND_PARTITIONED_INDEX:
+			Assert(relation->rd_rel->relam != 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);
@@ -1904,6 +1923,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
@@ -2058,6 +2142,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
 	 */
@@ -2390,6 +2479,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)
@@ -3406,6 +3497,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.
 	 *
@@ -3927,6 +4026,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);
 
@@ -5739,6 +5850,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 c26c395b0b..1ad00e4e3a 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -162,6 +162,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)
 	 *
@@ -446,6 +452,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 dbbf41b0c1..df16b4ca98 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -77,6 +77,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.16.1.windows.4

0004-Adding-tuple-visibility-functions-to-table-AM.patchapplication/octet-stream; name=0004-Adding-tuple-visibility-functions-to-table-AM.patchDownload
From e5e1e08073f742c87381c7dcad12addf4f625eb4 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:59:20 +1100
Subject: [PATCH 04/14] 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/access/nbtree/nbtsort.c                |   2 +-
 src/backend/catalog/index.c                        |   7 +-
 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 +----
 29 files changed, 329 insertions(+), 220 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 94e051d642..b0ed27e883 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 ef33cacec6..e805981bb9 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);
@@ -158,7 +159,7 @@ statapprox_heap(Relation rel, output_type *stat)
 			 * bother distinguishing tuples inserted/deleted by our own
 			 * transaction.
 			 */
-			switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf))
+			switch (rel->rd_tableamroutine->snapshot_satisfiesVacuum(&tuple, OldestXmin, buf))
 			{
 				case HEAPTUPLE_LIVE:
 				case HEAPTUPLE_DELETE_IN_PROGRESS:
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index b599b6ca21..17b2fd9f26 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 d7279248e7..3a595ef50c 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"
@@ -442,7 +443,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);
@@ -657,7 +658,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);
 
@@ -845,6 +847,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;
@@ -889,6 +892,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;
@@ -958,23 +962,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
@@ -986,6 +998,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.
@@ -1043,6 +1061,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;
@@ -1858,7 +1877,7 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 
 	pgstat_count_heap_getnext(scan->rs_rd);
 
-	return &(scan->rs_ctup);
+	return heap_copytuple(&(scan->rs_ctup));
 }
 
 /*
@@ -1977,7 +1996,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);
@@ -2124,7 +2143,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 */
@@ -2298,7 +2317,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;
@@ -3124,7 +3143,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)
 	{
@@ -3235,7 +3254,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;
 	}
 
@@ -3696,7 +3715,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);
@@ -3877,7 +3896,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;
 	}
 
@@ -4714,7 +4733,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/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c
index 098e0ce1be..f17a9cee36 100644
--- a/src/backend/access/nbtree/nbtsort.c
+++ b/src/backend/access/nbtree/nbtsort.c
@@ -80,7 +80,7 @@
 #include "utils/rel.h"
 #include "utils/sortsupport.h"
 #include "utils/tuplesort.h"
-
+#include "utils/tqual.h"
 
 /* Magic numbers for parallel state sharing */
 #define PARALLEL_KEY_BTREE_SHARED		UINT64CONST(0xA000000000000001)
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index bc99a60d34..ee27a9ff71 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2437,6 +2437,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	TransactionId OldestXmin;
 	BlockNumber root_blkno = InvalidBlockNumber;
 	OffsetNumber root_offsets[MaxHeapTuplesPerPage];
+	TableAmRoutine *method;
 
 	/*
 	 * sanity checks
@@ -2530,6 +2531,8 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	Assert(snapshot == SnapshotAny ? TransactionIdIsValid(OldestXmin) :
 		   !TransactionIdIsValid(OldestXmin));
 	Assert(snapshot == SnapshotAny || !anyvisible);
+	
+	method = heapRelation->rd_tableamroutine;
 
 	/* set our scan endpoints */
 	if (!allow_sync)
@@ -2610,8 +2613,8 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 			 * CREATE INDEX and ANALYZE may produce wildly different reltuples
 			 * values, e.g. when there are many recently-dead tuples.
 			 */
-			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 378784a93c..a74137d8c8 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 639b6992d5..c7fed4131f 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"
@@ -987,7 +988,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 f9da24c491..3e8274b732 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -995,7 +995,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 			 * cases impossible (e.g. in-progress insert from the same
 			 * transaction).
 			 */
-			switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf))
+			switch (onerel->rd_tableamroutine->snapshot_satisfiesVacuum(&tuple, OldestXmin, buf))
 			{
 				case HEAPTUPLE_DEAD:
 
@@ -2202,7 +2202,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 3e1c9e0714..bdb82db149 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -459,7 +459,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 1b09868ff8..cc038e3842 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -200,6 +200,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
  */
 static void
 ExecCheckHeapTupleVisible(EState *estate,
+						  Relation rel,
 						  HeapTuple tuple,
 						  Buffer buffer)
 {
@@ -211,7 +212,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
@@ -246,7 +247,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);
 }
 
@@ -1428,7 +1429,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 872d6e5735..9d7872b439 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -566,7 +566,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 4123cdebcf..250aa92e80 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 654eca4f3f..8f8bdc8e6a 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 3bb708f863..0175715e50 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -286,7 +286,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 4b45d3cccd..407672e462 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.16.1.windows.4

0005-slot-hooks-are-added-to-table-AM.patchapplication/octet-stream; name=0005-slot-hooks-are-added-to-table-AM.patchDownload
From 45eb7ab3ac587bfad2dfb746b71f60d7e105f952 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:17:54 +1100
Subject: [PATCH 05/14] 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.

Pending items: JIT changes has some problem with TupleTableSlot
structure changes. Yet to fix it. 
---
 contrib/postgres_fdw/postgres_fdw.c       |   2 +-
 src/backend/access/common/heaptuple.c     | 312 +-------------------
 src/backend/access/heap/heapam_handler.c  |   2 +
 src/backend/access/table/Makefile         |   2 +-
 src/backend/access/table/tableam_common.c | 460 ++++++++++++++++++++++++++++++
 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         | 290 ++++++++-----------
 src/backend/executor/nodeForeignscan.c    |   2 +-
 src/backend/executor/nodeModifyTable.c    |  26 +-
 src/backend/executor/tqueue.c             |   2 +-
 src/backend/replication/logical/worker.c  |   5 +-
 src/include/access/htup_details.h         |  17 +-
 src/include/access/tableam_common.h       |  37 +++
 src/include/access/tableamapi.h           |   2 +
 src/include/executor/tuptable.h           |  61 ++--
 20 files changed, 754 insertions(+), 612 deletions(-)
 create mode 100644 src/backend/access/table/tableam_common.c

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index e8a0d5482a..66525ff79f 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -3753,7 +3753,7 @@ apply_returning_filter(PgFdwDirectModifyState *dmstate,
 	 */
 	if (dmstate->hasSystemCols)
 	{
-		HeapTuple	resultTup = ExecMaterializeSlot(resultSlot);
+		HeapTuple	resultTup = ExecHeapifySlot(resultSlot);
 
 		/* ctid */
 		if (dmstate->ctidAttno)
diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 960bbe4203..eb8b48cf4e 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/tupdesc_details.h"
 #include "access/tuptoaster.h"
@@ -80,7 +81,7 @@
 /*
  * Return the missing value of an attribute, or NULL if there isn't one.
  */
-static Datum
+Datum
 getmissingattr(TupleDesc tupleDesc,
 			   int attnum, bool *isnull)
 {
@@ -1398,111 +1399,6 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 		values[attnum] = getmissingattr(tupleDesc, attnum + 1, &isnull[attnum]);
 }
 
-/*
- * 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 */
-	uint32		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.
@@ -1518,89 +1414,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 or missing value 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))
-		return getmissingattr(slot->tts_tupleDescriptor, attnum, isnull);
-
-	/*
-	 * 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, false);
 }
 
 /*
@@ -1612,38 +1426,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 NULLS or missing values.
-	 */
-	if (attnum < tdesc_natts)
-		slot_getmissingattrs(slot, attnum, tdesc_natts);
-
-	slot->tts_nvalid = tdesc_natts;
+	slot->tts_slottableam->slot_virtualize_tuple(slot, slot->tts_tupleDescriptor->natts);
 }
 
 /*
@@ -1654,41 +1437,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 NULLs or missing values
-	 */
-	if (attno < attnum)
-		slot_getmissingattrs(slot, attno, attnum);
-
-	slot->tts_nvalid = attnum;
+	slot->tts_slottableam->slot_virtualize_tuple(slot, attnum);
 }
 
 /*
@@ -1699,42 +1448,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, tupleDesc);
-	}
+	bool		isnull;
 
-	/*
-	 * 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;
+	slot->tts_slottableam->slot_getattr(slot, attnum, &isnull, false);
 
-	/*
-	 * 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");
-
-	/* and let the tuple tell it */
-	return heap_attisnull(tuple, attnum, tupleDesc);
+	return isnull;
 }
 
 /*
@@ -1748,19 +1466,9 @@ bool
 slot_getsysattr(TupleTableSlot *slot, int attnum,
 				Datum *value, bool *isnull)
 {
-	HeapTuple	tuple = slot->tts_tuple;
 
-	Assert(attnum < 0);			/* else caller error */
-	if (tuple == NULL ||
-		tuple == &(slot->tts_minhdr))
-	{
-		/* No physical tuple, or minimal tuple, so fail */
-		*value = (Datum) 0;
-		*isnull = true;
-		return false;
-	}
-	*value = heap_getsysattr(tuple, attnum, slot->tts_tupleDescriptor, isnull);
-	return true;
+	*value = slot->tts_slottableam->slot_getattr(slot, attnum, isnull, true);
+	return *isnull ? false : true;
 }
 
 /*
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..376dd6391e
--- /dev/null
+++ b/src/backend/access/table/tableam_common.c
@@ -0,0 +1,460 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 (HeapTupleHeaderGetNatts(stuple->hst_heaptuple->t_data) <
+								slot->tts_tupleDescriptor->natts)
+			{
+				MemoryContext oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
+
+				tup = heap_expand_tuple(stuple->hst_heaptuple,
+										slot->tts_tupleDescriptor);
+				if (slot->tts_shouldFree)
+					heap_freetuple(stuple->hst_heaptuple);
+				stuple->hst_heaptuple = tup;
+				slot->tts_shouldFree = true;
+				MemoryContextSwitchTo(oldContext);
+			}
+
+			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
+		{
+			if (HeapTupleHeaderGetNatts(stuple->hst_heaptuple->t_data)
+				< slot->tts_tupleDescriptor->natts)
+				tup = minimal_expand_tuple(stuple->hst_heaptuple,
+											slot->tts_tupleDescriptor);
+			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 NULLS or missing values.
+	 */
+	if (attno < upto)
+		slot_getmissingattrs(slot, attno, upto);
+
+	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, bool noerror)
+{
+	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 */
+		{
+			if (noerror)
+				goto no_error_return;
+			elog(ERROR, "cannot extract system attribute from virtual tuple");
+		}
+		if (tuple == &(stuple->hst_minhdr)) /* internal error */
+		{
+			if (noerror)
+				goto no_error_return;
+			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 or missing value 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))
+		return getmissingattr(slot->tts_tupleDescriptor, attnum, isnull);
+
+	/*
+	 * 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];
+
+no_error_return:
+	*isnull = true;
+	return (Datum) 0;
+}
+
+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 a42861da0d..9bd7257123 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2679,7 +2679,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 23892b1b81..69f69fe727 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -484,7 +484,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 9d8df5986e..3b3f8b97fb 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2515,7 +2515,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;
@@ -2596,7 +2596,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;
@@ -2954,7 +2954,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;
@@ -2996,7 +2996,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 	if (newSlot != NULL)
 	{
 		slot = ExecFilterJunk(relinfo->ri_junkFilter, newSlot);
-		slottuple = ExecMaterializeSlot(slot);
+		slottuple = ExecHeapifySlot(slot);
 		newtuple = slottuple;
 	}
 
@@ -3110,7 +3110,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;
@@ -4239,14 +4239,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 e530b262da..a1db38c6e6 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -494,13 +494,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();
@@ -512,13 +514,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();
@@ -530,13 +533,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 78cfcadea0..d7ad51de36 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"
@@ -131,15 +132,16 @@ MakeTupleTableSlot(TupleDesc tupleDesc)
 	slot->tts_isempty = true;
 	slot->tts_shouldFree = false;
 	slot->tts_shouldFreeMin = false;
-	slot->tts_tuple = NULL;
 	slot->tts_fixedTupleDescriptor = tupleDesc != NULL;
 	slot->tts_tupleDescriptor = tupleDesc;
 	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;
 
 	if (tupleDesc != NULL)
 	{
@@ -236,6 +238,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
  *
@@ -353,7 +403,7 @@ ExecSetSlotDescriptor(TupleTableSlot *slot, /* slot to change */
  * --------------------------------
  */
 TupleTableSlot *
-ExecStoreTuple(HeapTuple tuple,
+ExecStoreTuple(void *tuple,
 			   TupleTableSlot *slot,
 			   Buffer buffer,
 			   bool shouldFree)
@@ -364,47 +414,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;
 }
 
@@ -431,31 +461,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;
@@ -480,25 +498,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.
@@ -577,7 +579,7 @@ ExecStoreAllNullTuple(TupleTableSlot *slot)
  *		however the "system columns" of the result will not be meaningful.
  * --------------------------------
  */
-HeapTuple
+TableTuple
 ExecCopySlotTuple(TupleTableSlot *slot)
 {
 	/*
@@ -586,20 +588,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);
 }
 
 /* --------------------------------
@@ -618,29 +607,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)
-	{
-		if (TTS_HAS_PHYSICAL_TUPLE(slot) &&
-			HeapTupleHeaderGetNatts(slot->tts_tuple->t_data)
-			< slot->tts_tupleDescriptor->natts)
-			return minimal_expand_tuple(slot->tts_tuple,
-										slot->tts_tupleDescriptor);
-		else
-			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);
 }
 
 /* --------------------------------
@@ -658,41 +637,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))
-	{
-		if (HeapTupleHeaderGetNatts(slot->tts_tuple->t_data) <
-			slot->tts_tupleDescriptor->natts)
-		{
-			MemoryContext oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
-
-			slot->tts_tuple = heap_expand_tuple(slot->tts_tuple,
-												slot->tts_tupleDescriptor);
-			slot->tts_shouldFree = true;
-			MemoryContextSwitchTo(oldContext);
-			return slot->tts_tuple;
-		}
-		else
-		{
-			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;
 }
 
 /* --------------------------------
@@ -712,6 +684,7 @@ MinimalTuple
 ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 {
 	MemoryContext oldContext;
+	MinimalTuple tup;
 
 	/*
 	 * sanity checks
@@ -719,11 +692,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.
@@ -733,18 +703,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;
 }
 
 /* --------------------------------
@@ -773,18 +736,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
@@ -792,12 +756,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.
@@ -807,18 +767,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
@@ -828,17 +780,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 a2a28b7ec2..869eebf274 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 cc038e3842..c3a8020c25 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -181,7 +181,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;
@@ -281,7 +281,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
@@ -322,7 +322,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -335,7 +335,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		newId = InvalidOid;
 	}
@@ -353,7 +353,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
@@ -702,7 +702,7 @@ ExecDelete(ModifyTableState *mtstate,
 		 */
 		if (slot->tts_isempty)
 			ExecStoreAllNullTuple(slot);
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
 	}
 	else
@@ -881,7 +881,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))
@@ -943,7 +943,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
@@ -962,7 +962,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW UPDATE Triggers */
@@ -976,7 +976,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
@@ -992,7 +992,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
@@ -1239,7 +1239,7 @@ lreplace:;
 					{
 						*tupleid = hufd.ctid;
 						slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
-						tuple = ExecMaterializeSlot(slot);
+						tuple = ExecHeapifySlot(slot);
 						goto lreplace;
 					}
 				}
@@ -1691,7 +1691,7 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
 	estate->es_result_relation_info = partrel;
 
 	/* Get the heap tuple out of the given slot. */
-	tuple = ExecMaterializeSlot(slot);
+	tuple = ExecHeapifySlot(slot);
 
 	/*
 	 * If we're capturing transition tuples, we might need to convert from the
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 fdace7eea2..3c6e1f9328 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -748,9 +748,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 cebaea097d..dae9cdfb8b 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
@@ -662,7 +675,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.
@@ -793,6 +806,8 @@ extern Datum fastgetattr(HeapTuple tup, int attnum, TupleDesc tupleDesc,
 
 
 /* prototypes for functions in common/heaptuple.c */
+extern Datum getmissingattr(TupleDesc tupleDesc,
+			   int attnum, bool *isnull);
 extern Size heap_compute_data_size(TupleDesc tupleDesc,
 					   Datum *values, bool *isnull);
 extern void heap_fill_tuple(TupleDesc tupleDesc,
diff --git a/src/include/access/tableam_common.h b/src/include/access/tableam_common.h
index 78b24d76c7..fd44cd0b94 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, bool noerror);
+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 b71ec8e069..bade19c398 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
@@ -116,30 +128,24 @@ typedef struct TupleTableSlot
 	bool		tts_isempty;	/* true = slot is empty */
 	bool		tts_shouldFree; /* should pfree tts_tuple? */
 	bool		tts_shouldFreeMin;	/* should pfree tts_mintuple? */
-#define FIELDNO_TUPLETABLESLOT_SLOW 4
-	bool		tts_slow;		/* saved state for slot_deform_tuple */
-#define FIELDNO_TUPLETABLESLOT_TUPLE 5
-	HeapTuple	tts_tuple;		/* physical tuple, or NULL if virtual */
-#define FIELDNO_TUPLETABLESLOT_TUPLEDESCRIPTOR 6
+#define FIELDNO_TUPLETABLESLOT_TUPLEDESCRIPTOR 5
 	TupleDesc	tts_tupleDescriptor;	/* slot's tuple descriptor */
 	MemoryContext tts_mcxt;		/* slot itself is in this context */
-	Buffer		tts_buffer;		/* tuple's buffer, or InvalidBuffer */
-#define FIELDNO_TUPLETABLESLOT_NVALID 9
+#define FIELDNO_TUPLETABLESLOT_NVALID 7
 	int			tts_nvalid;		/* # of valid values in tts_values */
-#define FIELDNO_TUPLETABLESLOT_VALUES 10
+#define FIELDNO_TUPLETABLESLOT_VALUES 8
 	Datum	   *tts_values;		/* current per-attribute values */
-#define FIELDNO_TUPLETABLESLOT_ISNULL 11
+#define FIELDNO_TUPLETABLESLOT_ISNULL 9
 	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 */
-#define FIELDNO_TUPLETABLESLOT_OFF 14
-	uint32		tts_off;		/* saved state for slot_deform_tuple */
+	ItemPointerData tts_tid;	/* XXX describe */
+	Oid		tts_tableOid;	/* XXX describe */
+	Oid		tts_tupleOid;	/* XXX describe */
+	uint32		tts_speculativeToken;	/* XXX describe */
 	bool		tts_fixedTupleDescriptor; /* descriptor can't be changed */
+	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?
  */
@@ -151,9 +157,10 @@ extern TupleTableSlot *MakeTupleTableSlot(TupleDesc desc);
 extern TupleTableSlot *ExecAllocTableSlot(List **tupleTable, TupleDesc desc);
 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);
@@ -163,12 +170,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.16.1.windows.4

0006-Tuple-Insert-API-is-added-to-table-AM.patchapplication/octet-stream; name=0006-Tuple-Insert-API-is-added-to-table-AM.patchDownload
From fa07b969857b53fad82eeb65f2b4ac00abc533f6 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:59:20 +1100
Subject: [PATCH 06/14] 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             | 142 +++++++-------
 src/backend/executor/execReplication.c      |  72 +++----
 src/backend/executor/nodeLockRows.c         |  47 +++--
 src/backend/executor/nodeModifyTable.c      | 237 ++++++++++-------------
 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             |   3 +-
 src/include/nodes/execnodes.h               |   8 +-
 29 files changed, 971 insertions(+), 386 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 eb8b48cf4e..463132069f 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -1065,6 +1065,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 3a595ef50c..28cf4a35dd 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -1920,13 +1920,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;
@@ -1960,7 +1960,6 @@ heap_fetch(Relation relation,
 			ReleaseBuffer(buffer);
 			*userbuf = InvalidBuffer;
 		}
-		tuple->t_data = NULL;
 		return false;
 	}
 
@@ -1982,13 +1981,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);
@@ -2339,7 +2338,6 @@ heap_get_latest_tid(Relation relation,
 	}							/* end of loop */
 }
 
-
 /*
  * UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends
  *
@@ -4733,7 +4731,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)
 	{
@@ -5026,7 +5024,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;
@@ -5798,9 +5796,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
@@ -6154,14 +6151,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);
@@ -6216,6 +6217,7 @@ heap_finish_speculative(Relation relation, HeapTuple tuple)
 	END_CRIT_SECTION();
 
 	UnlockReleaseBuffer(buffer);
+	slot->tts_speculativeToken = 0;
 }
 
 /*
@@ -6245,8 +6247,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;
@@ -6255,6 +6259,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);
@@ -6368,6 +6376,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 9bd7257123..f44a45fa11 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"
@@ -2678,8 +2679,6 @@ CopyFrom(CopyState cstate)
 
 			if (slot == NULL)	/* "do nothing" */
 				skip_tuple = true;
-			else				/* trigger might have changed tuple */
-				tuple = ExecHeapifySlot(slot);
 		}
 
 		if (!skip_tuple)
@@ -2742,19 +2741,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);
@@ -2825,7 +2816,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;
 }
@@ -2858,12 +2849,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);
 
 	/*
@@ -2879,10 +2870,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);
 		}
@@ -2899,8 +2889,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 69f69fe727..28b2f485b0 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"
@@ -478,19 +479,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 */
 
@@ -509,7 +513,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 83a881eff3..f4676399c1 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"
@@ -4732,7 +4733,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);
 
@@ -4756,7 +4758,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 3b3f8b97fb..c8019be5cd 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"
@@ -2578,17 +2579,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 *
@@ -3243,9 +3248,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)
 	{
@@ -3261,11 +3267,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:
@@ -3297,7 +3303,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;
@@ -3343,6 +3350,7 @@ ltrmark:;
 	{
 		Page		page;
 		ItemId		lp;
+		HeapTupleData tupledata;
 
 		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 
@@ -3361,17 +3369,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;
 }
 
@@ -4179,8 +4187,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;
@@ -4253,10 +4261,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
@@ -4270,10 +4277,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 9a107aba56..cb2cdf8132 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"
@@ -1921,7 +1922,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 	 */
 	if (resultRelInfo->ri_PartitionRoot)
 	{
-		HeapTuple	tuple = ExecFetchSlotTuple(slot);
+		TableTuple	tuple = ExecFetchSlotTuple(slot);
 		TupleDesc	old_tupdesc = RelationGetDescr(rel);
 		TupleConversionMap *map;
 
@@ -2001,7 +2002,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				 */
 				if (resultRelInfo->ri_PartitionRoot)
 				{
-					HeapTuple	tuple = ExecFetchSlotTuple(slot);
+					TableTuple tuple = ExecFetchSlotTuple(slot);
 					TupleConversionMap *map;
 
 					rel = resultRelInfo->ri_PartitionRoot;
@@ -2048,7 +2049,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;
 
@@ -2508,7 +2509,8 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
 			 ItemPointer tid, TransactionId priorXmax)
 {
 	TupleTableSlot *slot;
-	HeapTuple	copyTuple;
+	TableTuple copyTuple;
+	tuple_data	t_data;
 
 	Assert(rti > 0);
 
@@ -2525,7 +2527,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.
@@ -2556,7 +2560,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
@@ -2589,14 +2593,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
@@ -2604,12 +2608,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;
@@ -2623,7 +2627,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);
@@ -2645,7 +2649,8 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 				{
 					case LockWaitBlock:
 						XactLockTableWait(SnapshotDirty.xmax,
-										  relation, &tuple.t_self,
+										  relation,
+										  tid,
 										  XLTW_FetchUpdated);
 						break;
 					case LockWaitSkip:
@@ -2674,20 +2679,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);
 
@@ -2723,12 +2731,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;
@@ -2750,10 +2761,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;
 		}
@@ -2762,7 +2769,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;
@@ -2771,7 +2778,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);
@@ -2790,7 +2797,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);
@@ -2798,17 +2807,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;
 }
 
 /*
@@ -2854,7 +2865,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;
 
@@ -2873,7 +2884,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;
@@ -2901,7 +2912,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");
@@ -2932,8 +2943,6 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 
 		if (erm->markType == ROW_MARK_REFERENCE)
 		{
-			HeapTuple	copyTuple;
-
 			Assert(erm->relation != NULL);
 
 			/* fetch the tuple's ctid */
@@ -2957,11 +2966,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");
 
 				/*
@@ -2975,32 +2984,28 @@ 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");
 
-				if (HeapTupleHeaderGetNatts(tuple.t_data) <
+				if (HeapTupleHeaderGetNatts(((HeapTuple)tuple)->t_data) <
 					RelationGetDescr(erm->relation)->natts)
 				{
-					copyTuple = heap_expand_tuple(&tuple,
+					TableTuple copyTuple = tuple;
+
+					tuple = heap_expand_tuple(copyTuple,
 												  RelationGetDescr(erm->relation));
+					heap_freetuple(copyTuple);
 				}
-				else
-				{
-					/* 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 */
@@ -3010,19 +3015,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);
+
 		}
 	}
 }
@@ -3191,8 +3189,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 b39ccf7dc1..f258848991 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);
 		}
 
@@ -395,8 +400,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 c3a8020c25..9a014e69c5 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"
@@ -174,15 +176,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;
 
@@ -201,7 +201,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
 static void
 ExecCheckHeapTupleVisible(EState *estate,
 						  Relation rel,
-						  HeapTuple tuple,
+						  TableTuple tuple,
 						  Buffer buffer)
 {
 	if (!IsolationUsesXactSnapshot())
@@ -214,13 +214,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")));
@@ -236,19 +238,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);
 }
 
 /* ----------------------------------------------------------------
@@ -267,7 +270,7 @@ ExecInsert(ModifyTableState *mtstate,
 		   EState *estate,
 		   bool canSetTag)
 {
-	HeapTuple	tuple;
+	TableTuple tuple;
 	ResultRelInfo *resultRelInfo;
 	Relation	resultRelationDesc;
 	Oid			newId;
@@ -277,32 +280,32 @@ ExecInsert(ModifyTableState *mtstate,
 	ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
 	OnConflictAction onconflict = node->onConflictAction;
 
-	/*
-	 * 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
 	 */
 	resultRelInfo = estate->es_result_relation_info;
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
+
+	ExecMaterializeSlot(slot);
+
 	/*
-	 * 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.
+	 * 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.
@@ -320,9 +323,6 @@ ExecInsert(ModifyTableState *mtstate,
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
-
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -334,9 +334,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)
@@ -352,14 +349,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;
 	}
@@ -381,7 +376,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 WITH CHECK policies.
@@ -420,7 +416,6 @@ ExecInsert(ModifyTableState *mtstate,
 			/* Perform a speculative insertion. */
 			uint32		specToken;
 			ItemPointerData conflictTid;
-			bool		specConflict;
 			List	   *arbiterIndexes;
 
 			arbiterIndexes = resultRelInfo->ri_onConflictArbiterIndexes;
@@ -438,7 +433,7 @@ ExecInsert(ModifyTableState *mtstate,
 			 * speculatively.
 			 */
 	vlock:
-			specConflict = false;
+			slot->tts_specConflict = false;
 			if (!ExecCheckIndexConstraints(slot, estate, &conflictTid,
 										   arbiterIndexes))
 			{
@@ -484,24 +479,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
@@ -517,7 +505,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;
@@ -529,19 +517,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,
-													   NIL);
+			newId = table_insert(resultRelationDesc, slot,
+								   estate->es_output_cid,
+								   0, NULL,
+								   ExecInsertIndexTuples,
+								   estate,
+								   NIL,
+								   &recheckIndexes);
 		}
 	}
 
@@ -549,7 +532,7 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		(estate->es_processed)++;
 		estate->es_lastoid = newId;
-		setLastTid(&(tuple->t_self));
+		setLastTid(&(slot->tts_tid));
 	}
 
 	/*
@@ -562,6 +545,7 @@ ExecInsert(ModifyTableState *mtstate,
 	if (mtstate->operation == CMD_UPDATE && mtstate->mt_transition_capture
 		&& mtstate->mt_transition_capture->tcs_update_new_table)
 	{
+		tuple = ExecHeapifySlot(slot);
 		ExecARUpdateTriggers(estate, resultRelInfo, NULL,
 							 NULL,
 							 tuple,
@@ -576,7 +560,7 @@ ExecInsert(ModifyTableState *mtstate,
 	}
 
 	/* AFTER ROW INSERT Triggers */
-	ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes,
+	ExecARInsertTriggers(estate, resultRelInfo, slot, recheckIndexes,
 						 ar_insert_trig_tcs);
 
 	list_free(recheckIndexes);
@@ -624,7 +608,7 @@ ExecInsert(ModifyTableState *mtstate,
 static TupleTableSlot *
 ExecDelete(ModifyTableState *mtstate,
 		   ItemPointer tupleid,
-		   HeapTuple oldtuple,
+		   TableTuple oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate,
@@ -675,8 +659,6 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
-		HeapTuple	tuple;
-
 		/*
 		 * delete from foreign table: let the FDW do it
 		 *
@@ -702,8 +684,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
 	{
@@ -717,11 +701,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:
@@ -845,7 +830,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)
@@ -859,20 +844,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);
@@ -881,7 +865,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))
@@ -918,14 +902,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;
@@ -991,14 +975,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
 	{
@@ -1009,7 +993,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
@@ -1175,11 +1159,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:
@@ -1250,26 +1237,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)
@@ -1327,11 +1294,12 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	ExprContext *econtext = mtstate->ps.ps_ExprContext;
 	Relation	relation = resultRelInfo->ri_RelationDesc;
 	ExprState  *onConflictSetWhere = resultRelInfo->ri_onConflict->oc_WhereClause;
-	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);
@@ -1342,10 +1310,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:
@@ -1370,7 +1336,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"),
@@ -1401,7 +1368,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:
@@ -1429,10 +1398,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
@@ -1447,7 +1416,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 */
 	}
@@ -1487,12 +1458,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;
 }
 
@@ -1898,7 +1871,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 e207b1ffb5..3d4e8d0093 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 41d540b46e..bb8a683b44 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 dae9cdfb8b..21446d3393 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -822,6 +822,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 fd44cd0b94..e5cc461bd8 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 a5b8610fa2..2fe7ed33a5 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -191,7 +191,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 45a077a949..fd706f8fee 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -195,16 +195,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);
@@ -541,9 +541,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 bade19c398..3584ea94a5 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -137,11 +137,12 @@ typedef struct TupleTableSlot
 	Datum	   *tts_values;		/* current per-attribute values */
 #define FIELDNO_TUPLETABLESLOT_ISNULL 9
 	bool	   *tts_isnull;		/* current per-attribute isnull flags */
+	bool		tts_fixedTupleDescriptor; /* descriptor can't be changed */
 	ItemPointerData tts_tid;	/* XXX describe */
 	Oid		tts_tableOid;	/* XXX describe */
 	Oid		tts_tupleOid;	/* XXX describe */
 	uint32		tts_speculativeToken;	/* XXX describe */
-	bool		tts_fixedTupleDescriptor; /* descriptor can't be changed */
+	bool		tts_specConflict;	/* XXX describe */
 	struct SlotTableAmRoutine *tts_slottableam; /* table AM */
 	void	   *tts_storage;	/* table AM's opaque space */
 } TupleTableSlot;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 6070a42b6f..cbf6b0e6cb 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -546,7 +546,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 */
 
@@ -2084,7 +2084,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
@@ -2094,7 +2094,7 @@ typedef struct SharedHashInfo
 {
 	int			num_workers;
 	HashInstrumentation hinstrument[FLEXIBLE_ARRAY_MEMBER];
-} SharedHashInfo;
+}			SharedHashInfo;
 
 /* ----------------
  *	 HashState information
@@ -2155,7 +2155,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.16.1.windows.4

0007-Scan-functions-are-added-to-table-AM.patchapplication/octet-stream; name=0007-Scan-functions-are-added-to-table-AM.patchDownload
From d06c75ac462245b0c9df01e948a3353363174793 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:59:20 +1100
Subject: [PATCH 07/14] 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           | 223 ++++++++++-----------------
 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/nbtree/nbtsort.c        |   2 +-
 src/backend/access/table/tableam.c         | 233 +++++++++++++++++++++++++++++
 src/backend/bootstrap/bootstrap.c          |  25 ++--
 src/backend/catalog/aclchk.c               |  13 +-
 src/backend/catalog/index.c                |  49 +++---
 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 +-
 56 files changed, 717 insertions(+), 452 deletions(-)

diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index b0ed27e883..6d47a446ea 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -125,7 +125,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
 			aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
 						   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 17b2fd9f26..1384f6ec9e 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 28cf4a35dd..269746f978 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -83,17 +83,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,
@@ -1394,87 +1383,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;
 
@@ -1544,9 +1462,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
 	 */
@@ -1557,27 +1482,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);
+	}
 }
 
 /* ----------------
@@ -1675,36 +1594,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);
-
-	if (!parallel_scan->phs_snapshot_any)
-	{
-		/* Snapshot was serialized -- restore it */
-		snapshot = RestoreSnapshot(parallel_scan->phs_snapshot_data);
-		RegisterSnapshot(snapshot);
-	}
-	else
-	{
-		/* SnapshotAny passed by caller (not serialized) */
-		snapshot = SnapshotAny;
-	}
-
-	return heap_beginscan_internal(relation, snapshot, 0, NULL, parallel_scan,
-								   true, true, true, false, false,
-								   !parallel_scan->phs_snapshot_any);
-}
-
 /* ----------------
  *		heap_parallelscan_startblock_init - find and set the scan's startblock
  *
@@ -1849,8 +1738,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 */
@@ -1880,6 +1768,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 91247f0fa5..2f737a30ed 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"
@@ -606,12 +607,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 e85abcfd72..10d098993a 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"
@@ -416,8 +417,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;
 
@@ -470,7 +471,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/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c
index f17a9cee36..0a5fd569b3 100644
--- a/src/backend/access/nbtree/nbtsort.c
+++ b/src/backend/access/nbtree/nbtsort.c
@@ -1631,7 +1631,7 @@ _bt_parallel_scan_and_sort(BTSpool *btspool, BTSpool *btspool2,
 	/* Join parallel scan */
 	indexInfo = BuildIndexInfo(btspool->index);
 	indexInfo->ii_Concurrent = btshared->isconcurrent;
-	scan = heap_beginscan_parallel(btspool->heap, &btshared->heapdesc);
+	scan = table_beginscan_parallel(btspool->heap, &btshared->heapdesc);
 	reltuples = IndexBuildHeapScan(btspool->heap, btspool->index, indexInfo,
 								   true, _bt_build_callback,
 								   (void *) &buildstate, scan);
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index eaeb888b55..142d5a18f9 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,184 @@ 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);
+
+	if (!parallel_scan->phs_snapshot_any)
+	{
+		/* Snapshot was serialized -- restore it */
+		snapshot = RestoreSnapshot(parallel_scan->phs_snapshot_data);
+		RegisterSnapshot(snapshot);
+	}
+	else
+	{
+		/* SnapshotAny passed by caller (not serialized) */
+		snapshot = SnapshotAny;
+	}
+
+	return relation->rd_tableamroutine->scan_begin(relation, snapshot, 0, NULL, parallel_scan,
+												true, true, true, false, false, !parallel_scan->phs_snapshot_any);
+}
+
+/*
+ * 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 +267,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 28ff2f0979..7fdcd31f47 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 83000575ce..c4ae823565 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"
@@ -843,14 +844,14 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
 									CharGetDatum(PROKIND_PROCEDURE));
 
 					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;
@@ -888,14 +889,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 ee27a9ff71..90d6d2e9ba 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -27,6 +27,7 @@
 #include "access/multixact.h"
 #include "access/relscan.h"
 #include "access/reloptions.h"
+#include "access/tableam.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
 #include "access/visibilitymap.h"
@@ -2101,10 +2102,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
 	{
@@ -2500,7 +2501,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		else
 			snapshot = SnapshotAny;
 
-		scan = heap_beginscan_strat(heapRelation,	/* relation */
+		scan = table_beginscan_strat(heapRelation,	/* relation */
 									snapshot,	/* snapshot */
 									0,	/* number of keys */
 									NULL,	/* scan key */
@@ -2536,7 +2537,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 
 	/* 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 */
@@ -2549,7 +2550,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;
 
@@ -2892,7 +2893,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		}
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 
 	/* we can now forget our snapshot, if set and registered by us */
 	if (need_unregister_snapshot)
@@ -2963,14 +2964,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();
 
@@ -3006,7 +3007,7 @@ IndexCheckExclusion(Relation heapRelation,
 								   estate, true);
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	UnregisterSnapshot(snapshot);
 
 	ExecDropSingleTupleTableSlot(slot);
@@ -3283,17 +3284,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;
@@ -3452,7 +3453,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 39ee773d93..6e0da55521 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"
@@ -1345,7 +1346,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);
 
 		/*
@@ -1354,7 +1355,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;
@@ -1370,7 +1371,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 ba18258ebb..1969f0c831 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"
@@ -333,9 +334,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);
@@ -344,7 +345,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 c7fed4131f..91ed6b077e 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -929,7 +929,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;
 	}
 
@@ -979,7 +979,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;
 
@@ -1065,7 +1065,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
@@ -1712,8 +1712,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);
 
@@ -1733,7 +1733,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 f44a45fa11..55beb31f7f 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2033,10 +2033,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();
 
@@ -2048,7 +2048,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 5342f217c0..1ccc123b61 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 0185970794..2e61fe533c 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"
@@ -2285,8 +2286,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);
@@ -2332,7 +2333,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 f4676399c1..7d2e01fbb0 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4620,7 +4620,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
@@ -4628,7 +4628,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)
 			{
@@ -4742,7 +4742,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 
 		MemoryContextSwitchTo(oldCxt);
-		heap_endscan(scan);
+		table_endscan(scan);
 		UnregisterSnapshot(snapshot);
 
 		ExecDropSingleTupleTableSlot(oldslot);
@@ -5148,9 +5148,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,
@@ -5162,7 +5162,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;
@@ -8328,7 +8328,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
@@ -8336,7 +8336,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);
 
@@ -8351,7 +8351,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	}
 
 	MemoryContextSwitchTo(oldcxt);
-	heap_endscan(scan);
+	table_endscan(scan);
 	UnregisterSnapshot(snapshot);
 	ExecDropSingleTupleTableSlot(slot);
 	FreeExecutorState(estate);
@@ -8406,9 +8406,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;
@@ -8437,7 +8437,7 @@ validateForeignKeyConstraint(char *conname,
 		RI_FKey_check_ins(&fcinfo);
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	UnregisterSnapshot(snapshot);
 }
 
@@ -10958,8 +10958,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;
@@ -11019,7 +11019,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 5c450caa4e..b743dedf33 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 2fdcb7f3fd..237aa1238a 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"
@@ -2386,8 +2387,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;
 
@@ -2416,7 +2417,7 @@ AlterDomainNotNull(List *names, bool notNull)
 					}
 				}
 			}
-			heap_endscan(scan);
+			table_endscan(scan);
 			UnregisterSnapshot(snapshot);
 
 			/* Close each rel after processing, but keep lock */
@@ -2782,8 +2783,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;
 
@@ -2826,7 +2827,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 a1782c2874..0b08657659 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;
@@ -1190,9 +1191,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;
@@ -1229,7 +1230,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 d7ad51de36..7a3e269367 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -720,7 +720,7 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 Datum
 ExecFetchSlotTupleDatum(TupleTableSlot *slot)
 {
-	HeapTuple	tup;
+	TableTuple tup;
 	TupleDesc	tupdesc;
 
 	/* Fetch slot's contents in regular-physical-tuple form */
@@ -804,7 +804,7 @@ ExecHeapifySlot(TupleTableSlot *slot)
 TupleTableSlot *
 ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
 {
-	HeapTuple	newTuple;
+	TableTuple newTuple;
 	MemoryContext oldContext;
 
 	/*
@@ -1145,7 +1145,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;
@@ -1153,7 +1153,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 23545896d4..b19abe6783 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)
 {
@@ -597,7 +597,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 1b1334006f..bcb09bc00e 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -2517,7 +2517,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,
@@ -2642,7 +2642,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 bdb82db149..45c9baf6c8 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"
@@ -431,8 +432,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);
 		}
 	}
@@ -742,7 +743,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)
@@ -832,7 +833,7 @@ ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 	/*
 	 * close heap scan
 	 */
-	heap_endscan(scanDesc);
+	table_endscan(scanDesc);
 
 	/*
 	 * close the heap relation.
@@ -963,10 +964,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);
 
 	/*
 	 * all done.
@@ -1114,5 +1115,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 869eebf274..a2e6470126 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 eaf7d2d563..d6b5540a7a 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);
 
 
@@ -245,7 +245,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)
 	{
@@ -289,7 +289,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;
@@ -297,7 +297,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 83221cdbae..237498c3bc 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);
@@ -399,7 +399,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] =
@@ -616,7 +616,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
@@ -691,12 +691,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 ddc0ae9061..8aaf0123de 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 01c9de88f4..a62b916b00 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;
 
 	/*
@@ -184,7 +184,7 @@ IndexNextWithReorder(IndexScanState *node)
 	EState	   *estate;
 	ExprContext *econtext;
 	IndexScanDesc scandesc;
-	HeapTuple	tuple;
+	TableTuple tuple;
 	TupleTableSlot *slot;
 	ReorderTuple *topmost = NULL;
 	bool		was_exact;
@@ -478,7 +478,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;
@@ -511,10 +511,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 9d7872b439..0a55c3b6a8 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -28,10 +28,12 @@
 
 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
  * ----------------------------------------------------------------
@@ -46,7 +48,7 @@ static bool SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset,
 static TupleTableSlot *
 SampleNext(SampleScanState *node)
 {
-	HeapTuple	tuple;
+	TableTuple tuple;
 	TupleTableSlot *slot;
 
 	/*
@@ -222,7 +224,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.
@@ -327,19 +329,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);
@@ -354,7 +356,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;
@@ -532,7 +534,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 9db368922a..758dbeb9c7 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"
@@ -48,8 +49,7 @@ static TupleTableSlot *SeqNext(SeqScanState *node);
 static TupleTableSlot *
 SeqNext(SeqScanState *node)
 {
-	HeapTuple	tuple;
-	HeapScanDesc scandesc;
+	TableScanDesc scandesc;
 	EState	   *estate;
 	ScanDirection direction;
 	TupleTableSlot *slot;
@@ -68,35 +68,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);
 }
 
 /*
@@ -203,7 +184,7 @@ void
 ExecEndSeqScan(SeqScanState *node)
 {
 	Relation	relation;
-	HeapScanDesc scanDesc;
+	TableScanDesc scanDesc;
 
 	/*
 	 * get information from node
@@ -226,7 +207,7 @@ ExecEndSeqScan(SeqScanState *node)
 	 * close heap scan
 	 */
 	if (scanDesc != NULL)
-		heap_endscan(scanDesc);
+		table_endscan(scanDesc);
 
 	/*
 	 * close the heap relation.
@@ -248,13 +229,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);
 }
@@ -301,7 +282,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);
 }
 
 /* ----------------------------------------------------------------
@@ -333,5 +314,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 fe5369a0c7..a9b9f6111e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -2590,7 +2590,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;
@@ -2673,7 +2673,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 08f6f67a15..ff510d4ab2 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -721,11 +721,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)
 	{
@@ -749,7 +749,7 @@ SPI_copytuple(HeapTuple tuple)
 }
 
 HeapTupleHeader
-SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc)
+SPI_returntuple(TableTuple tuple, TupleDesc tupdesc)
 {
 	MemoryContext oldcxt;
 	HeapTupleHeader dtup;
@@ -780,7 +780,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)
 {
@@ -948,7 +948,7 @@ char *
 SPI_gettype(TupleDesc tupdesc, int fnumber)
 {
 	Oid			typoid;
-	HeapTuple	typeTuple;
+	TableTuple typeTuple;
 	char	   *result;
 
 	SPI_result = 0;
@@ -1056,7 +1056,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);
@@ -1777,7 +1777,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);
@@ -1808,8 +1808,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 c4bc09ea81..30f7811f85 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1896,9 +1896,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;
@@ -1925,7 +1925,7 @@ get_database_list(void)
 		MemoryContextSwitchTo(oldcxt);
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	CommitTransactionCommand();
@@ -2056,13 +2056,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;
@@ -2148,7 +2148,7 @@ do_autovacuum(void)
 		}
 	}
 
-	heap_endscan(relScan);
+	table_endscan(relScan);
 
 	/* second pass: check TOAST tables */
 	ScanKeyInit(&key,
@@ -2156,8 +2156,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;
@@ -2203,7 +2203,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 96ba216387..be7350bdd2 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 679be605f1..9cf066b0f7 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 d8f45b3c43..7f1dc64e0e 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"
@@ -1218,10 +1219,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 e5cc461bd8..36b72e9767 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 a309809ba8..fbf3c898f7 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 e5bdaecc4e..e75ac2746e 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;
@@ -120,10 +120,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);
@@ -136,7 +136,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 01aa208c5e..0f3e86d81f 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -238,7 +238,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.16.1.windows.4

0008-Remove-HeapScanDesc-usage-outside-heap.patchapplication/octet-stream; name=0008-Remove-HeapScanDesc-usage-outside-heap.patchDownload
From b54cb3ece72850e8176a02f9a0d59ad8864900ef Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:59:20 +1100
Subject: [PATCH 08/14] 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/nbtree/nbtsort.c        |   2 +-
 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                |  10 +-
 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/catalog/index.h                |   4 +-
 src/include/nodes/execnodes.h              |   4 +-
 42 files changed, 522 insertions(+), 403 deletions(-)

diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index 6d47a446ea..cba2e63f13 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 1384f6ec9e..6a625ad20a 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 269746f978..daf74bed93 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -224,9 +224,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
@@ -240,8 +240,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;
@@ -252,20 +252,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)
 	{
@@ -274,25 +274,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 */
 
@@ -300,7 +300,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
@@ -308,7 +308,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);
 }
 
 /*
@@ -318,16 +318,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;
 }
 
 /*
@@ -338,8 +341,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;
@@ -349,13 +353,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;
 	}
 
 	/*
@@ -366,20 +370,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
@@ -389,7 +393,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;
 
@@ -424,7 +428,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);
@@ -432,20 +436,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;
 }
 
 /* ----------------
@@ -478,7 +482,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;
@@ -493,14 +497,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;
 			}
@@ -513,29 +517,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 */
 
@@ -546,14 +550,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;
 			}
@@ -564,30 +568,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
 		{
@@ -603,20 +607,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));
@@ -647,21 +651,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;
 				}
 			}
@@ -686,17 +690,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)
@@ -707,10 +711,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
@@ -724,8 +728,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);
 		}
 
 		/*
@@ -733,21 +737,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)
@@ -798,14 +802,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;
 			}
@@ -818,28 +822,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;
@@ -849,14 +853,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;
 			}
@@ -867,33 +871,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 */
 
@@ -904,20 +908,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));
@@ -926,8 +930,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;
 	}
@@ -940,7 +944,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));
 
@@ -951,7 +955,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.
@@ -960,19 +964,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;
 				}
 			}
@@ -991,7 +995,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
@@ -999,10 +1003,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)
@@ -1013,10 +1017,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
@@ -1030,8 +1034,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);
 		}
 
 		/*
@@ -1039,21 +1043,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;
@@ -1383,7 +1387,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,
@@ -1410,12 +1414,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;
@@ -1424,7 +1428,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
@@ -1448,13 +1452,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;
 }
 
 /* ----------------
@@ -1462,21 +1466,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
@@ -1507,29 +1513,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);
 }
@@ -1634,7 +1642,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;
 		}
 	}
@@ -1676,10 +1684,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.
@@ -1688,12 +1696,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;
@@ -1706,12 +1714,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;
 }
 
@@ -1739,17 +1749,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)
 	{
@@ -1763,7 +1775,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));
 }
@@ -1771,7 +1783,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 \
@@ -1783,7 +1795,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;
 
@@ -1791,11 +1803,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)
 	{
@@ -1810,7 +1822,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/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c
index 0a5fd569b3..8637a16ce5 100644
--- a/src/backend/access/nbtree/nbtsort.c
+++ b/src/backend/access/nbtree/nbtsort.c
@@ -1578,7 +1578,7 @@ _bt_parallel_scan_and_sort(BTSpool *btspool, BTSpool *btspool2,
 {
 	SortCoordinate coordinate;
 	BTBuildState buildstate;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	double		reltuples;
 	IndexInfo  *indexInfo;
 
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index 142d5a18f9..4991f62e1e 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;
@@ -79,6 +79,25 @@ table_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan)
 												true, true, true, false, false, !parallel_scan->phs_snapshot_any);
 }
 
+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
  *
@@ -86,7 +105,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);
 }
@@ -105,18 +124,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)
 {
@@ -124,7 +143,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);
@@ -134,7 +153,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)
@@ -144,7 +163,7 @@ table_beginscan_strat(Relation relation, Snapshot snapshot,
 												false, false, false);
 }
 
-HeapScanDesc
+TableScanDesc
 table_beginscan_bm(Relation relation, Snapshot snapshot,
 					 int nkeys, ScanKey key)
 {
@@ -152,7 +171,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)
@@ -167,7 +186,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);
@@ -183,7 +202,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,
@@ -198,7 +217,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);
 }
@@ -211,23 +230,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 7fdcd31f47..c442ad88a9 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 c4ae823565..b46ad74461 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -822,7 +822,7 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
 					ScanKeyData key[2];
 					int			keycount;
 					Relation	rel;
-					HeapScanDesc scan;
+					TableScanDesc scan;
 					HeapTuple	tuple;
 
 					keycount = 0;
@@ -876,7 +876,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 90d6d2e9ba..5d7336d77f 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2094,7 +2094,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],
@@ -2392,7 +2392,7 @@ IndexBuildHeapScan(Relation heapRelation,
 				   bool allow_sync,
 				   IndexBuildCallback callback,
 				   void *callback_state,
-				   HeapScanDesc scan)
+				   TableScanDesc scan)
 {
 	return IndexBuildHeapRangeScan(heapRelation, indexRelation,
 								   indexInfo, allow_sync,
@@ -2421,7 +2421,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 						BlockNumber numblocks,
 						IndexBuildCallback callback,
 						void *callback_state,
-						HeapScanDesc scan)
+						TableScanDesc scan)
 {
 	bool		is_system_catalog;
 	bool		checking_uniqueness;
@@ -2928,7 +2928,7 @@ IndexCheckExclusion(Relation heapRelation,
 					Relation indexRelation,
 					IndexInfo *indexInfo)
 {
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
@@ -3242,7 +3242,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 6e0da55521..a4a2215281 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1287,7 +1287,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 1969f0c831..d8aeab4678 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -323,7 +323,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 91ed6b077e..c0b88bb01f 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -763,7 +763,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;
@@ -1693,7 +1693,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 55beb31f7f..ab27d28588 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2027,7 +2027,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 1ccc123b61..b9f33f1e29 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 2e61fe533c..ca2c86b732 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2212,7 +2212,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 7d2e01fbb0..5ebb183731 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4561,7 +4561,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;
@@ -5137,7 +5137,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;
 
@@ -8287,7 +8287,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	Expr	   *origexpr;
 	ExprState  *exprstate;
 	TupleDesc	tupdesc;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tuple;
 	ExprContext *econtext;
 	MemoryContext oldcxt;
@@ -8370,7 +8370,7 @@ validateForeignKeyConstraint(char *conname,
 							 Oid pkindOid,
 							 Oid constraintOid)
 {
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tuple;
 	Trigger		trig;
 	Snapshot	snapshot;
@@ -10893,7 +10893,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 b743dedf33..f9c045efc6 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 237aa1238a..261423f467 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2381,7 +2381,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;
 
@@ -2777,7 +2777,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 0b08657659..d5872b0f68 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);
@@ -1160,7 +1160,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 45c9baf6c8..7e29030dfd 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);
@@ -381,8 +373,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;
@@ -391,7 +385,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,
@@ -434,7 +428,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
@@ -450,23 +444,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);
 		}
 	}
@@ -474,7 +466,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 
 	Assert(ntup <= MaxHeapTuplesPerPage);
-	scan->rs_ntuples = ntup;
+	pagescan->rs_ntuples = ntup;
 }
 
 /*
@@ -600,7 +592,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;
@@ -788,7 +780,7 @@ void
 ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 {
 	Relation	relation;
-	HeapScanDesc scanDesc;
+	TableScanDesc scanDesc;
 
 	/*
 	 * extract information from the node
@@ -969,6 +961,8 @@ ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags)
 															0,
 															NULL);
 
+	scanstate->pagescan = tableam_get_heappagescandesc(scanstate->ss.ss_currentScanDesc);
+
 	/*
 	 * all done.
 	 */
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index 0a55c3b6a8..be207765b6 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -30,9 +30,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
@@ -335,6 +334,7 @@ tablesample_init(SampleScanState *scanstate)
 									   scanstate->use_bulkread,
 									   allow_sync,
 									   scanstate->use_pagemode);
+		scanstate->pagescan = tableam_get_heappagescandesc(scanstate->ss.ss_currentScanDesc);
 	}
 	else
 	{
@@ -360,10 +360,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;
@@ -374,10 +375,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)
@@ -385,13 +385,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;
 	}
@@ -434,14 +433,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)
@@ -472,14 +469,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;
 
 			/*
@@ -492,10 +489,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);
 		}
 
 		/*
@@ -507,12 +504,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 */
@@ -527,16 +523,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,
@@ -548,12 +547,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 758dbeb9c7..b2b0a30343 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -295,9 +295,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 0231f8bf7c..564faf12eb 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"
@@ -263,7 +264,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 30f7811f85..d41cc5568c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1878,7 +1878,7 @@ get_database_list(void)
 {
 	List	   *dblist = NIL;
 	Relation	rel;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tup;
 	MemoryContext resultcxt;
 
@@ -1944,7 +1944,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 be7350bdd2..074a3ce70d 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 9cf066b0f7..796152f442 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 7f1dc64e0e..bf8c3e548d 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -1214,7 +1214,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 18c7dedd5d..3d29ea4ee4 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"
@@ -43,40 +44,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;
 
 /*
@@ -150,12 +165,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 36b72e9767..e5cc461bd8 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/catalog/index.h b/src/include/catalog/index.h
index f20c5f789b..6cab64df10 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -116,7 +116,7 @@ extern double IndexBuildHeapScan(Relation heapRelation,
 				   bool allow_sync,
 				   IndexBuildCallback callback,
 				   void *callback_state,
-				   HeapScanDesc scan);
+				   TableScanDesc scan);
 extern double IndexBuildHeapRangeScan(Relation heapRelation,
 						Relation indexRelation,
 						IndexInfo *indexInfo,
@@ -126,7 +126,7 @@ extern double IndexBuildHeapRangeScan(Relation heapRelation,
 						BlockNumber end_blockno,
 						IndexBuildCallback callback,
 						void *callback_state,
-						HeapScanDesc scan);
+						TableScanDesc scan);
 
 extern void validate_index(Oid heapId, Oid indexId, Snapshot snapshot);
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cbf6b0e6cb..f9471df231 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1166,7 +1166,7 @@ typedef struct ScanState
 {
 	PlanState	ps;				/* its first field is NodeTag */
 	Relation	ss_currentRelation;
-	HeapScanDesc ss_currentScanDesc;
+	TableScanDesc ss_currentScanDesc;
 	TupleTableSlot *ss_ScanTupleSlot;
 } ScanState;
 
@@ -1187,6 +1187,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 */
@@ -1415,6 +1416,7 @@ typedef struct ParallelBitmapHeapState
 typedef struct BitmapHeapScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
+	HeapPageScanDesc pagescan;
 	ExprState  *bitmapqualorig;
 	TIDBitmap  *tbm;
 	TBMIterator *tbmiterator;
-- 
2.16.1.windows.4

0009-BulkInsertState-is-added-into-table-AM.patchapplication/octet-stream; name=0009-BulkInsertState-is-added-into-table-AM.patchDownload
From 95523a7153f5822fe926800f0219897b36007b96 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:59:20 +1100
Subject: [PATCH 09/14] 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 daf74bed93..2b3e4f428c 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2331,7 +2331,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 4991f62e1e..25db521dca 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -389,3 +389,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 ab27d28588..39cb9c0c08 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2292,7 +2292,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;
@@ -2517,7 +2517,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 */
@@ -2597,7 +2597,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;
 			}
 
@@ -2778,7 +2778,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 28b2f485b0..e17e2cd808 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -53,7 +53,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;
@@ -466,7 +466,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);
@@ -509,7 +509,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 5ebb183731..fd197d9042 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4453,7 +4453,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	ListCell   *l;
 	EState	   *estate;
 	CommandId	mycid;
-	BulkInsertState bistate;
+	void       *bistate;
 	int			hi_options;
 	ExprState  *partqualstate = NULL;
 
@@ -4479,7 +4479,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())
@@ -4754,7 +4754,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 e5cc461bd8..3c2ce82df3 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.16.1.windows.4

0010-table-rewrite-functionality.patchapplication/octet-stream; name=0010-table-rewrite-functionality.patchDownload
From 711428b9865c44ce6eeb9640dac07c2a977e283c Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:59:20 +1100
Subject: [PATCH 10/14] 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 25db521dca..f1ba658f17 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -412,3 +412,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 c0b88bb01f..556f745012 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);
 
 
 /*---------------------------------------------------------------------------
@@ -893,7 +892,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);
 
 	/*
@@ -1043,7 +1042,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;
@@ -1057,9 +1056,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)
@@ -1086,16 +1084,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;
@@ -1759,10 +1756,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;
 
@@ -1778,11 +1776,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 3c2ce82df3..b8fdfddb31 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.16.1.windows.4

0011-Improve-tuple-locking-interface.patchapplication/octet-stream; name=0011-Improve-tuple-locking-interface.patchDownload
From dbd654c9a2dd3dcb5f79f74f6838e5f5a1dfb6f4 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:59:20 +1100
Subject: [PATCH 11/14] 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 2b3e4f428c..3ac3b8f0d3 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3207,6 +3207,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;
@@ -3220,6 +3221,8 @@ l1:
 			UnlockTupleTuplock(relation, &(tp.t_self), LockTupleExclusive);
 		if (vmbuffer != InvalidBuffer)
 			ReleaseBuffer(vmbuffer);
+		if (result == HeapTupleUpdated && ItemPointerEquals(tid, &hufd->ctid))
+			result = HeapTupleDeleted;
 		return result;
 	}
 
@@ -3429,6 +3432,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;
@@ -3849,6 +3856,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;
@@ -3868,6 +3876,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;
 	}
 
@@ -4571,6 +4581,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;
@@ -4623,6 +4637,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
@@ -4691,7 +4706,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;
@@ -4971,7 +4986,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;
@@ -5131,6 +5146,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;
 	}
@@ -5139,7 +5156,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);
@@ -6026,6 +6043,10 @@ next:
 	result = HeapTupleMayBeUpdated;
 
 out_locked:
+
+	if (result == HeapTupleUpdated && ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid))
+		result = HeapTupleDeleted;
+
 	UnlockReleaseBuffer(buf);
 
 out_unlocked:
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 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 f1ba658f17..6e6059e9ed 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 c8019be5cd..9a3f19a79a 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3250,8 +3250,6 @@ GetTupleForTrigger(EState *estate,
 	Relation	relation = relinfo->ri_RelationDesc;
 	TableTuple tuple;
 	HeapTuple	result;
-	Buffer		buffer;
-	tuple_data	t_data;
 
 	if (newSlot != NULL)
 	{
@@ -3266,11 +3264,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)
 		{
@@ -3291,63 +3289,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;
@@ -3377,9 +3367,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 cb2cdf8132..39de3bb112 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -2490,9 +2490,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)
@@ -2505,32 +2503,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.
 	 */
@@ -2540,7 +2518,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
@@ -2572,256 +2550,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 f258848991..f5417c1db0 100644
--- a/src/backend/executor/nodeLockRows.c
+++ b/src/backend/executor/nodeLockRows.c
@@ -79,13 +79,11 @@ lnext:
 		Datum		datum;
 		bool		isNull;
 		TableTuple tuple;
-		Buffer		buffer;
 		HeapUpdateFailureData hufd;
 		LockTupleMode lockmode;
 		HTSU_Result test;
 		TableTuple copyTuple;
 		ItemPointerData tid;
-		tuple_data	t_data;
 
 		/* clear any leftover test tuple for this rel */
 		testTuple = (TableTuple) (&(node->lr_curtuples[erm->rti - 1]));
@@ -183,12 +181,12 @@ lnext:
 				break;
 		}
 
-		test = table_lock_tuple(erm->relation, &tid, &tuple,
-								  estate->es_output_cid,
-								  lockmode, erm->waitPolicy, true,
-								  &buffer, &hufd);
-		if (BufferIsValid(buffer))
-			ReleaseBuffer(buffer);
+		test = table_lock_tuple(erm->relation, &tid, estate->es_snapshot,
+								  &tuple, estate->es_output_cid,
+								  lockmode, erm->waitPolicy,
+								  (IsolationUsesXactSnapshot() ? 0 : TUPLE_LOCK_FLAG_FIND_LAST_VERSION)
+								  | TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS,
+								  &hufd);
 
 		switch (test)
 		{
@@ -216,6 +214,16 @@ lnext:
 
 			case HeapTupleMayBeUpdated:
 				/* got the lock successfully */
+				if (hufd.traversed)
+				{
+					/* Save locked tuple for EvalPlanQual testing below */
+					*testTuple = tuple;
+
+					/* Remember we need to do EPQ testing */
+					epq_needed = true;
+
+					/* Continue loop until we have all target tuples */
+				}
 				break;
 
 			case HeapTupleUpdated:
@@ -223,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 9a014e69c5..2b84654248 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -211,7 +211,8 @@ ExecCheckHeapTupleVisible(EState *estate,
 	 * We need buffer pin and lock to call HeapTupleSatisfiesVisibility.
 	 * Caller should be holding pin, but not lock.
 	 */
-	LockBuffer(buffer, BUFFER_LOCK_SHARE);
+	if (BufferIsValid(buffer))
+		LockBuffer(buffer, BUFFER_LOCK_SHARE);
 	if (!HeapTupleSatisfiesVisibility(rel->rd_tableamroutine, tuple, estate->es_snapshot, buffer))
 	{
 		tuple_data	t_data = table_tuple_get_data(rel, tuple, XMIN);
@@ -227,7 +228,8 @@ ExecCheckHeapTupleVisible(EState *estate,
 					(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 					 errmsg("could not serialize access due to concurrent update")));
 	}
-	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+	if (BufferIsValid(buffer))
+		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 }
 
 /*
@@ -622,6 +624,7 @@ ExecDelete(ModifyTableState *mtstate,
 	HeapUpdateFailureData hufd;
 	TupleTableSlot *slot = NULL;
 	TransitionCaptureState *ar_delete_trig_tcs;
+	TableTuple	tuple;
 
 	if (tupleDeleted)
 		*tupleDeleted = false;
@@ -707,6 +710,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:
@@ -752,23 +784,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;
 
@@ -1167,6 +1192,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:
@@ -1211,25 +1267,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;
 
@@ -1298,8 +1345,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);
@@ -1310,8 +1357,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:
@@ -1368,8 +1419,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;
 
@@ -1398,10 +1456,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
@@ -1416,8 +1474,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 */
@@ -1463,8 +1519,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 fd706f8fee..61e3b0bf42 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -193,11 +193,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.16.1.windows.4

0012-Table-AM-shared-memory-API.patchapplication/octet-stream; name=0012-Table-AM-shared-memory-API.patchDownload
From a3e367cc84335d462d7c5a1a55b6aa3b95f5fb42 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:17:54 +1100
Subject: [PATCH 12/14] 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 6e6059e9ed..96df355b1a 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
  */
@@ -99,6 +100,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 be207765b6..e92f5cfe2c 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -490,7 +490,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.16.1.windows.4

#131David Steele
david@pgmasters.net
In reply to: Michael Paquier (#129)
Re: [HACKERS] Pluggable storage

On 3/28/18 8:39 PM, Michael Paquier wrote:

On Wed, Mar 28, 2018 at 12:23:56PM -0400, David Steele wrote:

I think this entry should be moved the the next CF. I'll do that
tomorrow unless there are objections.

Instead of moving things to the next CF by default, perhaps it would
make more sense to mark things as reviewed with feedback as this is the
last CF? There is a 5-month gap between this commit fest and the next
one, I am getting afraid of flooding the beginning of v12 development
cycle with entries which keep rotting over time. If the author(s) claim
that they will be able to work on it, then that's of course fine.

I agree and I do my best to return patches that have stalled, but I
don't think this patch is in that category. It has gotten review and
has been kept up to date. I don't think it's a good fit for v11 but I'd
like to see it in the first CF for v12.

Sorry for the digression, patches ignored across CFs contribute to the
bloat we see, and those eat the time of the CFM.

There's no question that bloat has become a problem. I don't have all
the answers, but vigilance by the CFMs is certainly a good start.

Regards,
--
-David
david@pgmasters.net

#132Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Haribabu Kommi (#130)
15 attachment(s)
Re: [HACKERS] Pluggable storage

On Thu, Mar 29, 2018 at 4:54 PM, Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:

The attached patches doesn't work with recent JIT changes that are gone in
master, because of removal many of the members from TupleTableSlot
structure
and it effects the JIT tuple deforming. This is yet to fixed.

There is an another thread proposed by Andres in abstracting the
TupleTableslot
dependency from HeapTuple in [1]. Based on the output of that thread, these
patches needs an update.

Here I attached a patches that are rebased to the latest master.

Apart from rebase, I have added the support for external relation to be
stored in the
pg_class. These are additional relations that may be used by the
extensions. Currently
these relations cannot be queried from SQL statements and also these
relations cannot
be dumped using pg_dump. Yet to check and confirm the pg_upgrade of these
relations.

JIT doesn't work at yet with these patches.

Regards,
Hari Babu
Fujitsu Australia

Attachments:

0012-Table-AM-shared-memory-API.patchapplication/octet-stream; name=0012-Table-AM-shared-memory-API.patchDownload
From 83fe080762fad948995b18ae365d6d17a84dbfd3 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:17:54 +1100
Subject: [PATCH 12/15] 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 062e184095..1bfb1942b5 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -509,6 +509,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)
@@ -543,6 +554,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 39b0f990c4..37a1c12c86 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
  */
@@ -99,6 +100,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 be207765b6..e92f5cfe2c 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -490,7 +490,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 26d882eb00..c6dfd80c04 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 7227f834a5..d29559395b 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 4897f1407e..982ba77ec2 100644
--- a/src/include/access/tableamapi.h
+++ b/src/include/access/tableamapi.h
@@ -107,7 +107,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? */
@@ -132,7 +132,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.
@@ -179,6 +178,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.16.1.windows.4

0013-Using-access-method-syntax-addition-to-create-table.patchapplication/octet-stream; name=0013-Using-access-method-syntax-addition-to-create-table.patchDownload
From 3dc92169835c89373702d1b22dbb7f96e2f3634e Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:17:54 +1100
Subject: [PATCH 13/15] Using "access method" syntax addition to create table

With the pluggable storage support, user can select the
table access method that he needs for the corresponding table.
The syntax addition is similar like CREATE INDEX.

CREATE TABLE ... [USING ACCESSMETHOD] ...

All the catalog relations are by default uses the HEAP method.
Currently the access method syntax support is added only for
main relations.

Currently Default access method HEAP is used for TOAST, VIEW,
SEQUENCE AND MATERIALIZED VIEWS.

Pending items: support of displaying access method with \d commands
---
 src/backend/bootstrap/bootparse.y         |  2 +
 src/backend/catalog/heap.c                |  4 ++
 src/backend/catalog/index.c               |  2 +-
 src/backend/catalog/toasting.c            |  1 +
 src/backend/commands/cluster.c            |  1 +
 src/backend/commands/tablecmds.c          | 28 ++++++++++++
 src/backend/nodes/copyfuncs.c             |  1 +
 src/backend/parser/gram.y                 | 72 ++++++++++++++++++-------------
 src/backend/utils/cache/relcache.c        | 12 +++---
 src/include/access/tableam.h              |  2 +
 src/include/catalog/heap.h                |  2 +
 src/include/nodes/parsenodes.h            |  1 +
 src/include/utils/relcache.h              |  1 +
 src/test/regress/expected/type_sanity.out | 16 ++++---
 src/test/regress/sql/type_sanity.sql      |  6 +--
 15 files changed, 107 insertions(+), 44 deletions(-)

diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 1ec0e5c8a9..ad4793e635 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -225,6 +225,7 @@ Boot_CreateStmt:
 												   shared_relation ? GLOBALTABLESPACE_OID : 0,
 												   $3,
 												   InvalidOid,
+												   HEAP_TABLE_AM_OID,
 												   tupdesc,
 												   RELKIND_RELATION,
 												   RELPERSISTENCE_PERMANENT,
@@ -244,6 +245,7 @@ Boot_CreateStmt:
 													  $7,
 													  InvalidOid,
 													  BOOTSTRAP_SUPERUSERID,
+													  HEAP_TABLE_AM_OID,
 													  tupdesc,
 													  NIL,
 													  RELKIND_RELATION,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 39813de991..ca9f040077 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -255,6 +255,7 @@ heap_create(const char *relname,
 			Oid reltablespace,
 			Oid relid,
 			Oid relfilenode,
+			Oid accessmtd,
 			TupleDesc tupDesc,
 			char relkind,
 			char relpersistence,
@@ -349,6 +350,7 @@ heap_create(const char *relname,
 									 relnamespace,
 									 tupDesc,
 									 relid,
+									 accessmtd,
 									 relfilenode,
 									 reltablespace,
 									 shared_relation,
@@ -1031,6 +1033,7 @@ heap_create_with_catalog(const char *relname,
 						 Oid reltypeid,
 						 Oid reloftypeid,
 						 Oid ownerid,
+						 Oid accessmtd,
 						 TupleDesc tupdesc,
 						 List *cooked_constraints,
 						 char relkind,
@@ -1174,6 +1177,7 @@ heap_create_with_catalog(const char *relname,
 							   reltablespace,
 							   relid,
 							   InvalidOid,
+							   accessmtd,
 							   tupdesc,
 							   relkind,
 							   relpersistence,
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 814cf51397..36171d5565 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -912,6 +912,7 @@ index_create(Relation heapRelation,
 								tableSpaceId,
 								indexRelationId,
 								relFileNode,
+								accessMethodObjectId,
 								indexTupDesc,
 								relkind,
 								relpersistence,
@@ -935,7 +936,6 @@ index_create(Relation heapRelation,
 	 * XXX should have a cleaner way to create cataloged indexes
 	 */
 	indexRelation->rd_rel->relowner = heapRelation->rd_rel->relowner;
-	indexRelation->rd_rel->relam = accessMethodObjectId;
 	indexRelation->rd_rel->relhasoids = false;
 	indexRelation->rd_rel->relispartition = OidIsValid(parentIndexRelid);
 
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 3baaa08238..e2dd63b0c3 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -266,6 +266,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 										   toast_typid,
 										   InvalidOid,
 										   rel->rd_rel->relowner,
+										   rel->rd_rel->relam,
 										   tupdesc,
 										   NIL,
 										   RELKIND_TOASTVALUE,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index d29fc98bad..973366e0bd 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -679,6 +679,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
 										  InvalidOid,
 										  InvalidOid,
 										  OldHeap->rd_rel->relowner,
+										  OldHeap->rd_rel->relam,
 										  OldHeapDesc,
 										  NIL,
 										  RELKIND_RELATION,
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 119ad815f0..076d22db05 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -538,6 +538,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
 	Oid			ofTypeId;
 	ObjectAddress address;
+	Oid			accessMethodId = InvalidOid;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -742,6 +743,32 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			attr->attidentity = colDef->identity;
 	}
 
+	/*
+	 * look up the access method, verify it can handle the requested features
+	 */
+	if (stmt->accessMethod != NULL)
+	{
+		HeapTuple	tuple;
+
+		tuple = SearchSysCache1(AMNAME, PointerGetDatum(stmt->accessMethod));
+		if (!HeapTupleIsValid(tuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("table access method \"%s\" does not exist",
+								 stmt->accessMethod)));
+		accessMethodId = HeapTupleGetOid(tuple);
+		ReleaseSysCache(tuple);
+	}
+	else if (relkind == RELKIND_RELATION ||
+			relkind == RELKIND_SEQUENCE ||
+			relkind == RELKIND_TOASTVALUE ||
+			relkind == RELKIND_VIEW ||
+			relkind == RELKIND_MATVIEW ||
+			relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		accessMethodId = HEAP_TABLE_AM_OID;
+	}
+
 	/*
 	 * Create the relation.  Inherited defaults and constraints are passed in
 	 * for immediate handling --- since they don't need parsing, they can be
@@ -754,6 +781,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 										  InvalidOid,
 										  ofTypeId,
 										  ownerId,
+										  accessMethodId,
 										  descriptor,
 										  list_concat(cookedDefaults,
 													  old_constraints),
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 7c045a7afe..1a5ee84167 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3320,6 +3320,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(options);
 	COPY_SCALAR_FIELD(oncommit);
 	COPY_STRING_FIELD(tablespacename);
+	COPY_STRING_FIELD(accessMethod);
 	COPY_SCALAR_FIELD(if_not_exists);
 }
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 268213d1ca..62b9339498 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -48,6 +48,7 @@
 #include <ctype.h>
 #include <limits.h>
 
+#include "access/tableam.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_am.h"
@@ -339,7 +340,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <str>		copy_file_name
 				database_name access_method_clause access_method attr_name
-				name cursor_name file_name
+				table_access_method_clause name cursor_name file_name
 				index_name opt_index_name cluster_index_specification
 
 %type <list>	func_name handler_name qual_Op qual_all_Op subquery_Op
@@ -3192,7 +3193,8 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionSpec table_access_method_clause OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
@@ -3202,15 +3204,16 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $10;
-					n->oncommit = $11;
-					n->tablespacename = $12;
+					n->accessMethod = $10;
+					n->options = $11;
+					n->oncommit = $12;
+					n->tablespacename = $13;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
-			OptTableElementList ')' OptInherit OptPartitionSpec OptWith
-			OnCommitOption OptTableSpace
+			OptTableElementList ')' OptInherit OptPartitionSpec table_access_method_clause
+			OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
@@ -3220,15 +3223,16 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $13;
-					n->oncommit = $14;
-					n->tablespacename = $15;
+					n->accessMethod = $13;
+					n->options = $14;
+					n->oncommit = $15;
+					n->tablespacename = $16;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name OF any_name
-			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
-			OptTableSpace
+			OptTypedTableElementList OptPartitionSpec table_access_method_clause
+			OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
@@ -3239,15 +3243,16 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $9;
-					n->oncommit = $10;
-					n->tablespacename = $11;
+					n->accessMethod = $9;
+					n->options = $10;
+					n->oncommit = $11;
+					n->tablespacename = $12;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
-			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
-			OptTableSpace
+			OptTypedTableElementList OptPartitionSpec table_access_method_clause
+			OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
@@ -3258,15 +3263,16 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
-					n->options = $12;
-					n->oncommit = $13;
-					n->tablespacename = $14;
+					n->accessMethod = $12;
+					n->options = $13;
+					n->oncommit = $14;
+					n->tablespacename = $15;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
-			OptTypedTableElementList PartitionBoundSpec OptPartitionSpec OptWith
-			OnCommitOption OptTableSpace
+			OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
+			table_access_method_clause OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
@@ -3277,15 +3283,16 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->partspec = $10;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $11;
-					n->oncommit = $12;
-					n->tablespacename = $13;
+					n->accessMethod = $11;
+					n->options = $12;
+					n->oncommit = $13;
+					n->tablespacename = $14;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
 			qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
-			OptWith OnCommitOption OptTableSpace
+			table_access_method_clause OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
@@ -3296,9 +3303,10 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->partspec = $13;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $14;
-					n->oncommit = $15;
-					n->tablespacename = $16;
+					n->accessMethod = $14;
+					n->options = $15;
+					n->oncommit = $16;
+					n->tablespacename = $17;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
@@ -3946,6 +3954,12 @@ part_elem: ColId opt_collate opt_class
 					$$ = n;
 				}
 		;
+
+table_access_method_clause:
+			USING access_method					{ $$ = $2; }
+			| /*EMPTY*/							{ $$ = DEFAULT_TABLE_ACCESS_METHOD; }
+		;
+
 /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
 OptWith:
 			WITH reloptions				{ $$ = $2; }
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index d5957bef02..fbfa3bd123 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1759,11 +1759,8 @@ 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))
+	if (IsCatalogRelation(relation) ||
+			!OidIsValid(relation->rd_rel->relam))
 	{
 		relation->rd_tableamhandler = HEAP_TABLE_AM_HANDLER_OID;
 	}
@@ -1945,6 +1942,7 @@ formrdesc(const char *relationName, Oid relationReltype,
 	/*
 	 * initialize the table am handler
 	 */
+	relation->rd_rel->relam = HEAP_TABLE_AM_OID;
 	relation->rd_tableamroutine = GetHeapamTableAmRoutine();
 
 	/*
@@ -3117,6 +3115,7 @@ RelationBuildLocalRelation(const char *relname,
 						   Oid relnamespace,
 						   TupleDesc tupDesc,
 						   Oid relid,
+						   Oid accessmtd,
 						   Oid relfilenode,
 						   Oid reltablespace,
 						   bool shared_relation,
@@ -3297,6 +3296,8 @@ RelationBuildLocalRelation(const char *relname,
 
 	RelationInitPhysicalAddr(rel);
 
+	rel->rd_rel->relam = accessmtd;
+
 	if (relkind == RELKIND_RELATION ||
 		relkind == RELKIND_MATVIEW ||
 		relkind == RELKIND_VIEW ||	/* Not exactly the storage, but underlying
@@ -3829,6 +3830,7 @@ RelationCacheInitializePhase3(void)
 		if (relation->rd_tableamroutine == NULL &&
 			(relation->rd_rel->relkind == RELKIND_RELATION ||
 			 relation->rd_rel->relkind == RELKIND_MATVIEW ||
+			 relation->rd_rel->relkind == RELKIND_VIEW ||
 			 relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			 relation->rd_rel->relkind == RELKIND_TOASTVALUE))
 		{
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index d29559395b..8250027637 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -19,6 +19,8 @@
 #include "executor/tuptable.h"
 #include "nodes/execnodes.h"
 
+#define DEFAULT_TABLE_ACCESS_METHOD	"heap_tableam"
+
 typedef union tuple_data
 {
 	TransactionId xid;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 59fc052494..00688d4718 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -45,6 +45,7 @@ extern Relation heap_create(const char *relname,
 			Oid reltablespace,
 			Oid relid,
 			Oid relfilenode,
+			Oid accessmtd,
 			TupleDesc tupDesc,
 			char relkind,
 			char relpersistence,
@@ -59,6 +60,7 @@ extern Oid heap_create_with_catalog(const char *relname,
 						 Oid reltypeid,
 						 Oid reloftypeid,
 						 Oid ownerid,
+						 Oid accessmtd,
 						 TupleDesc tupdesc,
 						 List *cooked_constraints,
 						 char relkind,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index cbbe065078..e661e40fe5 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2022,6 +2022,7 @@ typedef struct CreateStmt
 	List	   *options;		/* options from WITH clause */
 	OnCommitAction oncommit;	/* what do we do at COMMIT? */
 	char	   *tablespacename; /* table space to use, or NULL */
+	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
 } CreateStmt;
 
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index df16b4ca98..858a7b30d2 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -101,6 +101,7 @@ extern Relation RelationBuildLocalRelation(const char *relname,
 						   Oid relnamespace,
 						   TupleDesc tupDesc,
 						   Oid relid,
+						   Oid accessmtd,
 						   Oid relfilenode,
 						   Oid reltablespace,
 						   bool shared_relation,
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index b1419d4bc2..ccd88c0260 100644
--- a/src/test/regress/expected/type_sanity.out
+++ b/src/test/regress/expected/type_sanity.out
@@ -502,14 +502,18 @@ WHERE relkind NOT IN ('r', 'i', 'S', 't', 'v', 'm', 'c', 'f', 'p') OR
 -----+---------
 (0 rows)
 
--- Indexes should have an access method, others not.
+-- Except default tables, others should have an access method.
 SELECT p1.oid, p1.relname
 FROM pg_class as p1
-WHERE (p1.relkind = 'i' AND p1.relam = 0) OR
-    (p1.relkind != 'i' AND p1.relam != 0);
- oid | relname 
------+---------
-(0 rows)
+WHERE p1.relkind IN ('r', 'i', 'S', 't', 'v', 'm', 'p', 'i', 'I') and
+    p1.relam = 0;
+ oid  |   relname    
+------+--------------
+ 1247 | pg_type
+ 1249 | pg_attribute
+ 1255 | pg_proc
+ 1259 | pg_class
+(4 rows)
 
 -- **************** pg_attribute ****************
 -- Look for illegal values in pg_attribute fields
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index f9aeea3214..3ed0b9efcb 100644
--- a/src/test/regress/sql/type_sanity.sql
+++ b/src/test/regress/sql/type_sanity.sql
@@ -367,12 +367,12 @@ WHERE relkind NOT IN ('r', 'i', 'S', 't', 'v', 'm', 'c', 'f', 'p') OR
     relpersistence NOT IN ('p', 'u', 't') OR
     relreplident NOT IN ('d', 'n', 'f', 'i');
 
--- Indexes should have an access method, others not.
+-- Except default tables, others should have an access method.
 
 SELECT p1.oid, p1.relname
 FROM pg_class as p1
-WHERE (p1.relkind = 'i' AND p1.relam = 0) OR
-    (p1.relkind != 'i' AND p1.relam != 0);
+WHERE p1.relkind IN ('r', 'i', 'S', 't', 'v', 'm', 'p', 'i', 'I') and
+    p1.relam = 0;
 
 -- **************** pg_attribute ****************
 
-- 
2.16.1.windows.4

0014-ExecARUpdateTriggers-is-updated-to-accept-slot-inste.patchapplication/octet-stream; name=0014-ExecARUpdateTriggers-is-updated-to-accept-slot-inste.patchDownload
From ff823e22a17296b1c9219d432332cd73921c861c Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:17:54 +1100
Subject: [PATCH 14/15] ExecARUpdateTriggers is updated to accept slot instead
 of tuple

The After record update trigger function is changed to accept
slot instead of newtuple, thus is reduces the need of TableTuple
variable in the callers.
---
 src/backend/commands/trigger.c         |  4 ++--
 src/backend/executor/execReplication.c |  5 +----
 src/backend/executor/nodeModifyTable.c | 22 ++++++----------------
 src/include/commands/trigger.h         |  2 +-
 src/pl/tcl/pltcl.c                     |  2 +-
 5 files changed, 11 insertions(+), 24 deletions(-)

diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 65fd58f35f..ff475f8fa4 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3073,7 +3073,7 @@ void
 ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 					 ItemPointer tupleid,
 					 HeapTuple fdw_trigtuple,
-					 HeapTuple newtuple,
+					 TupleTableSlot *slot,
 					 List *recheckIndexes,
 					 TransitionCaptureState *transition_capture)
 {
@@ -3085,7 +3085,7 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 		  transition_capture->tcs_update_new_table)))
 	{
 		HeapTuple	trigtuple;
-
+		HeapTuple	newtuple = slot ? ExecHeapifySlot(slot) : NULL;
 		/*
 		 * Note: if the UPDATE is converted into a DELETE+INSERT as part of
 		 * update-partition-key operation, then this function is also called
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 8480044cd2..5146791b6a 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -401,7 +401,6 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 						 TupleTableSlot *searchslot, TupleTableSlot *slot)
 {
 	bool		skip_tuple = false;
-	TableTuple tuple;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	ItemPointer tid = &(searchslot->tts_tid);
@@ -437,12 +436,10 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 		table_update(rel, tid, slot, estate, GetCurrentCommandId(true), InvalidSnapshot,
 					   true, &hufd, &lockmode, IndexFunc, &recheckIndexes);
 
-		tuple = ExecHeapifySlot(slot);
-
 		/* AFTER ROW UPDATE Triggers */
 		ExecARUpdateTriggers(estate, resultRelInfo,
 							 tid,
-							 NULL, tuple, recheckIndexes, NULL);
+							 NULL, slot, recheckIndexes, NULL);
 
 		list_free(recheckIndexes);
 	}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 0633a27d78..28bccde00c 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -272,7 +272,6 @@ ExecInsert(ModifyTableState *mtstate,
 		   EState *estate,
 		   bool canSetTag)
 {
-	TableTuple tuple;
 	ResultRelInfo *resultRelInfo;
 	Relation	resultRelationDesc;
 	Oid			newId;
@@ -547,10 +546,9 @@ ExecInsert(ModifyTableState *mtstate,
 	if (mtstate->operation == CMD_UPDATE && mtstate->mt_transition_capture
 		&& mtstate->mt_transition_capture->tcs_update_new_table)
 	{
-		tuple = ExecHeapifySlot(slot);
 		ExecARUpdateTriggers(estate, resultRelInfo, NULL,
 							 NULL,
-							 tuple,
+							 slot,
 							 NULL,
 							 mtstate->mt_transition_capture);
 
@@ -936,7 +934,6 @@ ExecUpdate(ModifyTableState *mtstate,
 		   EState *estate,
 		   bool canSetTag)
 {
-	TableTuple tuple;
 	ResultRelInfo *resultRelInfo;
 	Relation	resultRelationDesc;
 	HTSU_Result result;
@@ -954,7 +951,7 @@ ExecUpdate(ModifyTableState *mtstate,
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecHeapifySlot(slot);
+	ExecMaterializeSlot(slot);
 
 	/*
 	 * get information on the (current) result relation
@@ -971,9 +968,6 @@ ExecUpdate(ModifyTableState *mtstate,
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
-
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW UPDATE Triggers */
@@ -985,9 +979,6 @@ ExecUpdate(ModifyTableState *mtstate,
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
-
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
@@ -1007,9 +998,6 @@ ExecUpdate(ModifyTableState *mtstate,
 		 * tableoid column, so initialize t_tableOid before evaluating them.
 		 */
 		ExecSlotUpdateTupleTableoid(slot, RelationGetRelid(resultRelationDesc));
-
-		/* FDW might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 	else
 	{
@@ -1066,6 +1054,7 @@ lreplace:;
 			PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
 			int			map_index;
 			TupleConversionMap *tupconv_map;
+			HeapTuple	tuple = ExecHeapifySlot(slot);
 
 			/*
 			 * Disallow an INSERT ON CONFLICT DO UPDATE that causes the
@@ -1198,6 +1187,8 @@ lreplace:;
 
 		if (result == HeapTupleUpdated && !IsolationUsesXactSnapshot())
 		{
+			TableTuple tuple;
+
 			result = table_lock_tuple(resultRelationDesc, tupleid,
 										estate->es_snapshot,
 										&tuple, estate->es_output_cid,
@@ -1221,7 +1212,6 @@ lreplace:;
 					return NULL;
 				}
 				slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
-				tuple = ExecHeapifySlot(slot);
 				goto lreplace;
 			}
 		}
@@ -1293,7 +1283,7 @@ lreplace:;
 		(estate->es_processed)++;
 
 	/* AFTER ROW UPDATE Triggers */
-	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldtuple, tuple,
+	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldtuple, slot,
 						 recheckIndexes,
 						 mtstate->operation == CMD_INSERT ?
 						 mtstate->mt_oc_transition_capture :
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 2fe7ed33a5..7cac03d469 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -230,7 +230,7 @@ extern void ExecARUpdateTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
 					 ItemPointer tupleid,
 					 HeapTuple fdw_trigtuple,
-					 HeapTuple newtuple,
+					 TupleTableSlot *slot,
 					 List *recheckIndexes,
 					 TransitionCaptureState *transition_capture);
 extern TupleTableSlot *ExecIRUpdateTriggers(EState *estate,
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index 558cabc949..0d9a40f711 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -2432,7 +2432,7 @@ pltcl_process_SPI_result(Tcl_Interp *interp,
 {
 	int			my_rc = TCL_OK;
 	int			loop_rc;
-	HeapTuple  *tuples;
+	TableTuple *tuples;
 	TupleDesc	tupdesc;
 
 	switch (spi_rc)
-- 
2.16.1.windows.4

0015-External-relation-infrastructure.patchapplication/octet-stream; name=0015-External-relation-infrastructure.patchDownload
From 999b351022699b4af22194f63ff2fc00b4d79609 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Wed, 18 Apr 2018 19:46:42 +1000
Subject: [PATCH 15/15] External relation infrastructure

Extensions can create an external relation in pg_class and use
it for their internal functionality and these tables cannot be
queried directly from SQL commands.

These tables are not visible from psql \d commands and also these
are not dumped by the pg_dump. we may need to enhance pg_dump
to handle these external relations for pg_upgrade.
---
 contrib/pageinspect/rawpage.c          | 6 ++++++
 contrib/pgstattuple/pgstattuple.c      | 3 +++
 src/backend/access/common/reloptions.c | 1 +
 src/backend/catalog/heap.c             | 3 ++-
 src/backend/catalog/namespace.c        | 6 ++++++
 src/backend/catalog/objectaddress.c    | 7 +++++++
 src/backend/executor/execMain.c        | 6 ++++++
 src/backend/parser/parse_relation.c    | 6 ++++++
 src/backend/utils/adt/dbsize.c         | 2 ++
 src/backend/utils/cache/relcache.c     | 7 +++++--
 src/include/catalog/pg_class.h         | 1 +
 11 files changed, 45 insertions(+), 3 deletions(-)

diff --git a/contrib/pageinspect/rawpage.c b/contrib/pageinspect/rawpage.c
index 72f1d21e1b..0760d981c8 100644
--- a/contrib/pageinspect/rawpage.c
+++ b/contrib/pageinspect/rawpage.c
@@ -128,6 +128,12 @@ get_raw_page_internal(text *relname, ForkNumber forknum, BlockNumber blkno)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot get raw page from partitioned table \"%s\"",
 						RelationGetRelationName(rel))));
+	if (rel->rd_rel->relkind == RELKIND_EXTERNAL)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot get raw page from external table \"%s\"",
+						RelationGetRelationName(rel))));
+
 
 	/*
 	 * Reject attempts to read non-local temporary relations; we would be
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index 6a625ad20a..a1754f389e 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -296,6 +296,9 @@ pgstat_relation(Relation rel, FunctionCallInfo fcinfo)
 		case RELKIND_PARTITIONED_TABLE:
 			err = "partitioned table";
 			break;
+		case RELKIND_EXTERNAL:
+			err = "external table";
+			break;
 		default:
 			err = "unknown";
 			break;
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 69ab2f101c..75af4d140f 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -1014,6 +1014,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 			options = index_reloptions(amoptions, datum, false);
 			break;
 		case RELKIND_FOREIGN_TABLE:
+		case RELKIND_EXTERNAL:
 			options = NULL;
 			break;
 		default:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index ca9f040077..5755a34347 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1396,7 +1396,8 @@ heap_create_init_fork(Relation rel)
 {
 	Assert(rel->rd_rel->relkind == RELKIND_RELATION ||
 		   rel->rd_rel->relkind == RELKIND_MATVIEW ||
-		   rel->rd_rel->relkind == RELKIND_TOASTVALUE);
+		   rel->rd_rel->relkind == RELKIND_TOASTVALUE ||
+		   rel->rd_rel->relkind == RELKIND_EXTERNAL);
 	RelationOpenSmgr(rel);
 	smgrcreate(rel->rd_smgr, INIT_FORKNUM, false);
 	log_smgrcreate(&rel->rd_smgr->smgr_rnode.node, INIT_FORKNUM);
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 0f67a122ed..0c46372a87 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -710,6 +710,12 @@ RelationIsVisible(Oid relid)
 		elog(ERROR, "cache lookup failed for relation %u", relid);
 	relform = (Form_pg_class) GETSTRUCT(reltup);
 
+	if (relform->relkind == RELKIND_EXTERNAL)
+	{
+		ReleaseSysCache(reltup);
+		return false;
+	}
+
 	recomputeNamespacePath();
 
 	/*
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 3cbee108c3..8ed96cfe53 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -3524,6 +3524,10 @@ getRelationDescription(StringInfo buffer, Oid relid)
 			appendStringInfo(buffer, _("foreign table %s"),
 							 relname);
 			break;
+		case RELKIND_EXTERNAL:
+			appendStringInfo(buffer, _("external table %s"),
+							 relname);
+			break;
 		default:
 			/* shouldn't get here */
 			appendStringInfo(buffer, _("relation %s"),
@@ -3992,6 +3996,9 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 		case RELKIND_FOREIGN_TABLE:
 			appendStringInfoString(buffer, "foreign table");
 			break;
+		case RELKIND_EXTERNAL:
+			appendStringInfoString(buffer, "external table");
+			break;
 		default:
 			/* shouldn't get here */
 			appendStringInfoString(buffer, "relation");
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index a4dd306445..9568a4e11c 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1222,6 +1222,12 @@ CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
 					break;
 			}
 			break;
+		case RELKIND_EXTERNAL:
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot change external table \"%s\"",
+							RelationGetRelationName(resultRel))));
+			break;
 		default:
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index bf5df26009..f11388f492 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1218,6 +1218,12 @@ addRangeTableEntry(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 
+	if (rte->relkind == RELKIND_EXTERNAL)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("Cannot execute SQL queries on external tables"),
+				 parser_errposition(pstate, exprLocation((Node *)relation))));
+
 	/*
 	 * Build the list of effective column names using user-supplied aliases
 	 * and/or actual column names.
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 07e5e78caa..a5e9a71976 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -881,6 +881,7 @@ pg_relation_filenode(PG_FUNCTION_ARGS)
 		case RELKIND_INDEX:
 		case RELKIND_SEQUENCE:
 		case RELKIND_TOASTVALUE:
+		case RELKIND_EXTERNAL:
 			/* okay, these have storage */
 			if (relform->relfilenode)
 				result = relform->relfilenode;
@@ -958,6 +959,7 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 		case RELKIND_INDEX:
 		case RELKIND_SEQUENCE:
 		case RELKIND_TOASTVALUE:
+		case RELKIND_EXTERNAL:
 			/* okay, these have storage */
 
 			/* This logic should match RelationInitPhysicalAddr */
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index fbfa3bd123..46ac80db0d 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1216,6 +1216,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_MATVIEW:
 		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_FOREIGN_TABLE: /* hari FIXME :To support COPY on foreign tables */
+		case RELKIND_EXTERNAL:
 			RelationInitTableAccessMethod(relation);
 			break;
 		default:
@@ -3303,7 +3304,8 @@ RelationBuildLocalRelation(const char *relname,
 		relkind == RELKIND_VIEW ||	/* Not exactly the storage, but underlying
 									 * tuple access, it is required */
 		relkind == RELKIND_PARTITIONED_TABLE ||
-		relkind == RELKIND_TOASTVALUE)
+		relkind == RELKIND_TOASTVALUE ||
+		relkind == RELKIND_EXTERNAL)
 		RelationInitTableAccessMethod(rel);
 
 	/*
@@ -3832,7 +3834,8 @@ RelationCacheInitializePhase3(void)
 			 relation->rd_rel->relkind == RELKIND_MATVIEW ||
 			 relation->rd_rel->relkind == RELKIND_VIEW ||
 			 relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
-			 relation->rd_rel->relkind == RELKIND_TOASTVALUE))
+			 relation->rd_rel->relkind == RELKIND_TOASTVALUE ||
+			 relation->rd_rel->relkind == RELKIND_EXTERNAL))
 		{
 			RelationInitTableAccessMethod(relation);
 			Assert(relation->rd_tableamroutine != NULL);
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index ef62c30cf9..bef4864b60 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -103,6 +103,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELKIND_FOREIGN_TABLE   'f'	/* foreign table */
 #define		  RELKIND_PARTITIONED_TABLE 'p' /* partitioned table */
 #define		  RELKIND_PARTITIONED_INDEX 'I' /* partitioned index */
+#define		  RELKIND_EXTERNAL 			'E'	/* External table */
 
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
-- 
2.16.1.windows.4

0001-Change-Create-Access-method-to-include-table-handler.patchapplication/octet-stream; name=0001-Change-Create-Access-method-to-include-table-handler.patchDownload
From f583d431ea79d44db1c2f4bd721bdd9fcf94bd12 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Thu, 29 Mar 2018 16:17:54 +1100
Subject: [PATCH 01/15] 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.dat          |  7 +++++++
 src/include/catalog/pg_type.dat          |  5 +++++
 src/test/regress/expected/opr_sanity.out | 19 ++++++++++++++++---
 src/test/regress/sql/opr_sanity.sql      | 16 +++++++++++++---
 8 files changed, 66 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 e548476623..268213d1ca 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -322,6 +322,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
@@ -5322,16 +5324,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 f821749af8..4ed8427f59 100644
--- a/src/include/catalog/pg_am.h
+++ b/src/include/catalog/pg_am.h
@@ -51,6 +51,7 @@ typedef FormData_pg_am *Form_pg_am;
  * Allowed values for amtype
  */
 #define AMTYPE_INDEX					'i' /* index access method */
+#define AMTYPE_TABLE					't' /* table access method */
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9b53855236..5543a22ea4 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7081,6 +7081,13 @@
 { oid => '3312', descr => 'I/O',
   proname => 'tsm_handler_out', prorettype => 'cstring',
   proargtypes => 'tsm_handler', prosrc => 'tsm_handler_out' },
+{ oid => '3425', descr => 'I/O',
+  proname => 'table_am_handler_in', proisstrict => 'f',
+  prorettype => 'table_am_handler', proargtypes => 'cstring',
+  prosrc => 'table_am_handler_in' },
+{ oid => '3426', descr => 'I/O',
+  proname => 'table_am_handler_out', prorettype => 'cstring',
+  proargtypes => 'table_am_handler', prosrc => 'table_am_handler_out' },
 
 # tablesample method handlers
 { oid => '3313', descr => 'BERNOULLI tablesample method handler',
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index ae7e89b322..67bc543a69 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -934,6 +934,11 @@
   typcategory => 'P', typinput => 'tsm_handler_in',
   typoutput => 'tsm_handler_out', typreceive => '-', typsend => '-',
   typalign => 'i' },
+{ oid => '3998',
+  typname => 'table_am_handler', typlen => '4', typbyval => 't', typtype => 'p',
+  typcategory => 'P', typinput => 'table_am_handler_in',
+  typoutput => 'table_am_handler_out', typreceive => '-', typsend => '-',
+  typalign => 'i' },
 { oid => '3831',
   typname => 'anyrange', typlen => '-1', typbyval => 'f', typtype => 'p',
   typcategory => 'P', typinput => 'anyrange_in', typoutput => 'anyrange_out',
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index a1e18a6ceb..78d8011469 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1721,11 +1721,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 a593d37643..20bba26052 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1156,15 +1156,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.16.1.windows.4

0002-Table-AM-API-init-functions.patchapplication/octet-stream; name=0002-Table-AM-API-init-functions.patchDownload
From b4104f6def7d7bc979ab5cb36815c1a1556e1c47 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:17:54 +1100
Subject: [PATCH 02/15] 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.dat            |   3 +
 src/include/catalog/pg_proc.dat          |   6 ++
 src/include/nodes/nodes.h                |   1 +
 9 files changed, 205 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.dat b/src/include/catalog/pg_am.dat
index 8722cacaae..1c3166d83e 100644
--- a/src/include/catalog/pg_am.dat
+++ b/src/include/catalog/pg_am.dat
@@ -30,5 +30,8 @@
 { oid => '3580', oid_symbol => 'BRIN_AM_OID',
   descr => 'block range index (BRIN) access method',
   amname => 'brin', amhandler => 'brinhandler', amtype => 'i' },
+{ oid => '4001', oid_symbol => 'HEAP_TABLE_AM_OID',
+  descr => 'heap table access method',
+  amname => 'heap_tableam', amhandler => 'heap_tableam_handler', amtype => 't' },
 
 ]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 5543a22ea4..7681211c4a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -918,6 +918,12 @@
   proname => 'int4', prorettype => 'int4', proargtypes => 'float4',
   prosrc => 'ftoi4' },
 
+# Table access method handlers
+{ oid => '4002', oid_symbol => 'HEAP_TABLE_AM_HANDLER_OID', 
+  descr => 'row-oriented heap table access method handler',
+  proname => 'heap_tableam_handler', provolatile => 'v', prorettype => 'table_am_handler',
+  proargtypes => 'internal', prosrc => 'heap_tableam_handler' },
+
 # Index access method handlers
 { oid => '330', descr => 'btree index access method handler',
   proname => 'bthandler', provolatile => 'v', prorettype => 'index_am_handler',
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index adb159a6da..6b368d4d14 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -503,6 +503,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 */
 	T_CallContext				/* in nodes/parsenodes.h */
-- 
2.16.1.windows.4

0003-Adding-tableam-hanlder-to-relation-structure.patchapplication/octet-stream; name=0003-Adding-tableam-hanlder-to-relation-structure.patchDownload
From 0a79c4614521f1bd5125c82d24307df7a4f83407 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:59:20 +1100
Subject: [PATCH 03/15] Adding tableam hanlder to relation structure

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

diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 22ff36714c..d5957bef02 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/tupdesc_details.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -1198,10 +1199,29 @@ 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:
+		case RELKIND_PARTITIONED_INDEX:
+			Assert(relation->rd_rel->relam != 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:
+		case RELKIND_FOREIGN_TABLE: /* hari FIXME :To support COPY on foreign tables */
+			RelationInitTableAccessMethod(relation);
+			break;
+		default:
+			/* nothing to do in other cases */
+			break;
+	}
 
 	/* extract reloptions if any */
 	RelationParseRelOptions(relation, pg_class_tuple);
@@ -1703,6 +1723,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
@@ -1857,6 +1942,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
 	 */
@@ -2189,6 +2279,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)
@@ -3205,6 +3297,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.
 	 *
@@ -3726,6 +3826,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);
 
@@ -5549,6 +5661,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 1d0461d295..83edad742b 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -132,6 +132,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)
 	 *
@@ -432,6 +438,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 dbbf41b0c1..df16b4ca98 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -77,6 +77,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.16.1.windows.4

0004-Adding-tuple-visibility-functions-to-table-AM.patchapplication/octet-stream; name=0004-Adding-tuple-visibility-functions-to-table-AM.patchDownload
From d8822b5e2e28465796d6be9253031bdc9e623e91 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:59:20 +1100
Subject: [PATCH 04/15] 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/amcheck/verify_nbtree.c                    |   2 +-
 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/access/nbtree/nbtsort.c                |   2 +-
 src/backend/catalog/index.c                        |   7 +-
 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 +----
 30 files changed, 330 insertions(+), 221 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/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index be0206d58e..b48c8fdaa3 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -35,7 +35,7 @@
 #include "storage/lmgr.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
-
+#include "utils/tqual.h"
 
 PG_MODULE_MAGIC;
 
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 94e051d642..b0ed27e883 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 ef33cacec6..e805981bb9 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);
@@ -158,7 +159,7 @@ statapprox_heap(Relation rel, output_type *stat)
 			 * bother distinguishing tuples inserted/deleted by our own
 			 * transaction.
 			 */
-			switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf))
+			switch (rel->rd_tableamroutine->snapshot_satisfiesVacuum(&tuple, OldestXmin, buf))
 			{
 				case HEAPTUPLE_LIVE:
 				case HEAPTUPLE_DELETE_IN_PROGRESS:
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index b599b6ca21..17b2fd9f26 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 4fdb549099..8a74203d41 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"
@@ -442,7 +443,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);
@@ -657,7 +658,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);
 
@@ -845,6 +847,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;
@@ -889,6 +892,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;
@@ -958,23 +962,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
@@ -986,6 +998,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.
@@ -1043,6 +1061,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;
@@ -1858,7 +1877,7 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 
 	pgstat_count_heap_getnext(scan->rs_rd);
 
-	return &(scan->rs_ctup);
+	return heap_copytuple(&(scan->rs_ctup));
 }
 
 /*
@@ -1977,7 +1996,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);
@@ -2124,7 +2143,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 */
@@ -2298,7 +2317,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;
@@ -3127,7 +3146,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)
 	{
@@ -3238,7 +3257,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;
 	}
 
@@ -3707,7 +3726,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);
@@ -3888,7 +3907,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;
 	}
 
@@ -4725,7 +4744,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 c2f5343dac..1b00519137 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 58b4411796..c8e06fdef3 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -475,7 +475,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
@@ -487,7 +487,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/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c
index feba5e1c8f..cde605f35e 100644
--- a/src/backend/access/nbtree/nbtsort.c
+++ b/src/backend/access/nbtree/nbtsort.c
@@ -80,7 +80,7 @@
 #include "utils/rel.h"
 #include "utils/sortsupport.h"
 #include "utils/tuplesort.h"
-
+#include "utils/tqual.h"
 
 /* Magic numbers for parallel state sharing */
 #define PARALLEL_KEY_BTREE_SHARED		UINT64CONST(0xA000000000000001)
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index dec4265d68..45e4b30e2c 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2467,6 +2467,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	TransactionId OldestXmin;
 	BlockNumber root_blkno = InvalidBlockNumber;
 	OffsetNumber root_offsets[MaxHeapTuplesPerPage];
+	TableAmRoutine *method;
 
 	/*
 	 * sanity checks
@@ -2560,6 +2561,8 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	Assert(snapshot == SnapshotAny ? TransactionIdIsValid(OldestXmin) :
 		   !TransactionIdIsValid(OldestXmin));
 	Assert(snapshot == SnapshotAny || !anyvisible);
+	
+	method = heapRelation->rd_tableamroutine;
 
 	/* set our scan endpoints */
 	if (!allow_sync)
@@ -2640,8 +2643,8 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 			 * CREATE INDEX and ANALYZE may produce wildly different reltuples
 			 * values, e.g. when there are many recently-dead tuples.
 			 */
-			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 25194e871c..cde34b9529 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 d088dc11a6..cb0176a646 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"
@@ -987,7 +988,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 5649a70800..fc6c58982e 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -1009,7 +1009,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 			 * cases impossible (e.g. in-progress insert from the same
 			 * transaction).
 			 */
-			switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf))
+			switch (onerel->rd_tableamroutine->snapshot_satisfiesVacuum(&tuple, OldestXmin, buf))
 			{
 				case HEAPTUPLE_DEAD:
 
@@ -2236,7 +2236,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 3e1c9e0714..bdb82db149 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -459,7 +459,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 7ec2c6bcaa..41f84d6f95 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -200,6 +200,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
  */
 static void
 ExecCheckHeapTupleVisible(EState *estate,
+						  Relation rel,
 						  HeapTuple tuple,
 						  Buffer buffer)
 {
@@ -211,7 +212,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
@@ -246,7 +247,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);
 }
 
@@ -1449,7 +1450,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 872d6e5735..9d7872b439 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -566,7 +566,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 4123cdebcf..250aa92e80 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 e8390311d0..8167e14ec1 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -3926,7 +3926,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 fc034ce601..c7d35961ad 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -286,7 +286,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 4b45d3cccd..407672e462 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 ca5cad7497..23f97df249 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.16.1.windows.4

0005-slot-hooks-are-added-to-table-AM.patchapplication/octet-stream; name=0005-slot-hooks-are-added-to-table-AM.patchDownload
From dad63f60c4309001a17b41f9ae68242023280d52 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:17:54 +1100
Subject: [PATCH 05/15] 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.

Pending items: JIT changes has some problem with TupleTableSlot
structure changes. Yet to fix it.
---
 contrib/postgres_fdw/postgres_fdw.c       |   2 +-
 src/backend/access/common/heaptuple.c     | 316 +-------------------
 src/backend/access/heap/heapam_handler.c  |   2 +
 src/backend/access/table/Makefile         |   2 +-
 src/backend/access/table/tableam_common.c | 462 ++++++++++++++++++++++++++++++
 src/backend/commands/copy.c               |   4 +-
 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         | 286 ++++++++----------
 src/backend/executor/nodeForeignscan.c    |   2 +-
 src/backend/executor/nodeModifyTable.c    |  26 +-
 src/backend/executor/tqueue.c             |   2 +-
 src/backend/replication/logical/worker.c  |   5 +-
 src/include/access/htup_details.h         |  17 +-
 src/include/access/tableam_common.h       |  37 +++
 src/include/access/tableamapi.h           |   2 +
 src/include/executor/tuptable.h           |  61 ++--
 20 files changed, 757 insertions(+), 613 deletions(-)
 create mode 100644 src/backend/access/table/tableam_common.c

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 30e572632e..50428c2014 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -3921,7 +3921,7 @@ apply_returning_filter(PgFdwDirectModifyState *dmstate,
 	 */
 	if (dmstate->hasSystemCols)
 	{
-		HeapTuple	resultTup = ExecMaterializeSlot(resultSlot);
+		HeapTuple	resultTup = ExecHeapifySlot(resultSlot);
 
 		/* ctid */
 		if (dmstate->ctidAttno)
diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index b9802b92c0..25e48deaa1 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/tupdesc_details.h"
 #include "access/tuptoaster.h"
@@ -80,7 +81,7 @@
 /*
  * Return the missing value of an attribute, or NULL if there isn't one.
  */
-static Datum
+Datum
 getmissingattr(TupleDesc tupleDesc,
 			   int attnum, bool *isnull)
 {
@@ -1397,111 +1398,6 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 		values[attnum] = getmissingattr(tupleDesc, attnum + 1, &isnull[attnum]);
 }
 
-/*
- * 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 */
-	uint32		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.
@@ -1517,89 +1413,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 or missing value 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))
-		return getmissingattr(slot->tts_tupleDescriptor, attnum, isnull);
-
-	/*
-	 * 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, false);
 }
 
 /*
@@ -1611,40 +1425,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);
-
-	attnum = slot->tts_nvalid;
-
-	/*
-	 * If tuple doesn't have all the atts indicated by tupleDesc, read the
-	 * rest as NULLS or missing values.
-	 */
-	if (attnum < tdesc_natts)
-		slot_getmissingattrs(slot, attnum, tdesc_natts);
-
-	slot->tts_nvalid = tdesc_natts;
+	slot->tts_slottableam->slot_virtualize_tuple(slot, slot->tts_tupleDescriptor->natts);
 }
 
 /*
@@ -1655,43 +1436,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);
-
-	attno = slot->tts_nvalid;
-
-	/*
-	 * If tuple doesn't have all the atts indicated by attnum, read the
-	 * rest as NULLs or missing values
-	 */
-	if (attno < attnum)
-		slot_getmissingattrs(slot, attno, attnum);
-
-	slot->tts_nvalid = attnum;
+	slot->tts_slottableam->slot_virtualize_tuple(slot, attnum);
 }
 
 /*
@@ -1702,42 +1447,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, tupleDesc);
-	}
+	bool		isnull;
 
-	/*
-	 * 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;
+	slot->tts_slottableam->slot_getattr(slot, attnum, &isnull, false);
 
-	/*
-	 * 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");
-
-	/* and let the tuple tell it */
-	return heap_attisnull(tuple, attnum, tupleDesc);
+	return isnull;
 }
 
 /*
@@ -1751,19 +1465,9 @@ bool
 slot_getsysattr(TupleTableSlot *slot, int attnum,
 				Datum *value, bool *isnull)
 {
-	HeapTuple	tuple = slot->tts_tuple;
 
-	Assert(attnum < 0);			/* else caller error */
-	if (tuple == NULL ||
-		tuple == &(slot->tts_minhdr))
-	{
-		/* No physical tuple, or minimal tuple, so fail */
-		*value = (Datum) 0;
-		*isnull = true;
-		return false;
-	}
-	*value = heap_getsysattr(tuple, attnum, slot->tts_tupleDescriptor, isnull);
-	return true;
+	*value = slot->tts_slottableam->slot_getattr(slot, attnum, isnull, true);
+	return *isnull ? false : true;
 }
 
 /*
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..121cdbff99
--- /dev/null
+++ b/src/backend/access/table/tableam_common.c
@@ -0,0 +1,462 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 (HeapTupleHeaderGetNatts(stuple->hst_heaptuple->t_data) <
+								slot->tts_tupleDescriptor->natts)
+			{
+				MemoryContext oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
+
+				tup = heap_expand_tuple(stuple->hst_heaptuple,
+										slot->tts_tupleDescriptor);
+				if (slot->tts_shouldFree)
+					heap_freetuple(stuple->hst_heaptuple);
+				stuple->hst_heaptuple = tup;
+				slot->tts_shouldFree = true;
+				MemoryContextSwitchTo(oldContext);
+			}
+
+			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
+		{
+			if (HeapTupleHeaderGetNatts(stuple->hst_heaptuple->t_data)
+				< slot->tts_tupleDescriptor->natts)
+				tup = minimal_expand_tuple(stuple->hst_heaptuple,
+											slot->tts_tupleDescriptor);
+			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);
+
+	attno = slot->tts_nvalid;
+
+	/*
+	 * If tuple doesn't have all the atts indicated by tupleDesc/upto attnum,
+	 * read the rest as NULLS or missing values.
+	 */
+	if (attno < upto)
+		slot_getmissingattrs(slot, attno, upto);
+
+	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, bool noerror)
+{
+	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 */
+		{
+			if (noerror)
+				goto no_error_return;
+			elog(ERROR, "cannot extract system attribute from virtual tuple");
+		}
+		if (tuple == &(stuple->hst_minhdr)) /* internal error */
+		{
+			if (noerror)
+				goto no_error_return;
+			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 or missing value 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))
+		return getmissingattr(slot->tts_tupleDescriptor, attnum, isnull);
+
+	/*
+	 * 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];
+
+no_error_return:
+	*isnull = true;
+	return (Datum) 0;
+}
+
+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 99479eed66..d3e9b5abd9 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2708,7 +2708,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)
@@ -2787,7 +2787,7 @@ CopyFrom(CopyState cstate)
 							goto next_tuple;
 
 						/* FDW might have changed tuple */
-						tuple = ExecMaterializeSlot(slot);
+						tuple = ExecHeapifySlot(slot);
 
 						/*
 						 * AFTER ROW Triggers might reference the tableoid
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 e1eb7c374b..1359455579 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -484,7 +484,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 02d2a0ffd7..3e518acd8c 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2516,7 +2516,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;
@@ -2597,7 +2597,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;
@@ -2955,7 +2955,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;
@@ -2997,7 +2997,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 	if (newSlot != NULL)
 	{
 		slot = ExecFilterJunk(relinfo->ri_junkFilter, newSlot);
-		slottuple = ExecMaterializeSlot(slot);
+		slottuple = ExecHeapifySlot(slot);
 		newtuple = slottuple;
 	}
 
@@ -3111,7 +3111,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;
@@ -4245,14 +4245,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 e530b262da..a1db38c6e6 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -494,13 +494,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();
@@ -512,13 +514,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();
@@ -530,13 +533,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 0333ccd0fe..6e6b30c2fa 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());
 
@@ -216,59 +216,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.
@@ -284,6 +231,7 @@ bool
 RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 						 TupleTableSlot *searchslot, TupleTableSlot *outslot)
 {
+	TupleTableSlot *scanslot;
 	HeapTuple	scantuple;
 	HeapScanDesc scan;
 	SnapshotData snap;
@@ -297,6 +245,8 @@ RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 	InitDirtySnapshot(snap);
 	scan = heap_beginscan(rel, &snap, 0, NULL);
 
+	scanslot = MakeSingleTupleTableSlot(desc);
+
 retry:
 	found = false;
 
@@ -305,12 +255,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;
@@ -334,7 +284,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());
 
@@ -372,6 +322,7 @@ retry:
 	}
 
 	heap_endscan(scan);
+	ExecDropSingleTupleTableSlot(scanslot);
 
 	return found;
 }
@@ -414,7 +365,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);
@@ -452,6 +403,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);
@@ -463,7 +415,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" */
@@ -479,21 +431,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);
@@ -513,6 +464,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);
@@ -524,7 +476,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);
 	}
 
@@ -533,11 +485,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 d14bf2ad69..77fdc26fd0 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"
@@ -131,15 +132,16 @@ MakeTupleTableSlot(TupleDesc tupleDesc)
 	slot->tts_isempty = true;
 	slot->tts_shouldFree = false;
 	slot->tts_shouldFreeMin = false;
-	slot->tts_tuple = NULL;
 	slot->tts_fixedTupleDescriptor = tupleDesc != NULL;
 	slot->tts_tupleDescriptor = tupleDesc;
 	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;
 
 	if (tupleDesc != NULL)
 	{
@@ -236,6 +238,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
  *
@@ -353,7 +403,7 @@ ExecSetSlotDescriptor(TupleTableSlot *slot, /* slot to change */
  * --------------------------------
  */
 TupleTableSlot *
-ExecStoreTuple(HeapTuple tuple,
+ExecStoreTuple(void *tuple,
 			   TupleTableSlot *slot,
 			   Buffer buffer,
 			   bool shouldFree)
@@ -364,47 +414,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;
 }
 
@@ -431,31 +461,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;
@@ -480,25 +498,9 @@ ExecClearTuple(TupleTableSlot *slot)	/* slot in which to store tuple */
 	Assert(slot != NULL);
 
 	/*
-	 * Free the old physical tuple if necessary.
-	 */
-	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.
+	 * Tell the table AM to release any resource associated with the slot.
 	 */
-	if (BufferIsValid(slot->tts_buffer))
-		ReleaseBuffer(slot->tts_buffer);
-
-	slot->tts_buffer = InvalidBuffer;
+	slot->tts_slottableam->slot_clear_tuple(slot);
 
 	/*
 	 * Mark it empty.
@@ -577,7 +579,7 @@ ExecStoreAllNullTuple(TupleTableSlot *slot)
  *		however the "system columns" of the result will not be meaningful.
  * --------------------------------
  */
-HeapTuple
+TableTuple
 ExecCopySlotTuple(TupleTableSlot *slot)
 {
 	/*
@@ -586,20 +588,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);
 }
 
 /* --------------------------------
@@ -618,28 +607,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)
-	{
-		if (HeapTupleHeaderGetNatts(slot->tts_tuple->t_data)
-			< slot->tts_tupleDescriptor->natts)
-			return minimal_expand_tuple(slot->tts_tuple,
-										slot->tts_tupleDescriptor);
-		else
-			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);
 }
 
 /* --------------------------------
@@ -657,38 +637,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))
-	{
-		if (HeapTupleHeaderGetNatts(slot->tts_tuple->t_data) <
-			slot->tts_tupleDescriptor->natts)
-		{
-			HeapTuple tuple;
-			MemoryContext oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
-
-			tuple = heap_expand_tuple(slot->tts_tuple,
-									  slot->tts_tupleDescriptor);
-			MemoryContextSwitchTo(oldContext);
-			slot = ExecStoreTuple(tuple, slot, InvalidBuffer, true);
-		}
-		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;
 }
 
 /* --------------------------------
@@ -708,6 +684,7 @@ MinimalTuple
 ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 {
 	MemoryContext oldContext;
+	MinimalTuple tup;
 
 	/*
 	 * sanity checks
@@ -715,11 +692,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.
@@ -729,18 +703,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;
 }
 
 /* --------------------------------
@@ -769,18 +736,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
@@ -788,12 +756,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.
@@ -803,18 +767,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
@@ -824,17 +780,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 a2a28b7ec2..869eebf274 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 41f84d6f95..eb1e9bff05 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -181,7 +181,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;
@@ -281,7 +281,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
@@ -322,7 +322,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -335,7 +335,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		newId = InvalidOid;
 	}
@@ -353,7 +353,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
@@ -703,7 +703,7 @@ ExecDelete(ModifyTableState *mtstate,
 		 */
 		if (slot->tts_isempty)
 			ExecStoreAllNullTuple(slot);
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
 	}
 	else
@@ -888,7 +888,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))
@@ -950,7 +950,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
@@ -969,7 +969,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW UPDATE Triggers */
@@ -983,7 +983,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
@@ -999,7 +999,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
@@ -1252,7 +1252,7 @@ lreplace:;
 					{
 						*tupleid = hufd.ctid;
 						slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
-						tuple = ExecMaterializeSlot(slot);
+						tuple = ExecHeapifySlot(slot);
 						goto lreplace;
 					}
 				}
@@ -1722,7 +1722,7 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
 	estate->es_result_relation_info = partrel;
 
 	/* Get the heap tuple out of the given slot. */
-	tuple = ExecMaterializeSlot(slot);
+	tuple = ExecHeapifySlot(slot);
 
 	/*
 	 * If we're capturing transition tuples, we might need to convert from the
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 aa7e27179e..086f6aae7b 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -751,9 +751,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 cf56d4ace4..1d35543015 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
@@ -670,7 +683,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.
@@ -801,6 +814,8 @@ extern Datum fastgetattr(HeapTuple tup, int attnum, TupleDesc tupleDesc,
 
 
 /* prototypes for functions in common/heaptuple.c */
+extern Datum getmissingattr(TupleDesc tupleDesc,
+			   int attnum, bool *isnull);
 extern Size heap_compute_data_size(TupleDesc tupleDesc,
 					   Datum *values, bool *isnull);
 extern void heap_fill_tuple(TupleDesc tupleDesc,
diff --git a/src/include/access/tableam_common.h b/src/include/access/tableam_common.h
index 78b24d76c7..fd44cd0b94 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, bool noerror);
+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 b71ec8e069..bade19c398 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
@@ -116,30 +128,24 @@ typedef struct TupleTableSlot
 	bool		tts_isempty;	/* true = slot is empty */
 	bool		tts_shouldFree; /* should pfree tts_tuple? */
 	bool		tts_shouldFreeMin;	/* should pfree tts_mintuple? */
-#define FIELDNO_TUPLETABLESLOT_SLOW 4
-	bool		tts_slow;		/* saved state for slot_deform_tuple */
-#define FIELDNO_TUPLETABLESLOT_TUPLE 5
-	HeapTuple	tts_tuple;		/* physical tuple, or NULL if virtual */
-#define FIELDNO_TUPLETABLESLOT_TUPLEDESCRIPTOR 6
+#define FIELDNO_TUPLETABLESLOT_TUPLEDESCRIPTOR 5
 	TupleDesc	tts_tupleDescriptor;	/* slot's tuple descriptor */
 	MemoryContext tts_mcxt;		/* slot itself is in this context */
-	Buffer		tts_buffer;		/* tuple's buffer, or InvalidBuffer */
-#define FIELDNO_TUPLETABLESLOT_NVALID 9
+#define FIELDNO_TUPLETABLESLOT_NVALID 7
 	int			tts_nvalid;		/* # of valid values in tts_values */
-#define FIELDNO_TUPLETABLESLOT_VALUES 10
+#define FIELDNO_TUPLETABLESLOT_VALUES 8
 	Datum	   *tts_values;		/* current per-attribute values */
-#define FIELDNO_TUPLETABLESLOT_ISNULL 11
+#define FIELDNO_TUPLETABLESLOT_ISNULL 9
 	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 */
-#define FIELDNO_TUPLETABLESLOT_OFF 14
-	uint32		tts_off;		/* saved state for slot_deform_tuple */
+	ItemPointerData tts_tid;	/* XXX describe */
+	Oid		tts_tableOid;	/* XXX describe */
+	Oid		tts_tupleOid;	/* XXX describe */
+	uint32		tts_speculativeToken;	/* XXX describe */
 	bool		tts_fixedTupleDescriptor; /* descriptor can't be changed */
+	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?
  */
@@ -151,9 +157,10 @@ extern TupleTableSlot *MakeTupleTableSlot(TupleDesc desc);
 extern TupleTableSlot *ExecAllocTableSlot(List **tupleTable, TupleDesc desc);
 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);
@@ -163,12 +170,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.16.1.windows.4

0006-Tuple-Insert-API-is-added-to-table-AM.patchapplication/octet-stream; name=0006-Tuple-Insert-API-is-added-to-table-AM.patchDownload
From c84ca2699bb430b517c846dd698a176d8062ab79 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:59:20 +1100
Subject: [PATCH 06/15] 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                 |  43 ++---
 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             | 146 +++++++-------
 src/backend/executor/execReplication.c      |  72 +++----
 src/backend/executor/nodeLockRows.c         |  47 +++--
 src/backend/executor/nodeModifyTable.c      | 229 ++++++++++------------
 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             |  74 +++++++-
 src/include/commands/trigger.h              |   2 +-
 src/include/executor/executor.h             |  15 +-
 src/include/executor/tuptable.h             |   3 +-
 src/include/nodes/execnodes.h               |   8 +-
 29 files changed, 974 insertions(+), 384 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 25e48deaa1..166209ec55 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -1064,6 +1064,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 8a74203d41..48d707ce77 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -1920,13 +1920,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;
@@ -1960,7 +1960,6 @@ heap_fetch(Relation relation,
 			ReleaseBuffer(buffer);
 			*userbuf = InvalidBuffer;
 		}
-		tuple->t_data = NULL;
 		return false;
 	}
 
@@ -1982,13 +1981,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);
@@ -2340,7 +2339,6 @@ heap_get_latest_tid(Relation relation,
 	}							/* end of loop */
 }
 
-
 /*
  * UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends
  *
@@ -4744,7 +4742,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)
 	{
@@ -5037,7 +5035,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;
@@ -5809,9 +5807,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
@@ -6171,14 +6168,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);
@@ -6233,6 +6234,7 @@ heap_finish_speculative(Relation relation, HeapTuple tuple)
 	END_CRIT_SECTION();
 
 	UnlockReleaseBuffer(buffer);
+	slot->tts_speculativeToken = 0;
 }
 
 /*
@@ -6262,8 +6264,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;
@@ -6272,6 +6276,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);
@@ -6385,6 +6393,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..006f604dbb 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, bool changingPart)
+{
+	/*
+	 * 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, changingPart);
+}
+
+
+/*
+ * 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 8d3c861a33..de696a04b8 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 cd42c50b09..7c427dce8a 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..af4e53b76e
--- /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, bool changingPart)
+{
+	return relation->rd_tableamroutine->tuple_delete(relation, tid, cid,
+												  crosscheck, wait, IndexFunc, hufd, changingPart);
+}
+
+/*
+ * 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 d3e9b5abd9..5a1e852388 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"
@@ -2707,8 +2708,6 @@ CopyFrom(CopyState cstate)
 
 			if (slot == NULL)	/* "do nothing" */
 				skip_tuple = true;
-			else				/* trigger might have changed tuple */
-				tuple = ExecHeapifySlot(slot);
 		}
 
 		if (!skip_tuple)
@@ -2797,20 +2796,14 @@ CopyFrom(CopyState cstate)
 						tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
 					}
 					else
-						heap_insert(resultRelInfo->ri_RelationDesc, tuple,
-									mycid, hi_options, bistate);
-
-					/* And create index entries for it */
-					if (resultRelInfo->ri_NumIndices > 0)
-						recheckIndexes = ExecInsertIndexTuples(slot,
-															   &(tuple->t_self),
-															   estate,
-															   false,
-															   NULL,
-															   NIL);
+					{
+						/* OK, store the tuple and create index entries for it */
+						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);
@@ -2888,7 +2881,7 @@ next_tuple:
 	 * indexes since those use WAL anyway)
 	 */
 	if (hi_options & HEAP_INSERT_SKIP_WAL)
-		heap_sync(cstate->rel);
+		table_sync(cstate->rel);
 
 	return processed;
 }
@@ -2921,12 +2914,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);
 
 	/*
@@ -2942,10 +2935,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);
 		}
@@ -2962,8 +2954,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 1359455579..b156f27259 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"
@@ -478,19 +479,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 */
 
@@ -509,7 +513,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 c1a9bda433..1eea2820b4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -20,6 +20,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"
@@ -4824,7 +4825,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);
 
@@ -4848,7 +4850,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 3e518acd8c..4eed3fe434 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"
@@ -2579,17 +2580,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 *
@@ -3244,9 +3249,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)
 	{
@@ -3262,11 +3268,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:
@@ -3303,7 +3309,8 @@ ltrmark:;
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("tuple to be locked was already moved to another partition 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;
@@ -3349,6 +3356,7 @@ ltrmark:;
 	{
 		Page		page;
 		ItemId		lp;
+		HeapTupleData tupledata;
 
 		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 
@@ -3367,17 +3375,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;
 }
 
@@ -4185,8 +4193,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;
@@ -4259,10 +4267,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
@@ -4276,10 +4283,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 903076ee3c..486d6986eb 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 ad8eca0a9d..cbc429e21f 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"
@@ -1915,7 +1916,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 	 */
 	if (resultRelInfo->ri_PartitionRoot)
 	{
-		HeapTuple	tuple = ExecFetchSlotTuple(slot);
+		TableTuple	tuple = ExecFetchSlotTuple(slot);
 		TupleDesc	old_tupdesc = RelationGetDescr(rel);
 		TupleConversionMap *map;
 
@@ -1995,7 +1996,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				 */
 				if (resultRelInfo->ri_PartitionRoot)
 				{
-					HeapTuple	tuple = ExecFetchSlotTuple(slot);
+					TableTuple tuple = ExecFetchSlotTuple(slot);
 					TupleConversionMap *map;
 
 					rel = resultRelInfo->ri_PartitionRoot;
@@ -2042,7 +2043,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;
 
@@ -2502,7 +2503,8 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
 			 ItemPointer tid, TransactionId priorXmax)
 {
 	TupleTableSlot *slot;
-	HeapTuple	copyTuple;
+	TableTuple copyTuple;
+	tuple_data	t_data;
 
 	Assert(rti > 0);
 
@@ -2519,7 +2521,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.
@@ -2550,7 +2554,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
@@ -2583,14 +2587,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
@@ -2598,12 +2602,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;
@@ -2617,7 +2621,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);
@@ -2639,7 +2643,8 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 				{
 					case LockWaitBlock:
 						XactLockTableWait(SnapshotDirty.xmax,
-										  relation, &tuple.t_self,
+										  relation,
+										  tid,
 										  XLTW_FetchUpdated);
 						break;
 					case LockWaitSkip:
@@ -2668,20 +2673,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);
 
@@ -2721,12 +2729,15 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 								(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 								 errmsg("tuple to be locked was already moved to another partition due to concurrent update")));
 
+#if 0 //hari
 					/* Should not encounter speculative tuple on recheck */
 					Assert(!HeapTupleHeaderIsSpeculative(tuple.t_data));
-					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;
@@ -2748,10 +2759,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;
 		}
@@ -2760,7 +2767,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;
@@ -2769,7 +2776,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);
@@ -2790,13 +2797,17 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 		 */
 
 		/* check whether next version would be in a different partition */
-		if (HeapTupleHeaderIndicatesMovedPartitions(tuple.t_data))
+		/* hari: FIXME: use a new table API */
+		if (HeapTupleHeaderIndicatesMovedPartitions(((HeapTuple) tuple)->t_data))
 			ereport(ERROR,
 					(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 					 errmsg("tuple to be locked was already moved to another partition due to concurrent update")));
 
+		t_data = table_tuple_get_data(relation, tuple, CTID);
+		ctid = t_data.tid;
+
 		/* check whether tuple has been deleted */
-		if (ItemPointerEquals(&tuple.t_self, &tuple.t_data->t_ctid))
+		if (ItemPointerEquals(tid, &ctid))
 		{
 			/* deleted, so forget about it */
 			ReleaseBuffer(buffer);
@@ -2804,17 +2815,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;
 }
 
 /*
@@ -2860,7 +2873,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;
 
@@ -2879,7 +2892,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;
@@ -2907,7 +2920,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");
@@ -2938,8 +2951,6 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 
 		if (erm->markType == ROW_MARK_REFERENCE)
 		{
-			HeapTuple	copyTuple;
-
 			Assert(erm->relation != NULL);
 
 			/* fetch the tuple's ctid */
@@ -2963,11 +2974,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");
 
 				/*
@@ -2981,32 +2992,28 @@ 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");
 
-				if (HeapTupleHeaderGetNatts(tuple.t_data) <
+				if (HeapTupleHeaderGetNatts(((HeapTuple)tuple)->t_data) <
 					RelationGetDescr(erm->relation)->natts)
 				{
-					copyTuple = heap_expand_tuple(&tuple,
+					TableTuple copyTuple = tuple;
+
+					tuple = heap_expand_tuple(copyTuple,
 												  RelationGetDescr(erm->relation));
+					heap_freetuple(copyTuple);
 				}
-				else
-				{
-					/* 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 */
@@ -3016,19 +3023,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);
+
 		}
 	}
 }
@@ -3197,8 +3197,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 6e6b30c2fa..08426f6f4d 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();
 
@@ -282,19 +283,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();
 
@@ -337,7 +339,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;
 
@@ -364,19 +365,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);
 
 		/*
@@ -400,7 +394,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);
@@ -425,22 +419,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 ace126cbf2..6dff5eeadb 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:
@@ -223,7 +228,8 @@ lnext:
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("tuple to be locked was already moved to another partition 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;
@@ -243,7 +249,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;
@@ -263,7 +270,7 @@ lnext:
 		}
 
 		/* Remember locked tuple's TID for EPQ testing and WHERE CURRENT OF */
-		erm->curCtid = tuple.t_self;
+		erm->curCtid = tid;
 	}
 
 	/*
@@ -285,7 +292,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 */
@@ -313,14 +320,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);
 		}
 
@@ -400,8 +405,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 eb1e9bff05..b87fd90d19 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"
@@ -174,15 +176,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;
 
@@ -201,7 +201,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
 static void
 ExecCheckHeapTupleVisible(EState *estate,
 						  Relation rel,
-						  HeapTuple tuple,
+						  TableTuple tuple,
 						  Buffer buffer)
 {
 	if (!IsolationUsesXactSnapshot())
@@ -214,13 +214,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")));
@@ -236,19 +238,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);
 }
 
 /* ----------------------------------------------------------------
@@ -267,7 +270,7 @@ ExecInsert(ModifyTableState *mtstate,
 		   EState *estate,
 		   bool canSetTag)
 {
-	HeapTuple	tuple;
+	TableTuple tuple;
 	ResultRelInfo *resultRelInfo;
 	Relation	resultRelationDesc;
 	Oid			newId;
@@ -277,32 +280,32 @@ ExecInsert(ModifyTableState *mtstate,
 	ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
 	OnConflictAction onconflict = node->onConflictAction;
 
-	/*
-	 * 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
 	 */
 	resultRelInfo = estate->es_result_relation_info;
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
+
+	ExecMaterializeSlot(slot);
+
 	/*
-	 * 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.
+	 * 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.
@@ -320,9 +323,6 @@ ExecInsert(ModifyTableState *mtstate,
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
-
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -334,9 +334,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)
@@ -352,14 +349,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;
 	}
@@ -381,7 +376,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 WITH CHECK policies.
@@ -420,7 +416,6 @@ ExecInsert(ModifyTableState *mtstate,
 			/* Perform a speculative insertion. */
 			uint32		specToken;
 			ItemPointerData conflictTid;
-			bool		specConflict;
 			List	   *arbiterIndexes;
 
 			arbiterIndexes = resultRelInfo->ri_onConflictArbiterIndexes;
@@ -438,7 +433,7 @@ ExecInsert(ModifyTableState *mtstate,
 			 * speculatively.
 			 */
 	vlock:
-			specConflict = false;
+			slot->tts_specConflict = false;
 			if (!ExecCheckIndexConstraints(slot, estate, &conflictTid,
 										   arbiterIndexes))
 			{
@@ -484,24 +479,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
@@ -517,7 +505,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;
@@ -529,19 +517,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,
-													   NIL);
+			newId = table_insert(resultRelationDesc, slot,
+								   estate->es_output_cid,
+								   0, NULL,
+								   ExecInsertIndexTuples,
+								   estate,
+								   NIL,
+								   &recheckIndexes);
 		}
 	}
 
@@ -549,7 +532,7 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		(estate->es_processed)++;
 		estate->es_lastoid = newId;
-		setLastTid(&(tuple->t_self));
+		setLastTid(&(slot->tts_tid));
 	}
 
 	/*
@@ -562,6 +545,7 @@ ExecInsert(ModifyTableState *mtstate,
 	if (mtstate->operation == CMD_UPDATE && mtstate->mt_transition_capture
 		&& mtstate->mt_transition_capture->tcs_update_new_table)
 	{
+		tuple = ExecHeapifySlot(slot);
 		ExecARUpdateTriggers(estate, resultRelInfo, NULL,
 							 NULL,
 							 tuple,
@@ -576,7 +560,7 @@ ExecInsert(ModifyTableState *mtstate,
 	}
 
 	/* AFTER ROW INSERT Triggers */
-	ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes,
+	ExecARInsertTriggers(estate, resultRelInfo, slot, recheckIndexes,
 						 ar_insert_trig_tcs);
 
 	list_free(recheckIndexes);
@@ -624,7 +608,7 @@ ExecInsert(ModifyTableState *mtstate,
 static TupleTableSlot *
 ExecDelete(ModifyTableState *mtstate,
 		   ItemPointer tupleid,
-		   HeapTuple oldtuple,
+		   TableTuple oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate,
@@ -676,8 +660,6 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
-		HeapTuple	tuple;
-
 		/*
 		 * delete from foreign table: let the FDW do it
 		 *
@@ -703,8 +685,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
 	{
@@ -718,10 +702,11 @@ ExecDelete(ModifyTableState *mtstate,
 		 * mode transactions.
 		 */
 ldelete:;
-		result = heap_delete(resultRelationDesc, tupleid,
+		result = table_delete(resultRelationDesc, tupleid,
 							 estate->es_output_cid,
 							 estate->es_crosscheck_snapshot,
 							 true /* wait for commit */ ,
+							 NULL,
 							 &hufd,
 							 changingPart);
 		switch (result)
@@ -852,7 +837,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)
@@ -866,20 +851,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);
@@ -888,7 +872,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))
@@ -925,14 +909,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;
@@ -998,14 +982,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
 	{
@@ -1016,7 +1000,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
@@ -1183,11 +1167,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:
@@ -1263,26 +1250,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)
@@ -1340,11 +1307,12 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	ExprContext *econtext = mtstate->ps.ps_ExprContext;
 	Relation	relation = resultRelInfo->ri_RelationDesc;
 	ExprState  *onConflictSetWhere = resultRelInfo->ri_onConflict->oc_WhereClause;
-	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);
@@ -1355,10 +1323,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:
@@ -1383,7 +1349,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"),
@@ -1422,7 +1389,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:
@@ -1450,10 +1419,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
@@ -1468,7 +1437,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 */
 	}
@@ -1508,12 +1479,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;
 }
 
@@ -1929,7 +1902,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 e207b1ffb5..3d4e8d0093 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 41d540b46e..bb8a683b44 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 23f97df249..f660807147 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, bool changingPart);
-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 1d35543015..2f1681d521 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -830,6 +830,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..1df7adf209
--- /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, bool changingPart);
+
+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 fd44cd0b94..e5cc461bd8 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..8b9812068a 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,62 @@ 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,
+											 bool changingPart);
+
+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 +101,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 a5b8610fa2..2fe7ed33a5 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -191,7 +191,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 6214d19380..d754daf441 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -194,16 +194,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);
@@ -540,9 +540,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 bade19c398..3584ea94a5 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -137,11 +137,12 @@ typedef struct TupleTableSlot
 	Datum	   *tts_values;		/* current per-attribute values */
 #define FIELDNO_TUPLETABLESLOT_ISNULL 9
 	bool	   *tts_isnull;		/* current per-attribute isnull flags */
+	bool		tts_fixedTupleDescriptor; /* descriptor can't be changed */
 	ItemPointerData tts_tid;	/* XXX describe */
 	Oid		tts_tableOid;	/* XXX describe */
 	Oid		tts_tupleOid;	/* XXX describe */
 	uint32		tts_speculativeToken;	/* XXX describe */
-	bool		tts_fixedTupleDescriptor; /* descriptor can't be changed */
+	bool		tts_specConflict;	/* XXX describe */
 	struct SlotTableAmRoutine *tts_slottableam; /* table AM */
 	void	   *tts_storage;	/* table AM's opaque space */
 } TupleTableSlot;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index fe93e78bee..061441abca 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -555,7 +555,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 */
 
@@ -2106,7 +2106,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
@@ -2116,7 +2116,7 @@ typedef struct SharedHashInfo
 {
 	int			num_workers;
 	HashInstrumentation hinstrument[FLEXIBLE_ARRAY_MEMBER];
-} SharedHashInfo;
+}			SharedHashInfo;
 
 /* ----------------
  *	 HashState information
@@ -2177,7 +2177,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.16.1.windows.4

0007-Scan-functions-are-added-to-table-AM.patchapplication/octet-stream; name=0007-Scan-functions-are-added-to-table-AM.patchDownload
From b2de2c382f28a6b5475f2833b6c95a7f8a65c634 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:59:20 +1100
Subject: [PATCH 07/15] 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/amcheck/verify_nbtree.c            |   2 +-
 contrib/pgrowlocks/pgrowlocks.c            |   6 +-
 contrib/pgstattuple/pgstattuple.c          |   6 +-
 src/backend/access/heap/heapam.c           | 223 ++++++++++-----------------
 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/nbtree/nbtsort.c        |   2 +-
 src/backend/access/table/tableam.c         | 233 +++++++++++++++++++++++++++++
 src/backend/bootstrap/bootstrap.c          |  25 ++--
 src/backend/catalog/aclchk.c               |  13 +-
 src/backend/catalog/index.c                |  49 +++---
 src/backend/catalog/partition.c            |   1 +
 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/partitioning/partbounds.c      |   7 +-
 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 +-
 58 files changed, 719 insertions(+), 453 deletions(-)

diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index b48c8fdaa3..7c7a1cf2ec 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -456,7 +456,7 @@ bt_check_every_level(Relation rel, Relation heaprel, bool readonly,
 		 *
 		 * Note that IndexBuildHeapScan() calls heap_endscan() for us.
 		 */
-		scan = heap_beginscan_strat(state->heaprel, /* relation */
+		scan = table_beginscan_strat(state->heaprel, /* relation */
 									snapshot,	/* snapshot */
 									0,	/* number of keys */
 									NULL,	/* scan key */
diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index b0ed27e883..6d47a446ea 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -125,7 +125,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
 			aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
 						   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 17b2fd9f26..1384f6ec9e 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 48d707ce77..bf04bf156a 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -83,17 +83,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,
@@ -1394,87 +1383,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;
 
@@ -1544,9 +1462,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
 	 */
@@ -1557,27 +1482,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);
+	}
 }
 
 /* ----------------
@@ -1675,36 +1594,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);
-
-	if (!parallel_scan->phs_snapshot_any)
-	{
-		/* Snapshot was serialized -- restore it */
-		snapshot = RestoreSnapshot(parallel_scan->phs_snapshot_data);
-		RegisterSnapshot(snapshot);
-	}
-	else
-	{
-		/* SnapshotAny passed by caller (not serialized) */
-		snapshot = SnapshotAny;
-	}
-
-	return heap_beginscan_internal(relation, snapshot, 0, NULL, parallel_scan,
-								   true, true, true, false, false,
-								   !parallel_scan->phs_snapshot_any);
-}
-
 /* ----------------
  *		heap_parallelscan_startblock_init - find and set the scan's startblock
  *
@@ -1849,8 +1738,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 */
@@ -1880,6 +1768,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 006f604dbb..010fef208e 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 c8e06fdef3..4b709a65ac 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"
@@ -397,9 +398,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;
 	}
 
@@ -435,7 +436,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;
 }
@@ -507,7 +508,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 22b5cc921f..c6dc0bed8a 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/index.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 10509cfe8a..2d097a7a04 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"
@@ -425,8 +426,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;
 
@@ -479,7 +480,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/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c
index cde605f35e..b3df0b221f 100644
--- a/src/backend/access/nbtree/nbtsort.c
+++ b/src/backend/access/nbtree/nbtsort.c
@@ -1673,7 +1673,7 @@ _bt_parallel_scan_and_sort(BTSpool *btspool, BTSpool *btspool2,
 	/* Join parallel scan */
 	indexInfo = BuildIndexInfo(btspool->index);
 	indexInfo->ii_Concurrent = btshared->isconcurrent;
-	scan = heap_beginscan_parallel(btspool->heap, &btshared->heapdesc);
+	scan = table_beginscan_parallel(btspool->heap, &btshared->heapdesc);
 	reltuples = IndexBuildHeapScan(btspool->heap, btspool->index, indexInfo,
 								   true, _bt_build_callback,
 								   (void *) &buildstate, scan);
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index af4e53b76e..ace187ba24 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,184 @@ 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);
+
+	if (!parallel_scan->phs_snapshot_any)
+	{
+		/* Snapshot was serialized -- restore it */
+		snapshot = RestoreSnapshot(parallel_scan->phs_snapshot_data);
+		RegisterSnapshot(snapshot);
+	}
+	else
+	{
+		/* SnapshotAny passed by caller (not serialized) */
+		snapshot = SnapshotAny;
+	}
+
+	return relation->rd_tableamroutine->scan_begin(relation, snapshot, 0, NULL, parallel_scan,
+												true, true, true, false, false, !parallel_scan->phs_snapshot_any);
+}
+
+/*
+ * 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 +267,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 a148bdc9fd..17645402fa 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"
@@ -588,18 +589,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,
@@ -607,7 +608,7 @@ boot_openrel(char *relname)
 				   sizeof((*app)->am_typ));
 			app++;
 		}
-		heap_endscan(scan);
+		table_endscan(scan);
 		heap_close(rel, NoLock);
 	}
 
@@ -918,25 +919,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 0ace1968df..c3d45fb636 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"
@@ -843,14 +844,14 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
 									CharGetDatum(PROKIND_PROCEDURE));
 
 					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;
@@ -888,14 +889,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 45e4b30e2c..63bca78b46 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -27,6 +27,7 @@
 #include "access/multixact.h"
 #include "access/relscan.h"
 #include "access/reloptions.h"
+#include "access/tableam.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
 #include "access/visibilitymap.h"
@@ -2131,10 +2132,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
 	{
@@ -2530,7 +2531,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		else
 			snapshot = SnapshotAny;
 
-		scan = heap_beginscan_strat(heapRelation,	/* relation */
+		scan = table_beginscan_strat(heapRelation,	/* relation */
 									snapshot,	/* snapshot */
 									0,	/* number of keys */
 									NULL,	/* scan key */
@@ -2566,7 +2567,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 
 	/* 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 */
@@ -2579,7 +2580,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;
 
@@ -2922,7 +2923,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		}
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 
 	/* we can now forget our snapshot, if set and registered by us */
 	if (need_unregister_snapshot)
@@ -2993,14 +2994,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();
 
@@ -3036,7 +3037,7 @@ IndexCheckExclusion(Relation heapRelation,
 								   estate, true);
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	UnregisterSnapshot(snapshot);
 
 	ExecDropSingleTupleTableSlot(slot);
@@ -3313,17 +3314,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;
@@ -3482,7 +3483,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 de801ad788..14c95cd293 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -17,6 +17,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/tableam.h"
 #include "access/tupconvert.h"
 #include "access/sysattr.h"
 #include "catalog/indexing.h"
diff --git a/src/backend/catalog/pg_conversion.c b/src/backend/catalog/pg_conversion.c
index fd5c18426b..86f426ef32 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"
@@ -160,14 +161,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 ec3bd1d22d..e565a14418 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"
@@ -333,9 +334,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);
@@ -344,7 +345,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 8705d8b1d3..4e42b10c47 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"
@@ -417,12 +418,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 cb0176a646..e190e6070e 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -929,7 +929,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;
 	}
 
@@ -979,7 +979,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;
 
@@ -1065,7 +1065,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
@@ -1712,8 +1712,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);
 
@@ -1733,7 +1733,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 5a1e852388..7729f847de 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2052,10 +2052,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();
 
@@ -2067,7 +2067,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 5342f217c0..1ccc123b61 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 f2dcc1c51f..b1537b6446 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"
@@ -2352,8 +2353,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);
@@ -2399,7 +2400,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 1eea2820b4..dc878a2a53 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4712,7 +4712,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
@@ -4720,7 +4720,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)
 			{
@@ -4834,7 +4834,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 
 		MemoryContextSwitchTo(oldCxt);
-		heap_endscan(scan);
+		table_endscan(scan);
 		UnregisterSnapshot(snapshot);
 
 		ExecDropSingleTupleTableSlot(oldslot);
@@ -5240,9 +5240,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,
@@ -5254,7 +5254,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;
@@ -8475,7 +8475,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
@@ -8483,7 +8483,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);
 
@@ -8498,7 +8498,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	}
 
 	MemoryContextSwitchTo(oldcxt);
-	heap_endscan(scan);
+	table_endscan(scan);
 	UnregisterSnapshot(snapshot);
 	ExecDropSingleTupleTableSlot(slot);
 	FreeExecutorState(estate);
@@ -8553,9 +8553,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;
@@ -8584,7 +8584,7 @@ validateForeignKeyConstraint(char *conname,
 		RI_FKey_check_ins(&fcinfo);
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	UnregisterSnapshot(snapshot);
 }
 
@@ -11135,8 +11135,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;
@@ -11196,7 +11196,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 f7e9160a4f..dd721c63a9 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"
@@ -418,8 +419,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))
 	{
@@ -436,7 +437,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;
@@ -463,7 +464,7 @@ DropTableSpace(DropTableSpaceStmt *stmt)
 	 */
 	CatalogTupleDelete(rel, &tuple->t_self);
 
-	heap_endscan(scandesc);
+	table_endscan(scandesc);
 
 	/*
 	 * Remove any comments or security labels on this tablespace.
@@ -927,8 +928,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),
@@ -939,7 +940,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()))
@@ -957,15 +958,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);
@@ -1007,8 +1008,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),
@@ -1049,7 +1050,7 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt)
 	heap_freetuple(newtuple);
 
 	/* Conclude heap scan. */
-	heap_endscan(scandesc);
+	table_endscan(scandesc);
 	heap_close(rel, NoLock);
 
 	return tablespaceoid;
@@ -1398,8 +1399,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))
@@ -1407,7 +1408,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)
@@ -1444,8 +1445,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))
@@ -1453,7 +1454,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 7746d18258..664a60f896 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"
@@ -2383,8 +2384,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;
 
@@ -2413,7 +2414,7 @@ AlterDomainNotNull(List *names, bool notNull)
 					}
 				}
 			}
-			heap_endscan(scan);
+			table_endscan(scan);
 			UnregisterSnapshot(snapshot);
 
 			/* Close each rel after processing, but keep lock */
@@ -2779,8 +2780,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;
 
@@ -2823,7 +2824,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 d90cb9a902..5f2069902a 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;
@@ -1190,9 +1191,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;
@@ -1229,7 +1230,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 486d6986eb..26c8e5065e 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			indnkeyatts = IndexRelationGetNumberOfKeyAttributes(index);
 	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 08426f6f4d..5c62d6c760 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;
@@ -233,8 +233,7 @@ RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 						 TupleTableSlot *searchslot, TupleTableSlot *outslot)
 {
 	TupleTableSlot *scanslot;
-	HeapTuple	scantuple;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	SnapshotData snap;
 	TransactionId xwait;
 	bool		found;
@@ -244,19 +243,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;
 
@@ -323,7 +322,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 77fdc26fd0..bc7dd3ecc1 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -720,7 +720,7 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 Datum
 ExecFetchSlotTupleDatum(TupleTableSlot *slot)
 {
-	HeapTuple	tup;
+	TableTuple tup;
 	TupleDesc	tupdesc;
 
 	/* Fetch slot's contents in regular-physical-tuple form */
@@ -804,7 +804,7 @@ ExecHeapifySlot(TupleTableSlot *slot)
 TupleTableSlot *
 ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
 {
-	HeapTuple	newTuple;
+	TableTuple newTuple;
 	MemoryContext oldContext;
 
 	/*
@@ -1145,7 +1145,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;
@@ -1153,7 +1153,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 23545896d4..b19abe6783 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)
 {
@@ -597,7 +597,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 1b1334006f..bcb09bc00e 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -2517,7 +2517,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,
@@ -2642,7 +2642,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 bdb82db149..45c9baf6c8 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"
@@ -431,8 +432,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);
 		}
 	}
@@ -742,7 +743,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)
@@ -832,7 +833,7 @@ ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 	/*
 	 * close heap scan
 	 */
-	heap_endscan(scanDesc);
+	table_endscan(scanDesc);
 
 	/*
 	 * close the heap relation.
@@ -963,10 +964,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);
 
 	/*
 	 * all done.
@@ -1114,5 +1115,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 869eebf274..a2e6470126 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 eaf7d2d563..d6b5540a7a 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);
 
 
@@ -245,7 +245,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)
 	{
@@ -289,7 +289,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;
@@ -297,7 +297,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 83221cdbae..237498c3bc 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);
@@ -399,7 +399,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] =
@@ -616,7 +616,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
@@ -691,12 +691,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 3a02a99621..379f992a80 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 d6012192a1..1da9ba8908 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;
 
 	/*
@@ -184,7 +184,7 @@ IndexNextWithReorder(IndexScanState *node)
 	EState	   *estate;
 	ExprContext *econtext;
 	IndexScanDesc scandesc;
-	HeapTuple	tuple;
+	TableTuple tuple;
 	TupleTableSlot *slot;
 	ReorderTuple *topmost = NULL;
 	bool		was_exact;
@@ -478,7 +478,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;
@@ -511,10 +511,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 9d7872b439..0a55c3b6a8 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -28,10 +28,12 @@
 
 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
  * ----------------------------------------------------------------
@@ -46,7 +48,7 @@ static bool SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset,
 static TupleTableSlot *
 SampleNext(SampleScanState *node)
 {
-	HeapTuple	tuple;
+	TableTuple tuple;
 	TupleTableSlot *slot;
 
 	/*
@@ -222,7 +224,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.
@@ -327,19 +329,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);
@@ -354,7 +356,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;
@@ -532,7 +534,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 9db368922a..758dbeb9c7 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"
@@ -48,8 +49,7 @@ static TupleTableSlot *SeqNext(SeqScanState *node);
 static TupleTableSlot *
 SeqNext(SeqScanState *node)
 {
-	HeapTuple	tuple;
-	HeapScanDesc scandesc;
+	TableScanDesc scandesc;
 	EState	   *estate;
 	ScanDirection direction;
 	TupleTableSlot *slot;
@@ -68,35 +68,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);
 }
 
 /*
@@ -203,7 +184,7 @@ void
 ExecEndSeqScan(SeqScanState *node)
 {
 	Relation	relation;
-	HeapScanDesc scanDesc;
+	TableScanDesc scanDesc;
 
 	/*
 	 * get information from node
@@ -226,7 +207,7 @@ ExecEndSeqScan(SeqScanState *node)
 	 * close heap scan
 	 */
 	if (scanDesc != NULL)
-		heap_endscan(scanDesc);
+		table_endscan(scanDesc);
 
 	/*
 	 * close the heap relation.
@@ -248,13 +229,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);
 }
@@ -301,7 +282,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);
 }
 
 /* ----------------------------------------------------------------
@@ -333,5 +314,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 fe5369a0c7..a9b9f6111e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -2590,7 +2590,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;
@@ -2673,7 +2673,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 08f6f67a15..ff510d4ab2 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -721,11 +721,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)
 	{
@@ -749,7 +749,7 @@ SPI_copytuple(HeapTuple tuple)
 }
 
 HeapTupleHeader
-SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc)
+SPI_returntuple(TableTuple tuple, TupleDesc tupdesc)
 {
 	MemoryContext oldcxt;
 	HeapTupleHeader dtup;
@@ -780,7 +780,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)
 {
@@ -948,7 +948,7 @@ char *
 SPI_gettype(TupleDesc tupdesc, int fnumber)
 {
 	Oid			typoid;
-	HeapTuple	typeTuple;
+	TableTuple typeTuple;
 	char	   *result;
 
 	SPI_result = 0;
@@ -1056,7 +1056,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);
@@ -1777,7 +1777,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);
@@ -1808,8 +1808,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/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
index 229ce3ff5c..9ee91a2d13 100644
--- a/src/backend/partitioning/partbounds.c
+++ b/src/backend/partitioning/partbounds.c
@@ -13,6 +13,7 @@
 */
 #include "postgres.h"
 
+#include "access/tableam.h"
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
@@ -704,7 +705,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);
 
 		/*
@@ -713,7 +714,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;
@@ -729,7 +730,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/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 3b90b16dae..862a685093 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1896,9 +1896,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;
@@ -1925,7 +1925,7 @@ get_database_list(void)
 		MemoryContextSwitchTo(oldcxt);
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	CommitTransactionCommand();
@@ -2056,13 +2056,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;
@@ -2148,7 +2148,7 @@ do_autovacuum(void)
 		}
 	}
 
-	heap_endscan(relScan);
+	table_endscan(relScan);
 
 	/* second pass: check TOAST tables */
 	ScanKeyInit(&key,
@@ -2156,8 +2156,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;
@@ -2203,7 +2203,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 084573e77c..a0ca9ee127 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 6ef333b725..d98a3e891f 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 d81a2ea342..0992fb7fd8 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 09e0df290d..99841b669b 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"
@@ -1218,10 +1219,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 f660807147..429b065634 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 1df7adf209..f3982c3c13 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 e5cc461bd8..36b72e9767 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 8b9812068a..2ab3ba62d1 100644
--- a/src/include/access/tableamapi.h
+++ b/src/include/access/tableamapi.h
@@ -82,6 +82,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.
@@ -115,6 +148,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 a309809ba8..fbf3c898f7 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 e5bdaecc4e..e75ac2746e 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;
@@ -120,10 +120,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);
@@ -136,7 +136,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 01aa208c5e..0f3e86d81f 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -238,7 +238,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.16.1.windows.4

0008-Remove-HeapScanDesc-usage-outside-heap.patchapplication/octet-stream; name=0008-Remove-HeapScanDesc-usage-outside-heap.patchDownload
From 2566e6e4bb2446ebe3ea2952e1073718ebc5f385 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:59:20 +1100
Subject: [PATCH 08/15] 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/amcheck/verify_nbtree.c            |   2 +-
 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/nbtree/nbtsort.c        |   2 +-
 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                |  10 +-
 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/partitioning/partbounds.c      |   2 +-
 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/catalog/index.h                |   4 +-
 src/include/nodes/execnodes.h              |   4 +-
 43 files changed, 523 insertions(+), 404 deletions(-)

diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index 7c7a1cf2ec..cd6c472ce8 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -447,7 +447,7 @@ bt_check_every_level(Relation rel, Relation heaprel, bool readonly,
 	if (state->heapallindexed)
 	{
 		IndexInfo  *indexinfo = BuildIndexInfo(state->rel);
-		HeapScanDesc scan;
+		TableScanDesc scan;
 
 		/*
 		 * Create our own scan for IndexBuildHeapScan(), rather than getting it
diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index 6d47a446ea..cba2e63f13 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 1384f6ec9e..6a625ad20a 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 bf04bf156a..2c100dcb3d 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -224,9 +224,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
@@ -240,8 +240,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;
@@ -252,20 +252,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)
 	{
@@ -274,25 +274,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 */
 
@@ -300,7 +300,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
@@ -308,7 +308,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);
 }
 
 /*
@@ -318,16 +318,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;
 }
 
 /*
@@ -338,8 +341,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;
@@ -349,13 +353,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;
 	}
 
 	/*
@@ -366,20 +370,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
@@ -389,7 +393,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;
 
@@ -424,7 +428,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);
@@ -432,20 +436,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;
 }
 
 /* ----------------
@@ -478,7 +482,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;
@@ -493,14 +497,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;
 			}
@@ -513,29 +517,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 */
 
@@ -546,14 +550,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;
 			}
@@ -564,30 +568,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
 		{
@@ -603,20 +607,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));
@@ -647,21 +651,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;
 				}
 			}
@@ -686,17 +690,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)
@@ -707,10 +711,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
@@ -724,8 +728,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);
 		}
 
 		/*
@@ -733,21 +737,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)
@@ -798,14 +802,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;
 			}
@@ -818,28 +822,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;
@@ -849,14 +853,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;
 			}
@@ -867,33 +871,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 */
 
@@ -904,20 +908,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));
@@ -926,8 +930,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;
 	}
@@ -940,7 +944,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));
 
@@ -951,7 +955,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.
@@ -960,19 +964,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;
 				}
 			}
@@ -991,7 +995,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
@@ -999,10 +1003,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)
@@ -1013,10 +1017,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
@@ -1030,8 +1034,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);
 		}
 
 		/*
@@ -1039,21 +1043,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;
@@ -1383,7 +1387,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,
@@ -1410,12 +1414,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;
@@ -1424,7 +1428,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
@@ -1448,13 +1452,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;
 }
 
 /* ----------------
@@ -1462,21 +1466,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
@@ -1507,29 +1513,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);
 }
@@ -1634,7 +1642,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;
 		}
 	}
@@ -1676,10 +1684,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.
@@ -1688,12 +1696,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;
@@ -1706,12 +1714,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;
 }
 
@@ -1739,17 +1749,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)
 	{
@@ -1763,7 +1775,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));
 }
@@ -1771,7 +1783,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 \
@@ -1783,7 +1795,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;
 
@@ -1791,11 +1803,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)
 	{
@@ -1810,7 +1822,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 010fef208e..e52ff51190 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 4b709a65ac..736701bfe9 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -481,10 +481,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/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c
index b3df0b221f..5155ac43fe 100644
--- a/src/backend/access/nbtree/nbtsort.c
+++ b/src/backend/access/nbtree/nbtsort.c
@@ -1620,7 +1620,7 @@ _bt_parallel_scan_and_sort(BTSpool *btspool, BTSpool *btspool2,
 {
 	SortCoordinate coordinate;
 	BTBuildState buildstate;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	double		reltuples;
 	IndexInfo  *indexInfo;
 
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index ace187ba24..ec5fe5f45a 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;
@@ -79,6 +79,25 @@ table_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan)
 												true, true, true, false, false, !parallel_scan->phs_snapshot_any);
 }
 
+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
  *
@@ -86,7 +105,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);
 }
@@ -105,18 +124,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)
 {
@@ -124,7 +143,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);
@@ -134,7 +153,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)
@@ -144,7 +163,7 @@ table_beginscan_strat(Relation relation, Snapshot snapshot,
 												false, false, false);
 }
 
-HeapScanDesc
+TableScanDesc
 table_beginscan_bm(Relation relation, Snapshot snapshot,
 					 int nkeys, ScanKey key)
 {
@@ -152,7 +171,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)
@@ -167,7 +186,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);
@@ -183,7 +202,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,
@@ -198,7 +217,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);
 }
@@ -211,23 +230,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 17645402fa..1b64917ea1 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -579,7 +579,7 @@ boot_openrel(char *relname)
 	int			i;
 	struct typmap **app;
 	Relation	rel;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tup;
 
 	if (strlen(relname) >= NAMEDATALEN)
@@ -895,7 +895,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 c3d45fb636..a16d7d8d99 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -822,7 +822,7 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
 					ScanKeyData key[2];
 					int			keycount;
 					Relation	rel;
-					HeapScanDesc scan;
+					TableScanDesc scan;
 					HeapTuple	tuple;
 
 					keycount = 0;
@@ -876,7 +876,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 63bca78b46..814cf51397 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2124,7 +2124,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],
@@ -2422,7 +2422,7 @@ IndexBuildHeapScan(Relation heapRelation,
 				   bool allow_sync,
 				   IndexBuildCallback callback,
 				   void *callback_state,
-				   HeapScanDesc scan)
+				   TableScanDesc scan)
 {
 	return IndexBuildHeapRangeScan(heapRelation, indexRelation,
 								   indexInfo, allow_sync,
@@ -2451,7 +2451,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 						BlockNumber numblocks,
 						IndexBuildCallback callback,
 						void *callback_state,
-						HeapScanDesc scan)
+						TableScanDesc scan)
 {
 	bool		is_system_catalog;
 	bool		checking_uniqueness;
@@ -2958,7 +2958,7 @@ IndexCheckExclusion(Relation heapRelation,
 					Relation indexRelation,
 					IndexInfo *indexInfo)
 {
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
@@ -3272,7 +3272,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/pg_conversion.c b/src/backend/catalog/pg_conversion.c
index 86f426ef32..8ae4b6629b 100644
--- a/src/backend/catalog/pg_conversion.c
+++ b/src/backend/catalog/pg_conversion.c
@@ -150,7 +150,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 e565a14418..cf55518137 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -323,7 +323,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 4e42b10c47..9150439861 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -392,7 +392,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 e190e6070e..473251e7b3 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -763,7 +763,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;
@@ -1693,7 +1693,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 7729f847de..d7d2f849e0 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2046,7 +2046,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 1ccc123b61..b9f33f1e29 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 b1537b6446..a16995c489 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2279,7 +2279,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 dc878a2a53..f8fff615fd 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4653,7 +4653,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;
@@ -5229,7 +5229,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;
 
@@ -8434,7 +8434,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	Expr	   *origexpr;
 	ExprState  *exprstate;
 	TupleDesc	tupdesc;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tuple;
 	ExprContext *econtext;
 	MemoryContext oldcxt;
@@ -8517,7 +8517,7 @@ validateForeignKeyConstraint(char *conname,
 							 Oid pkindOid,
 							 Oid constraintOid)
 {
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tuple;
 	Trigger		trig;
 	Snapshot	snapshot;
@@ -11070,7 +11070,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 dd721c63a9..0e7e2d65d2 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -404,7 +404,7 @@ DropTableSpace(DropTableSpaceStmt *stmt)
 {
 #ifdef HAVE_SYMLINK
 	char	   *tablespacename = stmt->tablespacename;
-	HeapScanDesc scandesc;
+	TableScanDesc scandesc;
 	Relation	rel;
 	HeapTuple	tuple;
 	ScanKeyData entry[1];
@@ -915,7 +915,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;
@@ -990,7 +990,7 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt)
 {
 	Relation	rel;
 	ScanKeyData entry[1];
-	HeapScanDesc scandesc;
+	TableScanDesc scandesc;
 	HeapTuple	tup;
 	Oid			tablespaceoid;
 	Datum		datum;
@@ -1384,7 +1384,7 @@ get_tablespace_oid(const char *tablespacename, bool missing_ok)
 {
 	Oid			result;
 	Relation	rel;
-	HeapScanDesc scandesc;
+	TableScanDesc scandesc;
 	HeapTuple	tuple;
 	ScanKeyData entry[1];
 
@@ -1430,7 +1430,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 664a60f896..e3c6503c62 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2378,7 +2378,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;
 
@@ -2774,7 +2774,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 5f2069902a..e3da71f9bc 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);
@@ -1160,7 +1160,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 45c9baf6c8..7e29030dfd 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);
@@ -381,8 +373,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;
@@ -391,7 +385,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,
@@ -434,7 +428,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
@@ -450,23 +444,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);
 		}
 	}
@@ -474,7 +466,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 
 	Assert(ntup <= MaxHeapTuplesPerPage);
-	scan->rs_ntuples = ntup;
+	pagescan->rs_ntuples = ntup;
 }
 
 /*
@@ -600,7 +592,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;
@@ -788,7 +780,7 @@ void
 ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 {
 	Relation	relation;
-	HeapScanDesc scanDesc;
+	TableScanDesc scanDesc;
 
 	/*
 	 * extract information from the node
@@ -969,6 +961,8 @@ ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags)
 															0,
 															NULL);
 
+	scanstate->pagescan = tableam_get_heappagescandesc(scanstate->ss.ss_currentScanDesc);
+
 	/*
 	 * all done.
 	 */
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index 0a55c3b6a8..be207765b6 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -30,9 +30,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
@@ -335,6 +334,7 @@ tablesample_init(SampleScanState *scanstate)
 									   scanstate->use_bulkread,
 									   allow_sync,
 									   scanstate->use_pagemode);
+		scanstate->pagescan = tableam_get_heappagescandesc(scanstate->ss.ss_currentScanDesc);
 	}
 	else
 	{
@@ -360,10 +360,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;
@@ -374,10 +375,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)
@@ -385,13 +385,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;
 	}
@@ -434,14 +433,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)
@@ -472,14 +469,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;
 
 			/*
@@ -492,10 +489,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);
 		}
 
 		/*
@@ -507,12 +504,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 */
@@ -527,16 +523,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,
@@ -548,12 +547,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 758dbeb9c7..b2b0a30343 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -295,9 +295,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 1ff0ef4866..8fe443c2d1 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"
@@ -272,7 +273,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/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
index 9ee91a2d13..41b76ae793 100644
--- a/src/backend/partitioning/partbounds.c
+++ b/src/backend/partitioning/partbounds.c
@@ -646,7 +646,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/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 862a685093..b2cf7341f2 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1878,7 +1878,7 @@ get_database_list(void)
 {
 	List	   *dblist = NIL;
 	Relation	rel;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tup;
 	MemoryContext resultcxt;
 
@@ -1944,7 +1944,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 a0ca9ee127..dc292aa0e0 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 d98a3e891f..8a8438286c 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 0992fb7fd8..2a2d890c0b 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 99841b669b..0c5551730f 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -1214,7 +1214,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 429b065634..6aca0af7d4 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 18c7dedd5d..3d29ea4ee4 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"
@@ -43,40 +44,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;
 
 /*
@@ -150,12 +165,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 f3982c3c13..afe8eeb4fe 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 36b72e9767..e5cc461bd8 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 2ab3ba62d1..29f83e0ab2 100644
--- a/src/include/access/tableamapi.h
+++ b/src/include/access/tableamapi.h
@@ -83,7 +83,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,
@@ -93,22 +93,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,
@@ -150,9 +157,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/catalog/index.h b/src/include/catalog/index.h
index f20c5f789b..6cab64df10 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -116,7 +116,7 @@ extern double IndexBuildHeapScan(Relation heapRelation,
 				   bool allow_sync,
 				   IndexBuildCallback callback,
 				   void *callback_state,
-				   HeapScanDesc scan);
+				   TableScanDesc scan);
 extern double IndexBuildHeapRangeScan(Relation heapRelation,
 						Relation indexRelation,
 						IndexInfo *indexInfo,
@@ -126,7 +126,7 @@ extern double IndexBuildHeapRangeScan(Relation heapRelation,
 						BlockNumber end_blockno,
 						IndexBuildCallback callback,
 						void *callback_state,
-						HeapScanDesc scan);
+						TableScanDesc scan);
 
 extern void validate_index(Oid heapId, Oid indexId, Snapshot snapshot);
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 061441abca..21b46c3da9 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1190,7 +1190,7 @@ typedef struct ScanState
 {
 	PlanState	ps;				/* its first field is NodeTag */
 	Relation	ss_currentRelation;
-	HeapScanDesc ss_currentScanDesc;
+	TableScanDesc ss_currentScanDesc;
 	TupleTableSlot *ss_ScanTupleSlot;
 } ScanState;
 
@@ -1211,6 +1211,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 */
@@ -1437,6 +1438,7 @@ typedef struct ParallelBitmapHeapState
 typedef struct BitmapHeapScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
+	HeapPageScanDesc pagescan;
 	ExprState  *bitmapqualorig;
 	TIDBitmap  *tbm;
 	TBMIterator *tbmiterator;
-- 
2.16.1.windows.4

0009-BulkInsertState-is-added-into-table-AM.patchapplication/octet-stream; name=0009-BulkInsertState-is-added-into-table-AM.patchDownload
From c15d043fa0956ea55b9ecf26fcb17dfac0335958 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:59:20 +1100
Subject: [PATCH 09/15] 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 2c100dcb3d..74f611f2a9 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2332,7 +2332,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 e52ff51190..cb9e7c0730 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 ec5fe5f45a..dc56749e11 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -389,3 +389,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 d7d2f849e0..14dcb8199e 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2312,7 +2312,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;
@@ -2552,7 +2552,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 */
@@ -2632,7 +2632,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;
 			}
 
@@ -2837,7 +2837,7 @@ next_tuple:
 	/* 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 b156f27259..97411fc8bf 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -53,7 +53,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;
@@ -466,7 +466,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);
@@ -509,7 +509,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 f8fff615fd..119ad815f0 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4545,7 +4545,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	ListCell   *l;
 	EState	   *estate;
 	CommandId	mycid;
-	BulkInsertState bistate;
+	void       *bistate;
 	int			hi_options;
 	ExprState  *partqualstate = NULL;
 
@@ -4571,7 +4571,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())
@@ -4846,7 +4846,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 6aca0af7d4..dd7040a71d 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 afe8eeb4fe..f82f97b77d 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 e5cc461bd8..3c2ce82df3 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 29f83e0ab2..dace6cc032 100644
--- a/src/include/access/tableamapi.h
+++ b/src/include/access/tableamapi.h
@@ -82,6 +82,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,
@@ -155,6 +158,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.16.1.windows.4

0010-table-rewrite-functionality.patchapplication/octet-stream; name=0010-table-rewrite-functionality.patchDownload
From 0f5cb8a09175623789c84ce219285f00b1cba97f Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:59:20 +1100
Subject: [PATCH 10/15] 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 cb9e7c0730..f2de024fdb 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 dc56749e11..576afe5de2 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -412,3 +412,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 473251e7b3..d29fc98bad 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);
 
 
 /*---------------------------------------------------------------------------
@@ -893,7 +892,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);
 
 	/*
@@ -1043,7 +1042,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;
@@ -1057,9 +1056,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)
@@ -1086,16 +1084,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;
@@ -1759,10 +1756,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;
 
@@ -1778,11 +1776,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 dd7040a71d..13658f9e93 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 f82f97b77d..77d08eae14 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 3c2ce82df3..b8fdfddb31 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 dace6cc032..f6fae31449 100644
--- a/src/include/access/tableamapi.h
+++ b/src/include/access/tableamapi.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 TableScanDesc (*ScanBegin_function) (Relation relation,
 											Snapshot snapshot,
 											int nkeys, ScanKey key,
@@ -162,6 +170,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.16.1.windows.4

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

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

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

#133Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Haribabu Kommi (#132)
15 attachment(s)
Re: [HACKERS] Pluggable storage

On Fri, Apr 20, 2018 at 4:44 PM Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:

Apart from rebase, I have added the support for external relation to be
stored in the
pg_class. These are additional relations that may be used by the
extensions. Currently
these relations cannot be queried from SQL statements and also these
relations cannot
be dumped using pg_dump. Yet to check and confirm the pg_upgrade of these
relations.

Here I attached rebased patchset to the latest HEAD. Apart from rebase, I
try to fix the JIT
support with pluggable storage, but it doesn't work yet.

Thanks Alexander for conducting the pluggable table access method
discussion in unconference
session at PGCon, I was not able to attend. From one of my colleague who
attended the session
told me that, there was a major discussion around TOAST and VACUUM features
support. I would
like to share the state of those two features in the current patch set.

TOAST:
I already moved some part of the toast capabilities to the storage, still
there exists majority of the
part that needs to be handled. The decision of TOAST should be part of the
access method that
handles the data. I will work on it further to provide better API.

VACUUM:
Not much changes are done in this apart moving the Vacuum visibility
functions as part of the
storage. But idea for the Vacuum was with each access method can define how
it should perform.

Regards,
Haribabu Kommi
Fujitsu Australia

Attachments:

0014-ExecARUpdateTriggers-is-updated-to-accept-slot-inste.patchapplication/octet-stream; name=0014-ExecARUpdateTriggers-is-updated-to-accept-slot-inste.patchDownload
From fe071949a7e85fde8805acc0ccdeb1d64de7d63a Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:17:54 +1100
Subject: [PATCH 14/16] ExecARUpdateTriggers is updated to accept slot instead
 of tuple

The After record update trigger function is changed to accept
slot instead of newtuple, thus is reduces the need of TableTuple
variable in the callers.
---
 src/backend/commands/trigger.c         |  4 ++--
 src/backend/executor/execReplication.c |  5 +----
 src/backend/executor/nodeModifyTable.c | 22 ++++++----------------
 src/include/commands/trigger.h         |  2 +-
 src/pl/tcl/pltcl.c                     |  2 +-
 5 files changed, 11 insertions(+), 24 deletions(-)

diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 75deabe0fe..5c6c7067fe 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3073,7 +3073,7 @@ void
 ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 					 ItemPointer tupleid,
 					 HeapTuple fdw_trigtuple,
-					 HeapTuple newtuple,
+					 TupleTableSlot *slot,
 					 List *recheckIndexes,
 					 TransitionCaptureState *transition_capture)
 {
@@ -3085,7 +3085,7 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 		  transition_capture->tcs_update_new_table)))
 	{
 		HeapTuple	trigtuple;
-
+		HeapTuple	newtuple = slot ? ExecHeapifySlot(slot) : NULL;
 		/*
 		 * Note: if the UPDATE is converted into a DELETE+INSERT as part of
 		 * update-partition-key operation, then this function is also called
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 3297fd7392..e83ab88c81 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -405,7 +405,6 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 						 TupleTableSlot *searchslot, TupleTableSlot *slot)
 {
 	bool		skip_tuple = false;
-	TableTuple tuple;
 	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	ItemPointer tid = &(searchslot->tts_tid);
@@ -443,12 +442,10 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 		table_update(rel, tid, slot, estate, GetCurrentCommandId(true), InvalidSnapshot,
 					   true, &hufd, &lockmode, IndexFunc, &recheckIndexes);
 
-		tuple = ExecHeapifySlot(slot);
-
 		/* AFTER ROW UPDATE Triggers */
 		ExecARUpdateTriggers(estate, resultRelInfo,
 							 tid,
-							 NULL, tuple, recheckIndexes, NULL);
+							 NULL, slot, recheckIndexes, NULL);
 
 		list_free(recheckIndexes);
 	}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 9560a2fa51..baa08a91b6 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -272,7 +272,6 @@ ExecInsert(ModifyTableState *mtstate,
 		   EState *estate,
 		   bool canSetTag)
 {
-	TableTuple tuple;
 	ResultRelInfo *resultRelInfo;
 	Relation	resultRelationDesc;
 	Oid			newId;
@@ -541,10 +540,9 @@ ExecInsert(ModifyTableState *mtstate,
 	if (mtstate->operation == CMD_UPDATE && mtstate->mt_transition_capture
 		&& mtstate->mt_transition_capture->tcs_update_new_table)
 	{
-		tuple = ExecHeapifySlot(slot);
 		ExecARUpdateTriggers(estate, resultRelInfo, NULL,
 							 NULL,
-							 tuple,
+							 slot,
 							 NULL,
 							 mtstate->mt_transition_capture);
 
@@ -930,7 +928,6 @@ ExecUpdate(ModifyTableState *mtstate,
 		   EState *estate,
 		   bool canSetTag)
 {
-	TableTuple tuple;
 	ResultRelInfo *resultRelInfo;
 	Relation	resultRelationDesc;
 	HTSU_Result result;
@@ -948,7 +945,7 @@ ExecUpdate(ModifyTableState *mtstate,
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecHeapifySlot(slot);
+	ExecMaterializeSlot(slot);
 
 	/*
 	 * get information on the (current) result relation
@@ -965,9 +962,6 @@ ExecUpdate(ModifyTableState *mtstate,
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
-
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW UPDATE Triggers */
@@ -979,9 +973,6 @@ ExecUpdate(ModifyTableState *mtstate,
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
-
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
@@ -1001,9 +992,6 @@ ExecUpdate(ModifyTableState *mtstate,
 		 * tableoid column, so initialize t_tableOid before evaluating them.
 		 */
 		ExecSlotUpdateTupleTableoid(slot, RelationGetRelid(resultRelationDesc));
-
-		/* FDW might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 	else
 	{
@@ -1060,6 +1048,7 @@ lreplace:;
 			PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
 			int			map_index;
 			TupleConversionMap *tupconv_map;
+			HeapTuple	tuple = ExecHeapifySlot(slot);
 
 			/*
 			 * Disallow an INSERT ON CONFLICT DO UPDATE that causes the
@@ -1189,6 +1178,8 @@ lreplace:;
 
 		if (result == HeapTupleUpdated && !IsolationUsesXactSnapshot())
 		{
+			TableTuple tuple;
+
 			result = table_lock_tuple(resultRelationDesc, tupleid,
 										estate->es_snapshot,
 										&tuple, estate->es_output_cid,
@@ -1212,7 +1203,6 @@ lreplace:;
 					return NULL;
 				}
 				slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
-				tuple = ExecHeapifySlot(slot);
 				goto lreplace;
 			}
 		}
@@ -1284,7 +1274,7 @@ lreplace:;
 		(estate->es_processed)++;
 
 	/* AFTER ROW UPDATE Triggers */
-	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldtuple, tuple,
+	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldtuple, slot,
 						 recheckIndexes,
 						 mtstate->operation == CMD_INSERT ?
 						 mtstate->mt_oc_transition_capture :
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 2fe7ed33a5..7cac03d469 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -230,7 +230,7 @@ extern void ExecARUpdateTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
 					 ItemPointer tupleid,
 					 HeapTuple fdw_trigtuple,
-					 HeapTuple newtuple,
+					 TupleTableSlot *slot,
 					 List *recheckIndexes,
 					 TransitionCaptureState *transition_capture);
 extern TupleTableSlot *ExecIRUpdateTriggers(EState *estate,
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index 12f7b13780..55319e3da8 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -2432,7 +2432,7 @@ pltcl_process_SPI_result(Tcl_Interp *interp,
 {
 	int			my_rc = TCL_OK;
 	int			loop_rc;
-	HeapTuple  *tuples;
+	TableTuple *tuples;
 	TupleDesc	tupdesc;
 
 	switch (spi_rc)
-- 
2.16.1.windows.4

0015-External-relation-infrastructure.patchapplication/octet-stream; name=0015-External-relation-infrastructure.patchDownload
From ed61637425713363ff147a1ef389c5583c0a872a Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Wed, 18 Apr 2018 19:46:42 +1000
Subject: [PATCH 15/16] External relation infrastructure

Extensions can create an external relation in pg_class and use
it for their internal functionality and these tables cannot be
queried directly from SQL commands.

These tables are not visible from psql \d commands and also these
are not dumped by the pg_dump. we may need to enhance pg_dump
to handle these external relations for pg_upgrade.
---
 contrib/pageinspect/rawpage.c          | 6 ++++++
 contrib/pgstattuple/pgstattuple.c      | 3 +++
 src/backend/access/common/reloptions.c | 1 +
 src/backend/catalog/heap.c             | 3 ++-
 src/backend/catalog/namespace.c        | 6 ++++++
 src/backend/catalog/objectaddress.c    | 7 +++++++
 src/backend/executor/execMain.c        | 6 ++++++
 src/backend/parser/parse_relation.c    | 6 ++++++
 src/backend/utils/adt/dbsize.c         | 2 ++
 src/backend/utils/cache/relcache.c     | 7 +++++--
 src/include/catalog/pg_class.h         | 1 +
 11 files changed, 45 insertions(+), 3 deletions(-)

diff --git a/contrib/pageinspect/rawpage.c b/contrib/pageinspect/rawpage.c
index d7bf782ccd..af211ad1fd 100644
--- a/contrib/pageinspect/rawpage.c
+++ b/contrib/pageinspect/rawpage.c
@@ -133,6 +133,12 @@ get_raw_page_internal(text *relname, ForkNumber forknum, BlockNumber blkno)
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot get raw page from partitioned index \"%s\"",
 						RelationGetRelationName(rel))));
+	if (rel->rd_rel->relkind == RELKIND_EXTERNAL)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot get raw page from external table \"%s\"",
+						RelationGetRelationName(rel))));
+
 
 	/*
 	 * Reject attempts to read non-local temporary relations; we would be
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index bd022e031a..f61316a4ed 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -299,6 +299,9 @@ pgstat_relation(Relation rel, FunctionCallInfo fcinfo)
 		case RELKIND_PARTITIONED_INDEX:
 			err = "partitioned index";
 			break;
+		case RELKIND_EXTERNAL:
+			err = "external table";
+			break;
 		default:
 			err = "unknown";
 			break;
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index e0c9c3431c..fc5eea0ebc 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -1014,6 +1014,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 			options = index_reloptions(amoptions, datum, false);
 			break;
 		case RELKIND_FOREIGN_TABLE:
+		case RELKIND_EXTERNAL:
 			options = NULL;
 			break;
 		default:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index a984c4a98a..8c7a06f3b5 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1396,7 +1396,8 @@ heap_create_init_fork(Relation rel)
 {
 	Assert(rel->rd_rel->relkind == RELKIND_RELATION ||
 		   rel->rd_rel->relkind == RELKIND_MATVIEW ||
-		   rel->rd_rel->relkind == RELKIND_TOASTVALUE);
+		   rel->rd_rel->relkind == RELKIND_TOASTVALUE ||
+		   rel->rd_rel->relkind == RELKIND_EXTERNAL);
 	RelationOpenSmgr(rel);
 	smgrcreate(rel->rd_smgr, INIT_FORKNUM, false);
 	log_smgrcreate(&rel->rd_smgr->smgr_rnode.node, INIT_FORKNUM);
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 0f67a122ed..0c46372a87 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -710,6 +710,12 @@ RelationIsVisible(Oid relid)
 		elog(ERROR, "cache lookup failed for relation %u", relid);
 	relform = (Form_pg_class) GETSTRUCT(reltup);
 
+	if (relform->relkind == RELKIND_EXTERNAL)
+	{
+		ReleaseSysCache(reltup);
+		return false;
+	}
+
 	recomputeNamespacePath();
 
 	/*
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index ad682673e6..db8c66ff2f 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -3654,6 +3654,10 @@ getRelationDescription(StringInfo buffer, Oid relid)
 			appendStringInfo(buffer, _("foreign table %s"),
 							 relname);
 			break;
+		case RELKIND_EXTERNAL:
+			appendStringInfo(buffer, _("external table %s"),
+							 relname);
+			break;
 		default:
 			/* shouldn't get here */
 			appendStringInfo(buffer, _("relation %s"),
@@ -4122,6 +4126,9 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 		case RELKIND_FOREIGN_TABLE:
 			appendStringInfoString(buffer, "foreign table");
 			break;
+		case RELKIND_EXTERNAL:
+			appendStringInfoString(buffer, "external table");
+			break;
 		default:
 			/* shouldn't get here */
 			appendStringInfoString(buffer, "relation");
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 745a9fa491..9aec828f71 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1222,6 +1222,12 @@ CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
 					break;
 			}
 			break;
+		case RELKIND_EXTERNAL:
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot change external table \"%s\"",
+							RelationGetRelationName(resultRel))));
+			break;
 		default:
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index bf5df26009..f11388f492 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1218,6 +1218,12 @@ addRangeTableEntry(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 
+	if (rte->relkind == RELKIND_EXTERNAL)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("Cannot execute SQL queries on external tables"),
+				 parser_errposition(pstate, exprLocation((Node *)relation))));
+
 	/*
 	 * Build the list of effective column names using user-supplied aliases
 	 * and/or actual column names.
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 07e5e78caa..a5e9a71976 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -881,6 +881,7 @@ pg_relation_filenode(PG_FUNCTION_ARGS)
 		case RELKIND_INDEX:
 		case RELKIND_SEQUENCE:
 		case RELKIND_TOASTVALUE:
+		case RELKIND_EXTERNAL:
 			/* okay, these have storage */
 			if (relform->relfilenode)
 				result = relform->relfilenode;
@@ -958,6 +959,7 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 		case RELKIND_INDEX:
 		case RELKIND_SEQUENCE:
 		case RELKIND_TOASTVALUE:
+		case RELKIND_EXTERNAL:
 			/* okay, these have storage */
 
 			/* This logic should match RelationInitPhysicalAddr */
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 75475060f9..4565e659f8 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1217,6 +1217,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 		case RELKIND_MATVIEW:
 		case RELKIND_PARTITIONED_TABLE:
 		case RELKIND_FOREIGN_TABLE: /* hari FIXME :To support COPY on foreign tables */
+		case RELKIND_EXTERNAL:
 			RelationInitTableAccessMethod(relation);
 			break;
 		default:
@@ -3373,7 +3374,8 @@ RelationBuildLocalRelation(const char *relname,
 		relkind == RELKIND_VIEW ||	/* Not exactly the storage, but underlying
 									 * tuple access, it is required */
 		relkind == RELKIND_PARTITIONED_TABLE ||
-		relkind == RELKIND_TOASTVALUE)
+		relkind == RELKIND_TOASTVALUE ||
+		relkind == RELKIND_EXTERNAL)
 		RelationInitTableAccessMethod(rel);
 
 	/*
@@ -3902,7 +3904,8 @@ RelationCacheInitializePhase3(void)
 			 relation->rd_rel->relkind == RELKIND_MATVIEW ||
 			 relation->rd_rel->relkind == RELKIND_VIEW ||
 			 relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
-			 relation->rd_rel->relkind == RELKIND_TOASTVALUE))
+			 relation->rd_rel->relkind == RELKIND_TOASTVALUE ||
+			 relation->rd_rel->relkind == RELKIND_EXTERNAL))
 		{
 			RelationInitTableAccessMethod(relation);
 			Assert(relation->rd_tableamroutine != NULL);
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index dc6c415c58..4a98562ec4 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -104,6 +104,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELKIND_FOREIGN_TABLE   'f'	/* foreign table */
 #define		  RELKIND_PARTITIONED_TABLE 'p' /* partitioned table */
 #define		  RELKIND_PARTITIONED_INDEX 'I' /* partitioned index */
+#define		  RELKIND_EXTERNAL 			'E'	/* External table */
 
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
-- 
2.16.1.windows.4

0001-Change-Create-Access-method-to-include-table-handler.patchapplication/octet-stream; name=0001-Change-Create-Access-method-to-include-table-handler.patchDownload
From 5ac450114cd8398c5141e7e711db2694e37b6818 Mon Sep 17 00:00:00 2001
From: Hari Babu <haribabuk@fast.au.fujitsu.com>
Date: Thu, 29 Mar 2018 16:17:54 +1100
Subject: [PATCH 01/16] 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.dat          |  7 +++++++
 src/include/catalog/pg_type.dat          |  5 +++++
 src/test/regress/expected/opr_sanity.out | 19 ++++++++++++++++---
 src/test/regress/sql/opr_sanity.sql      | 16 +++++++++++++---
 8 files changed, 66 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 90dfac2cb1..c29a50194d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -322,6 +322,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
@@ -5324,16 +5326,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 26cb234987..2bdbe2fe35 100644
--- a/src/include/catalog/pg_am.h
+++ b/src/include/catalog/pg_am.h
@@ -51,6 +51,7 @@ typedef FormData_pg_am *Form_pg_am;
  * Allowed values for amtype
  */
 #define AMTYPE_INDEX					'i' /* index access method */
+#define AMTYPE_TABLE					't' /* table access method */
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 66c6c224a8..1dbc1ddcc8 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7084,6 +7084,13 @@
 { oid => '3312', descr => 'I/O',
   proname => 'tsm_handler_out', prorettype => 'cstring',
   proargtypes => 'tsm_handler', prosrc => 'tsm_handler_out' },
+{ oid => '3425', descr => 'I/O',
+  proname => 'table_am_handler_in', proisstrict => 'f',
+  prorettype => 'table_am_handler', proargtypes => 'cstring',
+  prosrc => 'table_am_handler_in' },
+{ oid => '3426', descr => 'I/O',
+  proname => 'table_am_handler_out', prorettype => 'cstring',
+  proargtypes => 'table_am_handler', prosrc => 'table_am_handler_out' },
 
 # tablesample method handlers
 { oid => '3313', descr => 'BERNOULLI tablesample method handler',
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index 48e01cd694..152b4b680d 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -934,6 +934,11 @@
   typcategory => 'P', typinput => 'tsm_handler_in',
   typoutput => 'tsm_handler_out', typreceive => '-', typsend => '-',
   typalign => 'i' },
+{ oid => '3998',
+  typname => 'table_am_handler', typlen => '4', typbyval => 't', typtype => 'p',
+  typcategory => 'P', typinput => 'table_am_handler_in',
+  typoutput => 'table_am_handler_out', typreceive => '-', typsend => '-',
+  typalign => 'i' },
 { oid => '3831',
   typname => 'anyrange', typlen => '-1', typbyval => 'f', typtype => 'p',
   typcategory => 'P', typinput => 'anyrange_in', typoutput => 'anyrange_out',
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index a1e18a6ceb..78d8011469 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1721,11 +1721,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 a593d37643..20bba26052 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1156,15 +1156,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.16.1.windows.4

0002-Table-AM-API-init-functions.patchapplication/octet-stream; name=0002-Table-AM-API-init-functions.patchDownload
From d5c4e514bb77471e896796dd2c228b92ed0ce081 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:17:54 +1100
Subject: [PATCH 02/16] 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.dat            |   3 +
 src/include/catalog/pg_proc.dat          |   6 ++
 src/include/nodes/nodes.h                |   1 +
 9 files changed, 205 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.dat b/src/include/catalog/pg_am.dat
index bef53a319a..aa3293d736 100644
--- a/src/include/catalog/pg_am.dat
+++ b/src/include/catalog/pg_am.dat
@@ -30,5 +30,8 @@
 { oid => '3580', oid_symbol => 'BRIN_AM_OID',
   descr => 'block range index (BRIN) access method',
   amname => 'brin', amhandler => 'brinhandler', amtype => 'i' },
+{ oid => '4001', oid_symbol => 'HEAP_TABLE_AM_OID',
+  descr => 'heap table access method',
+  amname => 'heap_tableam', amhandler => 'heap_tableam_handler', amtype => 't' },
 
 ]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 1dbc1ddcc8..4a047c0e08 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -921,6 +921,12 @@
   proname => 'int4', prorettype => 'int4', proargtypes => 'float4',
   prosrc => 'ftoi4' },
 
+# Table access method handlers
+{ oid => '4002', oid_symbol => 'HEAP_TABLE_AM_HANDLER_OID', 
+  descr => 'row-oriented heap table access method handler',
+  proname => 'heap_tableam_handler', provolatile => 'v', prorettype => 'table_am_handler',
+  proargtypes => 'internal', prosrc => 'heap_tableam_handler' },
+
 # Index access method handlers
 { oid => '330', descr => 'btree index access method handler',
   proname => 'bthandler', provolatile => 'v', prorettype => 'index_am_handler',
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 43f1552241..945bbc3ddf 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -502,6 +502,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 */
 	T_CallContext				/* in nodes/parsenodes.h */
-- 
2.16.1.windows.4

0003-Adding-tableam-hanlder-to-relation-structure.patchapplication/octet-stream; name=0003-Adding-tableam-hanlder-to-relation-structure.patchDownload
From be8cd6f8e676e3b158928f0e31185ade0215a10f Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:59:20 +1100
Subject: [PATCH 03/16] Adding tableam hanlder to relation structure

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

diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index d85dc92505..4ad19db3e4 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/tupdesc_details.h"
 #include "access/xact.h"
 #include "access/xlog.h"
@@ -1199,10 +1200,29 @@ 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:
+		case RELKIND_PARTITIONED_INDEX:
+			Assert(relation->rd_rel->relam != 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:
+		case RELKIND_FOREIGN_TABLE: /* hari FIXME :To support COPY on foreign tables */
+			RelationInitTableAccessMethod(relation);
+			break;
+		default:
+			/* nothing to do in other cases */
+			break;
+	}
 
 	/* extract reloptions if any */
 	RelationParseRelOptions(relation, pg_class_tuple);
@@ -1704,6 +1724,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
@@ -1858,6 +1943,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
 	 */
@@ -2274,6 +2364,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)
@@ -3275,6 +3367,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.
 	 *
@@ -3796,6 +3896,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);
 
@@ -5620,6 +5732,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 c97f9d1b43..ded5b077e0 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -132,6 +132,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)
 	 *
@@ -432,6 +438,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 dbbf41b0c1..df16b4ca98 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -77,6 +77,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.16.1.windows.4

0004-Adding-tuple-visibility-functions-to-table-AM.patchapplication/octet-stream; name=0004-Adding-tuple-visibility-functions-to-table-AM.patchDownload
From d78cba30fee704ae16adae8a11dfa558af569b97 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:59:20 +1100
Subject: [PATCH 04/16] 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/amcheck/verify_nbtree.c                    |   2 +-
 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/access/nbtree/nbtsort.c                |   2 +-
 src/backend/catalog/index.c                        |   7 +-
 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 +----
 30 files changed, 330 insertions(+), 221 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/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index a1438a2855..075e5548b9 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -35,7 +35,7 @@
 #include "storage/lmgr.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
-
+#include "utils/tqual.h"
 
 PG_MODULE_MAGIC;
 
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 94e051d642..b0ed27e883 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 ef33cacec6..e805981bb9 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);
@@ -158,7 +159,7 @@ statapprox_heap(Relation rel, output_type *stat)
 			 * bother distinguishing tuples inserted/deleted by our own
 			 * transaction.
 			 */
-			switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf))
+			switch (rel->rd_tableamroutine->snapshot_satisfiesVacuum(&tuple, OldestXmin, buf))
 			{
 				case HEAPTUPLE_LIVE:
 				case HEAPTUPLE_DELETE_IN_PROGRESS:
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index 6d67bd8271..03f67c0beb 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -325,6 +325,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);
@@ -340,7 +341,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 72395a50b8..ee407084c5 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"
@@ -442,7 +443,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);
@@ -657,7 +658,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);
 
@@ -845,6 +847,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;
@@ -889,6 +892,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;
@@ -958,23 +962,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
@@ -986,6 +998,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.
@@ -1043,6 +1061,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;
@@ -1858,7 +1877,7 @@ heap_getnext(HeapScanDesc scan, ScanDirection direction)
 
 	pgstat_count_heap_getnext(scan->rs_rd);
 
-	return &(scan->rs_ctup);
+	return heap_copytuple(&(scan->rs_ctup));
 }
 
 /*
@@ -1977,7 +1996,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);
@@ -2124,7 +2143,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 */
@@ -2298,7 +2317,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;
@@ -3127,7 +3146,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)
 	{
@@ -3238,7 +3257,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;
 	}
 
@@ -3707,7 +3726,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);
@@ -3888,7 +3907,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;
 	}
 
@@ -4728,7 +4747,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 c2f5343dac..1b00519137 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 58b4411796..c8e06fdef3 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -475,7 +475,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
@@ -487,7 +487,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/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c
index e012df596e..89c2ba3285 100644
--- a/src/backend/access/nbtree/nbtsort.c
+++ b/src/backend/access/nbtree/nbtsort.c
@@ -80,7 +80,7 @@
 #include "utils/rel.h"
 #include "utils/sortsupport.h"
 #include "utils/tuplesort.h"
-
+#include "utils/tqual.h"
 
 /* Magic numbers for parallel state sharing */
 #define PARALLEL_KEY_BTREE_SHARED		UINT64CONST(0xA000000000000001)
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8b276bc430..9286e78bcc 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2467,6 +2467,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	TransactionId OldestXmin;
 	BlockNumber root_blkno = InvalidBlockNumber;
 	OffsetNumber root_offsets[MaxHeapTuplesPerPage];
+	TableAmRoutine *method;
 
 	/*
 	 * sanity checks
@@ -2560,6 +2561,8 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	Assert(snapshot == SnapshotAny ? TransactionIdIsValid(OldestXmin) :
 		   !TransactionIdIsValid(OldestXmin));
 	Assert(snapshot == SnapshotAny || !anyvisible);
+	
+	method = heapRelation->rd_tableamroutine;
 
 	/* set our scan endpoints */
 	if (!allow_sync)
@@ -2640,8 +2643,8 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 			 * CREATE INDEX and ANALYZE may produce wildly different reltuples
 			 * values, e.g. when there are many recently-dead tuples.
 			 */
-			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 25194e871c..cde34b9529 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 482d463420..5e9462c63e 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"
@@ -987,7 +988,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 5649a70800..fc6c58982e 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -1009,7 +1009,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
 			 * cases impossible (e.g. in-progress insert from the same
 			 * transaction).
 			 */
-			switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf))
+			switch (onerel->rd_tableamroutine->snapshot_satisfiesVacuum(&tuple, OldestXmin, buf))
 			{
 				case HEAPTUPLE_DEAD:
 
@@ -2236,7 +2236,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 3e1c9e0714..bdb82db149 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -459,7 +459,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 7e0b867971..85c67772de 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -200,6 +200,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
  */
 static void
 ExecCheckHeapTupleVisible(EState *estate,
+						  Relation rel,
 						  HeapTuple tuple,
 						  Buffer buffer)
 {
@@ -211,7 +212,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
@@ -246,7 +247,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);
 }
 
@@ -1442,7 +1443,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 15177dbed7..950f88100c 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -566,7 +566,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 4123cdebcf..250aa92e80 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 e8390311d0..8167e14ec1 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -3926,7 +3926,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 fc034ce601..c7d35961ad 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -286,7 +286,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 4b45d3cccd..407672e462 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 ca5cad7497..23f97df249 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.16.1.windows.4

0005-slot-hooks-are-added-to-table-AM.patchapplication/octet-stream; name=0005-slot-hooks-are-added-to-table-AM.patchDownload
From 52f814db92329bc2f713860d6d222a5db2a64c26 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:17:54 +1100
Subject: [PATCH 05/16] 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.

JIT changes of Tuple deform are not proper.
---
 contrib/postgres_fdw/postgres_fdw.c       |   2 +-
 src/backend/access/common/heaptuple.c     | 316 +-------------------
 src/backend/access/heap/heapam_handler.c  |   2 +
 src/backend/access/table/Makefile         |   2 +-
 src/backend/access/table/tableam_common.c | 462 ++++++++++++++++++++++++++++++
 src/backend/commands/copy.c               |   4 +-
 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         | 286 ++++++++----------
 src/backend/executor/nodeForeignscan.c    |   2 +-
 src/backend/executor/nodeModifyTable.c    |  26 +-
 src/backend/executor/tqueue.c             |   2 +-
 src/backend/jit/llvm/llvmjit.c            |   6 +-
 src/backend/jit/llvm/llvmjit_deform.c     |  18 +-
 src/backend/jit/llvm/llvmjit_expr.c       |  23 +-
 src/backend/jit/llvm/llvmjit_types.c      |   3 +-
 src/backend/replication/logical/worker.c  |   5 +-
 src/include/access/htup_details.h         |  17 +-
 src/include/access/tableam_common.h       |  37 +++
 src/include/access/tableamapi.h           |   2 +
 src/include/executor/tuptable.h           |  61 ++--
 src/include/jit/llvmjit.h                 |   3 +-
 25 files changed, 782 insertions(+), 641 deletions(-)
 create mode 100644 src/backend/access/table/tableam_common.c

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 78b0f43ca8..c3cc5ad9d9 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -3944,7 +3944,7 @@ apply_returning_filter(PgFdwDirectModifyState *dmstate,
 	 */
 	if (dmstate->hasSystemCols)
 	{
-		HeapTuple	resultTup = ExecMaterializeSlot(resultSlot);
+		HeapTuple	resultTup = ExecHeapifySlot(resultSlot);
 
 		/* ctid */
 		if (dmstate->ctidAttno)
diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 104172184f..25e48deaa1 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/tupdesc_details.h"
 #include "access/tuptoaster.h"
@@ -80,7 +81,7 @@
 /*
  * Return the missing value of an attribute, or NULL if there isn't one.
  */
-static Datum
+Datum
 getmissingattr(TupleDesc tupleDesc,
 			   int attnum, bool *isnull)
 {
@@ -1397,111 +1398,6 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 		values[attnum] = getmissingattr(tupleDesc, attnum + 1, &isnull[attnum]);
 }
 
-/*
- * 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 */
-	uint32		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.
@@ -1517,89 +1413,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 or missing value 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))
-		return getmissingattr(slot->tts_tupleDescriptor, attnum, isnull);
-
-	/*
-	 * 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, false);
 }
 
 /*
@@ -1611,40 +1425,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);
-
-	attnum = slot->tts_nvalid;
-
-	/*
-	 * If tuple doesn't have all the atts indicated by tupleDesc, read the
-	 * rest as NULLS or missing values.
-	 */
-	if (attnum < tdesc_natts)
-		slot_getmissingattrs(slot, attnum, tdesc_natts);
-
-	slot->tts_nvalid = tdesc_natts;
+	slot->tts_slottableam->slot_virtualize_tuple(slot, slot->tts_tupleDescriptor->natts);
 }
 
 /*
@@ -1655,43 +1436,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);
-
-	attno = slot->tts_nvalid;
-
-	/*
-	 * If tuple doesn't have all the atts indicated by attnum, read the rest
-	 * as NULLs or missing values
-	 */
-	if (attno < attnum)
-		slot_getmissingattrs(slot, attno, attnum);
-
-	slot->tts_nvalid = attnum;
+	slot->tts_slottableam->slot_virtualize_tuple(slot, attnum);
 }
 
 /*
@@ -1702,42 +1447,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, tupleDesc);
-	}
+	bool		isnull;
 
-	/*
-	 * 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;
+	slot->tts_slottableam->slot_getattr(slot, attnum, &isnull, false);
 
-	/*
-	 * 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");
-
-	/* and let the tuple tell it */
-	return heap_attisnull(tuple, attnum, tupleDesc);
+	return isnull;
 }
 
 /*
@@ -1751,19 +1465,9 @@ bool
 slot_getsysattr(TupleTableSlot *slot, int attnum,
 				Datum *value, bool *isnull)
 {
-	HeapTuple	tuple = slot->tts_tuple;
 
-	Assert(attnum < 0);			/* else caller error */
-	if (tuple == NULL ||
-		tuple == &(slot->tts_minhdr))
-	{
-		/* No physical tuple, or minimal tuple, so fail */
-		*value = (Datum) 0;
-		*isnull = true;
-		return false;
-	}
-	*value = heap_getsysattr(tuple, attnum, slot->tts_tupleDescriptor, isnull);
-	return true;
+	*value = slot->tts_slottableam->slot_getattr(slot, attnum, isnull, true);
+	return *isnull ? false : true;
 }
 
 /*
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..121cdbff99
--- /dev/null
+++ b/src/backend/access/table/tableam_common.c
@@ -0,0 +1,462 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 (HeapTupleHeaderGetNatts(stuple->hst_heaptuple->t_data) <
+								slot->tts_tupleDescriptor->natts)
+			{
+				MemoryContext oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
+
+				tup = heap_expand_tuple(stuple->hst_heaptuple,
+										slot->tts_tupleDescriptor);
+				if (slot->tts_shouldFree)
+					heap_freetuple(stuple->hst_heaptuple);
+				stuple->hst_heaptuple = tup;
+				slot->tts_shouldFree = true;
+				MemoryContextSwitchTo(oldContext);
+			}
+
+			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
+		{
+			if (HeapTupleHeaderGetNatts(stuple->hst_heaptuple->t_data)
+				< slot->tts_tupleDescriptor->natts)
+				tup = minimal_expand_tuple(stuple->hst_heaptuple,
+											slot->tts_tupleDescriptor);
+			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);
+
+	attno = slot->tts_nvalid;
+
+	/*
+	 * If tuple doesn't have all the atts indicated by tupleDesc/upto attnum,
+	 * read the rest as NULLS or missing values.
+	 */
+	if (attno < upto)
+		slot_getmissingattrs(slot, attno, upto);
+
+	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, bool noerror)
+{
+	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 */
+		{
+			if (noerror)
+				goto no_error_return;
+			elog(ERROR, "cannot extract system attribute from virtual tuple");
+		}
+		if (tuple == &(stuple->hst_minhdr)) /* internal error */
+		{
+			if (noerror)
+				goto no_error_return;
+			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 or missing value 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))
+		return getmissingattr(slot->tts_tupleDescriptor, attnum, isnull);
+
+	/*
+	 * 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];
+
+no_error_return:
+	*isnull = true;
+	return (Datum) 0;
+}
+
+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 3a66cb5025..72de5274f2 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2712,7 +2712,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)
@@ -2786,7 +2786,7 @@ CopyFrom(CopyState cstate)
 							goto next_tuple;
 
 						/* FDW might have changed tuple */
-						tuple = ExecMaterializeSlot(slot);
+						tuple = ExecHeapifySlot(slot);
 
 						/*
 						 * AFTER ROW Triggers might reference the tableoid
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 e1eb7c374b..1359455579 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -484,7 +484,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 57519fe8d6..f46fd0a935 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2516,7 +2516,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;
@@ -2597,7 +2597,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;
@@ -2955,7 +2955,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;
@@ -2997,7 +2997,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 	if (newSlot != NULL)
 	{
 		slot = ExecFilterJunk(relinfo->ri_junkFilter, newSlot);
-		slottuple = ExecMaterializeSlot(slot);
+		slottuple = ExecHeapifySlot(slot);
 		newtuple = slottuple;
 	}
 
@@ -3111,7 +3111,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;
@@ -4246,14 +4246,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 9d6e25aae5..35b0da13ae 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -494,13 +494,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();
@@ -512,13 +514,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();
@@ -530,13 +533,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 41e857e378..ec42cf7801 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());
 
@@ -217,59 +217,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.
@@ -285,6 +232,7 @@ bool
 RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 						 TupleTableSlot *searchslot, TupleTableSlot *outslot)
 {
+	TupleTableSlot *scanslot;
 	HeapTuple	scantuple;
 	HeapScanDesc scan;
 	SnapshotData snap;
@@ -298,6 +246,8 @@ RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 	InitDirtySnapshot(snap);
 	scan = heap_beginscan(rel, &snap, 0, NULL);
 
+	scanslot = MakeSingleTupleTableSlot(desc);
+
 retry:
 	found = false;
 
@@ -306,12 +256,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;
@@ -335,7 +285,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());
 
@@ -374,6 +324,7 @@ retry:
 	}
 
 	heap_endscan(scan);
+	ExecDropSingleTupleTableSlot(scanslot);
 
 	return found;
 }
@@ -418,7 +369,7 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 			ExecPartitionCheck(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);
@@ -456,6 +407,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);
@@ -467,7 +419,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" */
@@ -485,21 +437,20 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 			ExecPartitionCheck(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);
@@ -519,6 +470,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);
@@ -530,7 +482,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);
 	}
 
@@ -539,11 +491,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 0beb7f80be..77fdc26fd0 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"
@@ -131,15 +132,16 @@ MakeTupleTableSlot(TupleDesc tupleDesc)
 	slot->tts_isempty = true;
 	slot->tts_shouldFree = false;
 	slot->tts_shouldFreeMin = false;
-	slot->tts_tuple = NULL;
 	slot->tts_fixedTupleDescriptor = tupleDesc != NULL;
 	slot->tts_tupleDescriptor = tupleDesc;
 	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;
 
 	if (tupleDesc != NULL)
 	{
@@ -236,6 +238,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
  *
@@ -353,7 +403,7 @@ ExecSetSlotDescriptor(TupleTableSlot *slot, /* slot to change */
  * --------------------------------
  */
 TupleTableSlot *
-ExecStoreTuple(HeapTuple tuple,
+ExecStoreTuple(void *tuple,
 			   TupleTableSlot *slot,
 			   Buffer buffer,
 			   bool shouldFree)
@@ -364,47 +414,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;
 }
 
@@ -431,31 +461,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;
@@ -480,25 +498,9 @@ ExecClearTuple(TupleTableSlot *slot)	/* slot in which to store tuple */
 	Assert(slot != NULL);
 
 	/*
-	 * Free the old physical tuple if necessary.
-	 */
-	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.
+	 * Tell the table AM to release any resource associated with the slot.
 	 */
-	if (BufferIsValid(slot->tts_buffer))
-		ReleaseBuffer(slot->tts_buffer);
-
-	slot->tts_buffer = InvalidBuffer;
+	slot->tts_slottableam->slot_clear_tuple(slot);
 
 	/*
 	 * Mark it empty.
@@ -577,7 +579,7 @@ ExecStoreAllNullTuple(TupleTableSlot *slot)
  *		however the "system columns" of the result will not be meaningful.
  * --------------------------------
  */
-HeapTuple
+TableTuple
 ExecCopySlotTuple(TupleTableSlot *slot)
 {
 	/*
@@ -586,20 +588,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);
 }
 
 /* --------------------------------
@@ -618,28 +607,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)
-	{
-		if (HeapTupleHeaderGetNatts(slot->tts_tuple->t_data)
-			< slot->tts_tupleDescriptor->natts)
-			return minimal_expand_tuple(slot->tts_tuple,
-										slot->tts_tupleDescriptor);
-		else
-			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);
 }
 
 /* --------------------------------
@@ -657,38 +637,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))
-	{
-		if (HeapTupleHeaderGetNatts(slot->tts_tuple->t_data) <
-			slot->tts_tupleDescriptor->natts)
-		{
-			HeapTuple	tuple;
-			MemoryContext oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
-
-			tuple = heap_expand_tuple(slot->tts_tuple,
-									  slot->tts_tupleDescriptor);
-			MemoryContextSwitchTo(oldContext);
-			slot = ExecStoreTuple(tuple, slot, InvalidBuffer, true);
-		}
-		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;
 }
 
 /* --------------------------------
@@ -708,6 +684,7 @@ MinimalTuple
 ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 {
 	MemoryContext oldContext;
+	MinimalTuple tup;
 
 	/*
 	 * sanity checks
@@ -715,11 +692,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.
@@ -729,18 +703,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;
 }
 
 /* --------------------------------
@@ -769,18 +736,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
@@ -788,12 +756,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.
@@ -803,18 +767,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
@@ -824,17 +780,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 a2a28b7ec2..869eebf274 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 85c67772de..4300ac44bd 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -181,7 +181,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;
@@ -281,7 +281,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
@@ -322,7 +322,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -335,7 +335,7 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 
 		newId = InvalidOid;
 	}
@@ -353,7 +353,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
@@ -697,7 +697,7 @@ ExecDelete(ModifyTableState *mtstate,
 		 */
 		if (slot->tts_isempty)
 			ExecStoreAllNullTuple(slot);
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
 	}
 	else
@@ -882,7 +882,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))
@@ -944,7 +944,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
@@ -963,7 +963,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW UPDATE Triggers */
@@ -977,7 +977,7 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;
 
 		/* trigger might have changed tuple */
-		tuple = ExecMaterializeSlot(slot);
+		tuple = ExecHeapifySlot(slot);
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
@@ -993,7 +993,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
@@ -1243,7 +1243,7 @@ lreplace:;
 					{
 						*tupleid = hufd.ctid;
 						slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
-						tuple = ExecMaterializeSlot(slot);
+						tuple = ExecHeapifySlot(slot);
 						goto lreplace;
 					}
 				}
@@ -1719,7 +1719,7 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
 	estate->es_result_relation_info = partrel;
 
 	/* Get the heap tuple out of the given slot. */
-	tuple = ExecMaterializeSlot(slot);
+	tuple = ExecHeapifySlot(slot);
 
 	/*
 	 * If we're capturing transition tuples, we might need to convert from the
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/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.c
index daae964b1c..bcfb270954 100644
--- a/src/backend/jit/llvm/llvmjit.c
+++ b/src/backend/jit/llvm/llvmjit.c
@@ -77,7 +77,8 @@ LLVMValueRef FuncStrlen;
 LLVMValueRef FuncVarsizeAny;
 LLVMValueRef FuncSlotGetsomeattrs;
 LLVMValueRef FuncSlotGetmissingattrs;
-LLVMValueRef FuncHeapGetsysattr;
+LLVMValueRef FuncSlotGetattr;
+LLVMValueRef FuncExecHeapifySlot;
 LLVMValueRef FuncMakeExpandedObjectReadOnlyInternal;
 LLVMValueRef FuncExecEvalArrayRefSubscript;
 LLVMValueRef FuncExecAggTransReparent;
@@ -815,7 +816,8 @@ llvm_create_types(void)
 	FuncVarsizeAny = LLVMGetNamedFunction(mod, "varsize_any");
 	FuncSlotGetsomeattrs = LLVMGetNamedFunction(mod, "slot_getsomeattrs");
 	FuncSlotGetmissingattrs = LLVMGetNamedFunction(mod, "slot_getmissingattrs");
-	FuncHeapGetsysattr = LLVMGetNamedFunction(mod, "heap_getsysattr");
+	FuncSlotGetattr = LLVMGetNamedFunction(mod, "slot_getattr");
+	FuncExecHeapifySlot = LLVMGetNamedFunction(mod, "ExecHeapifySlot");
 	FuncMakeExpandedObjectReadOnlyInternal = LLVMGetNamedFunction(mod, "MakeExpandedObjectReadOnlyInternal");
 	FuncExecEvalArrayRefSubscript = LLVMGetNamedFunction(mod, "ExecEvalArrayRefSubscript");
 	FuncExecAggTransReparent = LLVMGetNamedFunction(mod, "ExecAggTransReparent");
diff --git a/src/backend/jit/llvm/llvmjit_deform.c b/src/backend/jit/llvm/llvmjit_deform.c
index 795f67114e..ebe85cc1e7 100644
--- a/src/backend/jit/llvm/llvmjit_deform.c
+++ b/src/backend/jit/llvm/llvmjit_deform.c
@@ -167,13 +167,21 @@ slot_compile_deform(LLVMJitContext *context, TupleDesc desc, int natts)
 		l_load_struct_gep(b, v_slot, FIELDNO_TUPLETABLESLOT_ISNULL,
 						  "tts_ISNULL");
 
-	v_slotoffp = LLVMBuildStructGEP(b, v_slot, FIELDNO_TUPLETABLESLOT_OFF, "");
-	v_slowp = LLVMBuildStructGEP(b, v_slot, FIELDNO_TUPLETABLESLOT_SLOW, "");
+	v_slotoffp = l_sizet_const(0);
+	v_slowp = l_pbool_const(true);
 	v_nvalidp = LLVMBuildStructGEP(b, v_slot, FIELDNO_TUPLETABLESLOT_NVALID, "");
 
-	v_tupleheaderp =
-		l_load_struct_gep(b, v_slot, FIELDNO_TUPLETABLESLOT_TUPLE,
-						  "tupleheader");
+	{
+		LLVMValueRef v_params[1];
+
+		v_params[0] = v_slot;
+
+		v_tupleheaderp = LLVMBuildCall(b,
+										llvm_get_decl(mod, FuncExecHeapifySlot),
+										v_params, lengthof(v_params),
+										"");
+	}
+
 	v_tuplep =
 		l_load_struct_gep(b, v_tupleheaderp, FIELDNO_HEAPTUPLEDATA_DATA,
 						  "tuple");
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index 36c5f7d500..c8ecbdb766 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -408,9 +408,7 @@ llvm_compile_expr(ExprState *state)
 				{
 					int			attnum = op->d.var.attnum;
 					LLVMValueRef v_attnum;
-					LLVMValueRef v_tuple;
-					LLVMValueRef v_tupleDescriptor;
-					LLVMValueRef v_params[4];
+					LLVMValueRef v_params[3];
 					LLVMValueRef v_syscol;
 					LLVMValueRef v_slot;
 
@@ -423,26 +421,13 @@ llvm_compile_expr(ExprState *state)
 
 					Assert(op->d.var.attnum < 0);
 
-					v_tuple = l_load_struct_gep(b, v_slot,
-												FIELDNO_TUPLETABLESLOT_TUPLE,
-												"v.tuple");
-
-					/*
-					 * Could optimize this a bit for fixed descriptors, but
-					 * this shouldn't be that critical a path.
-					 */
-					v_tupleDescriptor =
-						l_load_struct_gep(b, v_slot,
-										  FIELDNO_TUPLETABLESLOT_TUPLEDESCRIPTOR,
-										  "v.tupledesc");
 					v_attnum = l_int32_const(attnum);
 
-					v_params[0] = v_tuple;
+					v_params[0] = v_slot;
 					v_params[1] = v_attnum;
-					v_params[2] = v_tupleDescriptor;
-					v_params[3] = v_resnullp;
+					v_params[2] = v_resnullp;
 					v_syscol = LLVMBuildCall(b,
-											 llvm_get_decl(mod, FuncHeapGetsysattr),
+											 llvm_get_decl(mod, FuncSlotGetattr),
 											 v_params, lengthof(v_params),
 											 "");
 					LLVMBuildStore(b, v_syscol, v_resvaluep);
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c
index 42304d0640..c3facd3e86 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -99,7 +99,8 @@ void	   *referenced_functions[] =
 	varsize_any,
 	slot_getsomeattrs,
 	slot_getmissingattrs,
-	heap_getsysattr,
+	slot_getattr,
+	ExecHeapifySlot,
 	MakeExpandedObjectReadOnlyInternal,
 	ExecEvalArrayRefSubscript,
 	ExecAggTransReparent,
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 0d2b795e39..091cfd00bd 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -751,9 +751,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 1867a70f6f..4db9303abb 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
@@ -665,7 +678,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.
@@ -796,6 +809,8 @@ extern Datum fastgetattr(HeapTuple tup, int attnum, TupleDesc tupleDesc,
 
 
 /* prototypes for functions in common/heaptuple.c */
+extern Datum getmissingattr(TupleDesc tupleDesc,
+			   int attnum, bool *isnull);
 extern Size heap_compute_data_size(TupleDesc tupleDesc,
 					   Datum *values, bool *isnull);
 extern void heap_fill_tuple(TupleDesc tupleDesc,
diff --git a/src/include/access/tableam_common.h b/src/include/access/tableam_common.h
index 78b24d76c7..fd44cd0b94 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, bool noerror);
+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 0b874d9763..fb39c3ef27 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
@@ -116,30 +128,24 @@ typedef struct TupleTableSlot
 	bool		tts_isempty;	/* true = slot is empty */
 	bool		tts_shouldFree; /* should pfree tts_tuple? */
 	bool		tts_shouldFreeMin;	/* should pfree tts_mintuple? */
-#define FIELDNO_TUPLETABLESLOT_SLOW 4
-	bool		tts_slow;		/* saved state for slot_deform_tuple */
-#define FIELDNO_TUPLETABLESLOT_TUPLE 5
-	HeapTuple	tts_tuple;		/* physical tuple, or NULL if virtual */
-#define FIELDNO_TUPLETABLESLOT_TUPLEDESCRIPTOR 6
+#define FIELDNO_TUPLETABLESLOT_TUPLEDESCRIPTOR 5
 	TupleDesc	tts_tupleDescriptor;	/* slot's tuple descriptor */
 	MemoryContext tts_mcxt;		/* slot itself is in this context */
-	Buffer		tts_buffer;		/* tuple's buffer, or InvalidBuffer */
-#define FIELDNO_TUPLETABLESLOT_NVALID 9
+#define FIELDNO_TUPLETABLESLOT_NVALID 7
 	int			tts_nvalid;		/* # of valid values in tts_values */
-#define FIELDNO_TUPLETABLESLOT_VALUES 10
+#define FIELDNO_TUPLETABLESLOT_VALUES 8
 	Datum	   *tts_values;		/* current per-attribute values */
-#define FIELDNO_TUPLETABLESLOT_ISNULL 11
+#define FIELDNO_TUPLETABLESLOT_ISNULL 9
 	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 */
-#define FIELDNO_TUPLETABLESLOT_OFF 14
-	uint32		tts_off;		/* saved state for slot_deform_tuple */
 	bool		tts_fixedTupleDescriptor;	/* descriptor can't be changed */
+	ItemPointerData tts_tid;	/* XXX describe */
+	Oid		tts_tableOid;	/* XXX describe */
+	Oid		tts_tupleOid;	/* XXX describe */
+	uint32		tts_speculativeToken;	/* XXX describe */
+	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?
  */
@@ -151,9 +157,10 @@ extern TupleTableSlot *MakeTupleTableSlot(TupleDesc desc);
 extern TupleTableSlot *ExecAllocTableSlot(List **tupleTable, TupleDesc desc);
 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);
@@ -163,12 +170,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);
 
diff --git a/src/include/jit/llvmjit.h b/src/include/jit/llvmjit.h
index b0093db49d..0be8ee16c6 100644
--- a/src/include/jit/llvmjit.h
+++ b/src/include/jit/llvmjit.h
@@ -79,7 +79,8 @@ extern LLVMValueRef FuncStrlen;
 extern LLVMValueRef FuncVarsizeAny;
 extern LLVMValueRef FuncSlotGetsomeattrs;
 extern LLVMValueRef FuncSlotGetmissingattrs;
-extern LLVMValueRef FuncHeapGetsysattr;
+extern LLVMValueRef FuncSlotGetattr;
+extern LLVMValueRef FuncExecHeapifySlot;
 extern LLVMValueRef FuncMakeExpandedObjectReadOnlyInternal;
 extern LLVMValueRef FuncExecEvalArrayRefSubscript;
 extern LLVMValueRef FuncExecAggTransReparent;
-- 
2.16.1.windows.4

0006-Tuple-Insert-API-is-added-to-table-AM.patchapplication/octet-stream; name=0006-Tuple-Insert-API-is-added-to-table-AM.patchDownload
From 26ce3dac2118614cd728c038e9a7bcfb80f3de2b Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:59:20 +1100
Subject: [PATCH 06/16] 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                 |  43 ++---
 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             | 146 +++++++-------
 src/backend/executor/execReplication.c      |  72 +++----
 src/backend/executor/nodeLockRows.c         |  47 +++--
 src/backend/executor/nodeModifyTable.c      | 229 ++++++++++------------
 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             |  74 +++++++-
 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, 973 insertions(+), 383 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 25e48deaa1..166209ec55 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -1064,6 +1064,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 ee407084c5..67b9b07337 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -1920,13 +1920,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;
@@ -1960,7 +1960,6 @@ heap_fetch(Relation relation,
 			ReleaseBuffer(buffer);
 			*userbuf = InvalidBuffer;
 		}
-		tuple->t_data = NULL;
 		return false;
 	}
 
@@ -1982,13 +1981,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);
@@ -2340,7 +2339,6 @@ heap_get_latest_tid(Relation relation,
 	}							/* end of loop */
 }
 
-
 /*
  * UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends
  *
@@ -4747,7 +4745,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)
 	{
@@ -5040,7 +5038,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;
@@ -5812,9 +5810,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
@@ -6174,14 +6171,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);
@@ -6236,6 +6237,7 @@ heap_finish_speculative(Relation relation, HeapTuple tuple)
 	END_CRIT_SECTION();
 
 	UnlockReleaseBuffer(buffer);
+	slot->tts_speculativeToken = 0;
 }
 
 /*
@@ -6265,8 +6267,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;
@@ -6275,6 +6279,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);
@@ -6388,6 +6396,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..006f604dbb 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, bool changingPart)
+{
+	/*
+	 * 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, changingPart);
+}
+
+
+/*
+ * 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 8d3c861a33..de696a04b8 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 cd42c50b09..7c427dce8a 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..af4e53b76e
--- /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, bool changingPart)
+{
+	return relation->rd_tableamroutine->tuple_delete(relation, tid, cid,
+												  crosscheck, wait, IndexFunc, hufd, changingPart);
+}
+
+/*
+ * 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 72de5274f2..020b857d74 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"
@@ -2711,8 +2712,6 @@ CopyFrom(CopyState cstate)
 
 			if (slot == NULL)	/* "do nothing" */
 				skip_tuple = true;
-			else				/* trigger might have changed tuple */
-				tuple = ExecHeapifySlot(slot);
 		}
 
 		if (!skip_tuple)
@@ -2796,20 +2795,14 @@ CopyFrom(CopyState cstate)
 						tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
 					}
 					else
-						heap_insert(resultRelInfo->ri_RelationDesc, tuple,
-									mycid, hi_options, bistate);
-
-					/* And create index entries for it */
-					if (resultRelInfo->ri_NumIndices > 0)
-						recheckIndexes = ExecInsertIndexTuples(slot,
-															   &(tuple->t_self),
-															   estate,
-															   false,
-															   NULL,
-															   NIL);
+					{
+						/* OK, store the tuple and create index entries for it */
+						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);
@@ -2887,7 +2880,7 @@ next_tuple:
 	 * indexes since those use WAL anyway)
 	 */
 	if (hi_options & HEAP_INSERT_SKIP_WAL)
-		heap_sync(cstate->rel);
+		table_sync(cstate->rel);
 
 	return processed;
 }
@@ -2920,12 +2913,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);
 
 	/*
@@ -2941,10 +2934,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);
 		}
@@ -2961,8 +2953,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 1359455579..b156f27259 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"
@@ -478,19 +479,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 */
 
@@ -509,7 +513,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 8b848f91a7..f3bae024b1 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -20,6 +20,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"
@@ -4825,7 +4826,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);
 
@@ -4849,7 +4851,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 f46fd0a935..a4ea981442 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"
@@ -2579,17 +2580,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 *
@@ -3244,9 +3249,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)
 	{
@@ -3262,11 +3268,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:
@@ -3303,7 +3309,8 @@ ltrmark:;
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("tuple to be locked was already moved to another partition 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;
@@ -3350,6 +3357,7 @@ ltrmark:;
 	{
 		Page		page;
 		ItemId		lp;
+		HeapTupleData tupledata;
 
 		buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 
@@ -3368,17 +3376,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;
 }
 
@@ -4186,8 +4194,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;
@@ -4260,10 +4268,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
@@ -4277,10 +4284,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 903076ee3c..486d6986eb 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 969944cc12..c677e4b19a 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"
@@ -1924,7 +1925,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 	 */
 	if (resultRelInfo->ri_PartitionRoot)
 	{
-		HeapTuple	tuple = ExecFetchSlotTuple(slot);
+		TableTuple	tuple = ExecFetchSlotTuple(slot);
 		TupleDesc	old_tupdesc = RelationGetDescr(rel);
 		TupleConversionMap *map;
 
@@ -2004,7 +2005,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				 */
 				if (resultRelInfo->ri_PartitionRoot)
 				{
-					HeapTuple	tuple = ExecFetchSlotTuple(slot);
+					TableTuple tuple = ExecFetchSlotTuple(slot);
 					TupleConversionMap *map;
 
 					rel = resultRelInfo->ri_PartitionRoot;
@@ -2051,7 +2052,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;
 
@@ -2506,7 +2507,8 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
 			 ItemPointer tid, TransactionId priorXmax)
 {
 	TupleTableSlot *slot;
-	HeapTuple	copyTuple;
+	TableTuple copyTuple;
+	tuple_data	t_data;
 
 	Assert(rti > 0);
 
@@ -2523,7 +2525,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.
@@ -2554,7 +2558,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
@@ -2587,14 +2591,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
@@ -2602,12 +2606,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;
@@ -2621,7 +2625,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);
@@ -2643,7 +2647,8 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 				{
 					case LockWaitBlock:
 						XactLockTableWait(SnapshotDirty.xmax,
-										  relation, &tuple.t_self,
+										  relation,
+										  tid,
 										  XLTW_FetchUpdated);
 						break;
 					case LockWaitSkip:
@@ -2672,20 +2677,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);
 
@@ -2725,12 +2733,15 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 								(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 								 errmsg("tuple to be locked was already moved to another partition due to concurrent update")));
 
+#if 0 //hari
 					/* Should not encounter speculative tuple on recheck */
 					Assert(!HeapTupleHeaderIsSpeculative(tuple.t_data));
-					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;
@@ -2753,10 +2764,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;
 		}
@@ -2765,7 +2772,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;
@@ -2774,7 +2781,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);
@@ -2795,13 +2802,17 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
 		 */
 
 		/* check whether next version would be in a different partition */
-		if (HeapTupleHeaderIndicatesMovedPartitions(tuple.t_data))
+		/* hari: FIXME: use a new table API */
+		if (HeapTupleHeaderIndicatesMovedPartitions(((HeapTuple) tuple)->t_data))
 			ereport(ERROR,
 					(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 					 errmsg("tuple to be locked was already moved to another partition due to concurrent update")));
 
+		t_data = table_tuple_get_data(relation, tuple, CTID);
+		ctid = t_data.tid;
+
 		/* check whether tuple has been deleted */
-		if (ItemPointerEquals(&tuple.t_self, &tuple.t_data->t_ctid))
+		if (ItemPointerEquals(tid, &ctid))
 		{
 			/* deleted, so forget about it */
 			ReleaseBuffer(buffer);
@@ -2809,17 +2820,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;
 }
 
 /*
@@ -2865,7 +2878,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;
 
@@ -2884,7 +2897,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;
@@ -2912,7 +2925,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");
@@ -2943,8 +2956,6 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 
 		if (erm->markType == ROW_MARK_REFERENCE)
 		{
-			HeapTuple	copyTuple;
-
 			Assert(erm->relation != NULL);
 
 			/* fetch the tuple's ctid */
@@ -2968,11 +2979,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");
 
 				/*
@@ -2986,32 +2997,28 @@ 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");
 
-				if (HeapTupleHeaderGetNatts(tuple.t_data) <
+				if (HeapTupleHeaderGetNatts(((HeapTuple)tuple)->t_data) <
 					RelationGetDescr(erm->relation)->natts)
 				{
-					copyTuple = heap_expand_tuple(&tuple,
+					TableTuple copyTuple = tuple;
+
+					tuple = heap_expand_tuple(copyTuple,
 												  RelationGetDescr(erm->relation));
+					heap_freetuple(copyTuple);
 				}
-				else
-				{
-					/* 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 */
@@ -3021,19 +3028,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);
+
 		}
 	}
 }
@@ -3202,8 +3202,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 ec42cf7801..53410c4cbf 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();
 
@@ -283,19 +284,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();
 
@@ -339,7 +341,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;
 
@@ -368,19 +369,12 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 		if (resultRelInfo->ri_PartitionCheck)
 			ExecPartitionCheck(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);
 
 		/*
@@ -404,7 +398,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);
@@ -429,6 +423,9 @@ 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)
@@ -436,17 +433,10 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 		if (resultRelInfo->ri_PartitionCheck)
 			ExecPartitionCheck(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 30de8a95ab..5c75e46547 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:
@@ -223,7 +228,8 @@ lnext:
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("tuple to be locked was already moved to another partition 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;
@@ -243,7 +249,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;
@@ -264,7 +271,7 @@ lnext:
 		}
 
 		/* Remember locked tuple's TID for EPQ testing and WHERE CURRENT OF */
-		erm->curCtid = tuple.t_self;
+		erm->curCtid = tid;
 	}
 
 	/*
@@ -286,7 +293,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 */
@@ -314,14 +321,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);
 		}
 
@@ -401,8 +406,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 4300ac44bd..99347845da 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"
@@ -174,15 +176,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;
 
@@ -201,7 +201,7 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
 static void
 ExecCheckHeapTupleVisible(EState *estate,
 						  Relation rel,
-						  HeapTuple tuple,
+						  TableTuple tuple,
 						  Buffer buffer)
 {
 	if (!IsolationUsesXactSnapshot())
@@ -214,13 +214,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")));
@@ -236,19 +238,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);
 }
 
 /* ----------------------------------------------------------------
@@ -267,7 +270,7 @@ ExecInsert(ModifyTableState *mtstate,
 		   EState *estate,
 		   bool canSetTag)
 {
-	HeapTuple	tuple;
+	TableTuple tuple;
 	ResultRelInfo *resultRelInfo;
 	Relation	resultRelationDesc;
 	Oid			newId;
@@ -277,32 +280,32 @@ ExecInsert(ModifyTableState *mtstate,
 	ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
 	OnConflictAction onconflict = node->onConflictAction;
 
-	/*
-	 * 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
 	 */
 	resultRelInfo = estate->es_result_relation_info;
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
+
+	ExecMaterializeSlot(slot);
+
 	/*
-	 * 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.
+	 * 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.
@@ -320,9 +323,6 @@ ExecInsert(ModifyTableState *mtstate,
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
-
-		/* trigger might have changed tuple */
-		tuple = ExecHeapifySlot(slot);
 	}
 
 	/* INSTEAD OF ROW INSERT Triggers */
@@ -334,9 +334,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)
@@ -352,14 +349,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;
 	}
@@ -371,7 +366,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 WITH CHECK policies.
@@ -414,7 +410,6 @@ ExecInsert(ModifyTableState *mtstate,
 			/* Perform a speculative insertion. */
 			uint32		specToken;
 			ItemPointerData conflictTid;
-			bool		specConflict;
 			List	   *arbiterIndexes;
 
 			arbiterIndexes = resultRelInfo->ri_onConflictArbiterIndexes;
@@ -432,7 +427,7 @@ ExecInsert(ModifyTableState *mtstate,
 			 * speculatively.
 			 */
 	vlock:
-			specConflict = false;
+			slot->tts_specConflict = false;
 			if (!ExecCheckIndexConstraints(slot, estate, &conflictTid,
 										   arbiterIndexes))
 			{
@@ -478,24 +473,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
@@ -511,7 +499,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;
@@ -523,19 +511,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,
-													   NIL);
+			newId = table_insert(resultRelationDesc, slot,
+								   estate->es_output_cid,
+								   0, NULL,
+								   ExecInsertIndexTuples,
+								   estate,
+								   NIL,
+								   &recheckIndexes);
 		}
 	}
 
@@ -543,7 +526,7 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		(estate->es_processed)++;
 		estate->es_lastoid = newId;
-		setLastTid(&(tuple->t_self));
+		setLastTid(&(slot->tts_tid));
 	}
 
 	/*
@@ -556,6 +539,7 @@ ExecInsert(ModifyTableState *mtstate,
 	if (mtstate->operation == CMD_UPDATE && mtstate->mt_transition_capture
 		&& mtstate->mt_transition_capture->tcs_update_new_table)
 	{
+		tuple = ExecHeapifySlot(slot);
 		ExecARUpdateTriggers(estate, resultRelInfo, NULL,
 							 NULL,
 							 tuple,
@@ -570,7 +554,7 @@ ExecInsert(ModifyTableState *mtstate,
 	}
 
 	/* AFTER ROW INSERT Triggers */
-	ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes,
+	ExecARInsertTriggers(estate, resultRelInfo, slot, recheckIndexes,
 						 ar_insert_trig_tcs);
 
 	list_free(recheckIndexes);
@@ -618,7 +602,7 @@ ExecInsert(ModifyTableState *mtstate,
 static TupleTableSlot *
 ExecDelete(ModifyTableState *mtstate,
 		   ItemPointer tupleid,
-		   HeapTuple oldtuple,
+		   TableTuple oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate,
@@ -670,8 +654,6 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else if (resultRelInfo->ri_FdwRoutine)
 	{
-		HeapTuple	tuple;
-
 		/*
 		 * delete from foreign table: let the FDW do it
 		 *
@@ -697,8 +679,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
 	{
@@ -712,10 +696,11 @@ ExecDelete(ModifyTableState *mtstate,
 		 * mode transactions.
 		 */
 ldelete:;
-		result = heap_delete(resultRelationDesc, tupleid,
+		result = table_delete(resultRelationDesc, tupleid,
 							 estate->es_output_cid,
 							 estate->es_crosscheck_snapshot,
 							 true /* wait for commit */ ,
+							 NULL,
 							 &hufd,
 							 changingPart);
 		switch (result)
@@ -846,7 +831,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)
@@ -860,20 +845,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);
@@ -882,7 +866,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))
@@ -919,14 +903,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;
@@ -992,14 +976,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
 	{
@@ -1010,7 +994,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
@@ -1174,11 +1158,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:
@@ -1254,26 +1241,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)
@@ -1331,11 +1298,12 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	ExprContext *econtext = mtstate->ps.ps_ExprContext;
 	Relation	relation = resultRelInfo->ri_RelationDesc;
 	ExprState  *onConflictSetWhere = resultRelInfo->ri_onConflict->oc_WhereClause;
-	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);
@@ -1346,10 +1314,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:
@@ -1374,7 +1340,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"),
@@ -1415,7 +1382,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:
@@ -1443,10 +1412,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
@@ -1461,7 +1430,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 */
 	}
@@ -1501,12 +1472,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;
 }
 
@@ -1926,7 +1899,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 e207b1ffb5..3d4e8d0093 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 41d540b46e..bb8a683b44 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 23f97df249..f660807147 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, bool changingPart);
-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 4db9303abb..2845d70cc2 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -825,6 +825,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..1df7adf209
--- /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, bool changingPart);
+
+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 fd44cd0b94..e5cc461bd8 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..8b9812068a 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,62 @@ 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,
+											 bool changingPart);
+
+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 +101,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 a5b8610fa2..2fe7ed33a5 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -191,7 +191,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 f82b51667f..406572771b 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -193,16 +193,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);
@@ -539,9 +539,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 fb39c3ef27..52576dbed2 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -142,6 +142,7 @@ typedef struct TupleTableSlot
 	Oid		tts_tableOid;	/* XXX describe */
 	Oid		tts_tupleOid;	/* XXX describe */
 	uint32		tts_speculativeToken;	/* XXX describe */
+	bool		tts_specConflict;	/* XXX describe */
 	struct SlotTableAmRoutine *tts_slottableam; /* table AM */
 	void	   *tts_storage;	/* table AM's opaque space */
 } TupleTableSlot;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index da7f52cab0..1c6778b3b8 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -555,7 +555,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 */
 
@@ -2107,7 +2107,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
@@ -2117,7 +2117,7 @@ typedef struct SharedHashInfo
 {
 	int			num_workers;
 	HashInstrumentation hinstrument[FLEXIBLE_ARRAY_MEMBER];
-} SharedHashInfo;
+}			SharedHashInfo;
 
 /* ----------------
  *	 HashState information
@@ -2178,7 +2178,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.16.1.windows.4

0007-Scan-functions-are-added-to-table-AM.patchapplication/octet-stream; name=0007-Scan-functions-are-added-to-table-AM.patchDownload
From 79b379ca574da7d18f5b867656caac5cba7fb696 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:59:20 +1100
Subject: [PATCH 07/16] 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/amcheck/verify_nbtree.c            |   2 +-
 contrib/pgrowlocks/pgrowlocks.c            |   6 +-
 contrib/pgstattuple/pgstattuple.c          |   6 +-
 src/backend/access/heap/heapam.c           | 223 ++++++++++-----------------
 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/nbtree/nbtsort.c        |   2 +-
 src/backend/access/table/tableam.c         | 233 +++++++++++++++++++++++++++++
 src/backend/bootstrap/bootstrap.c          |  25 ++--
 src/backend/catalog/aclchk.c               |  13 +-
 src/backend/catalog/index.c                |  49 +++---
 src/backend/catalog/partition.c            |   1 +
 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/partitioning/partbounds.c      |   7 +-
 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 +-
 58 files changed, 719 insertions(+), 453 deletions(-)

diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index 075e5548b9..70dbdcbd30 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -497,7 +497,7 @@ bt_check_every_level(Relation rel, Relation heaprel, bool readonly,
 		 *
 		 * Note that IndexBuildHeapScan() calls heap_endscan() for us.
 		 */
-		scan = heap_beginscan_strat(state->heaprel, /* relation */
+		scan = table_beginscan_strat(state->heaprel, /* relation */
 									snapshot,	/* snapshot */
 									0,	/* number of keys */
 									NULL,	/* scan key */
diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index b0ed27e883..6d47a446ea 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -125,7 +125,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
 			aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
 						   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 03f67c0beb..d9b08796c8 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -328,13 +328,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();
 
@@ -387,7 +387,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 67b9b07337..c8fee6a4cb 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -83,17 +83,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,
@@ -1394,87 +1383,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;
 
@@ -1544,9 +1462,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
 	 */
@@ -1557,27 +1482,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);
+	}
 }
 
 /* ----------------
@@ -1675,36 +1594,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);
-
-	if (!parallel_scan->phs_snapshot_any)
-	{
-		/* Snapshot was serialized -- restore it */
-		snapshot = RestoreSnapshot(parallel_scan->phs_snapshot_data);
-		RegisterSnapshot(snapshot);
-	}
-	else
-	{
-		/* SnapshotAny passed by caller (not serialized) */
-		snapshot = SnapshotAny;
-	}
-
-	return heap_beginscan_internal(relation, snapshot, 0, NULL, parallel_scan,
-								   true, true, true, false, false,
-								   !parallel_scan->phs_snapshot_any);
-}
-
 /* ----------------
  *		heap_parallelscan_startblock_init - find and set the scan's startblock
  *
@@ -1849,8 +1738,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 */
@@ -1880,6 +1768,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 006f604dbb..010fef208e 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 c8e06fdef3..4b709a65ac 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"
@@ -397,9 +398,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;
 	}
 
@@ -435,7 +436,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;
 }
@@ -507,7 +508,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 22b5cc921f..c6dc0bed8a 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/index.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 907cce0724..1b47e76049 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"
@@ -426,8 +427,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;
 
@@ -480,7 +481,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/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c
index 89c2ba3285..106373fb7d 100644
--- a/src/backend/access/nbtree/nbtsort.c
+++ b/src/backend/access/nbtree/nbtsort.c
@@ -1685,7 +1685,7 @@ _bt_parallel_scan_and_sort(BTSpool *btspool, BTSpool *btspool2,
 	/* Join parallel scan */
 	indexInfo = BuildIndexInfo(btspool->index);
 	indexInfo->ii_Concurrent = btshared->isconcurrent;
-	scan = heap_beginscan_parallel(btspool->heap, &btshared->heapdesc);
+	scan = table_beginscan_parallel(btspool->heap, &btshared->heapdesc);
 	reltuples = IndexBuildHeapScan(btspool->heap, btspool->index, indexInfo,
 								   true, _bt_build_callback,
 								   (void *) &buildstate, scan);
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index af4e53b76e..ace187ba24 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,184 @@ 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);
+
+	if (!parallel_scan->phs_snapshot_any)
+	{
+		/* Snapshot was serialized -- restore it */
+		snapshot = RestoreSnapshot(parallel_scan->phs_snapshot_data);
+		RegisterSnapshot(snapshot);
+	}
+	else
+	{
+		/* SnapshotAny passed by caller (not serialized) */
+		snapshot = SnapshotAny;
+	}
+
+	return relation->rd_tableamroutine->scan_begin(relation, snapshot, 0, NULL, parallel_scan,
+												true, true, true, false, false, !parallel_scan->phs_snapshot_any);
+}
+
+/*
+ * 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 +267,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 7e34bee63e..dde8c1f0a7 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"
@@ -588,18 +589,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,
@@ -607,7 +608,7 @@ boot_openrel(char *relname)
 				   sizeof((*app)->am_typ));
 			app++;
 		}
-		heap_endscan(scan);
+		table_endscan(scan);
 		heap_close(rel, NoLock);
 	}
 
@@ -918,25 +919,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 578e4c6592..08ef93a8eb 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"
@@ -844,14 +845,14 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
 									CharGetDatum(PROKIND_PROCEDURE));
 
 					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;
@@ -889,14 +890,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 9286e78bcc..0f31815638 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -27,6 +27,7 @@
 #include "access/multixact.h"
 #include "access/relscan.h"
 #include "access/reloptions.h"
+#include "access/tableam.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
 #include "access/visibilitymap.h"
@@ -2131,10 +2132,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
 	{
@@ -2530,7 +2531,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		else
 			snapshot = SnapshotAny;
 
-		scan = heap_beginscan_strat(heapRelation,	/* relation */
+		scan = table_beginscan_strat(heapRelation,	/* relation */
 									snapshot,	/* snapshot */
 									0,	/* number of keys */
 									NULL,	/* scan key */
@@ -2566,7 +2567,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 
 	/* 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 */
@@ -2579,7 +2580,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;
 
@@ -2923,7 +2924,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		}
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 
 	/* we can now forget our snapshot, if set and registered by us */
 	if (need_unregister_snapshot)
@@ -2994,14 +2995,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();
 
@@ -3037,7 +3038,7 @@ IndexCheckExclusion(Relation heapRelation,
 								   estate, true);
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	UnregisterSnapshot(snapshot);
 
 	ExecDropSingleTupleTableSlot(slot);
@@ -3314,17 +3315,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;
@@ -3483,7 +3484,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 558022647c..4e6c5df158 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -17,6 +17,7 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
+#include "access/tableam.h"
 #include "access/tupconvert.h"
 #include "access/sysattr.h"
 #include "catalog/indexing.h"
diff --git a/src/backend/catalog/pg_conversion.c b/src/backend/catalog/pg_conversion.c
index fd5c18426b..86f426ef32 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"
@@ -160,14 +161,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 ec3bd1d22d..e565a14418 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"
@@ -333,9 +334,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);
@@ -344,7 +345,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 8705d8b1d3..4e42b10c47 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"
@@ -417,12 +418,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 5e9462c63e..fcaec72026 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -929,7 +929,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;
 	}
 
@@ -979,7 +979,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;
 
@@ -1065,7 +1065,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
@@ -1712,8 +1712,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);
 
@@ -1733,7 +1733,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 020b857d74..0fbfcf1c78 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2052,10 +2052,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();
 
@@ -2067,7 +2067,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 5342f217c0..1ccc123b61 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 3a3223bffb..b830dc9992 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"
@@ -2365,8 +2366,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);
@@ -2412,7 +2413,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 f3bae024b1..398d5d27e3 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4713,7 +4713,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
@@ -4721,7 +4721,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)
 			{
@@ -4835,7 +4835,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		}
 
 		MemoryContextSwitchTo(oldCxt);
-		heap_endscan(scan);
+		table_endscan(scan);
 		UnregisterSnapshot(snapshot);
 
 		ExecDropSingleTupleTableSlot(oldslot);
@@ -5241,9 +5241,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,
@@ -5255,7 +5255,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;
@@ -8476,7 +8476,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
@@ -8484,7 +8484,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);
 
@@ -8499,7 +8499,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	}
 
 	MemoryContextSwitchTo(oldcxt);
-	heap_endscan(scan);
+	table_endscan(scan);
 	UnregisterSnapshot(snapshot);
 	ExecDropSingleTupleTableSlot(slot);
 	FreeExecutorState(estate);
@@ -8554,9 +8554,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;
@@ -8585,7 +8585,7 @@ validateForeignKeyConstraint(char *conname,
 		RI_FKey_check_ins(&fcinfo);
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	UnregisterSnapshot(snapshot);
 }
 
@@ -11136,8 +11136,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;
@@ -11197,7 +11197,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 f7e9160a4f..dd721c63a9 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"
@@ -418,8 +419,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))
 	{
@@ -436,7 +437,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;
@@ -463,7 +464,7 @@ DropTableSpace(DropTableSpaceStmt *stmt)
 	 */
 	CatalogTupleDelete(rel, &tuple->t_self);
 
-	heap_endscan(scandesc);
+	table_endscan(scandesc);
 
 	/*
 	 * Remove any comments or security labels on this tablespace.
@@ -927,8 +928,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),
@@ -939,7 +940,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()))
@@ -957,15 +958,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);
@@ -1007,8 +1008,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),
@@ -1049,7 +1050,7 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt)
 	heap_freetuple(newtuple);
 
 	/* Conclude heap scan. */
-	heap_endscan(scandesc);
+	table_endscan(scandesc);
 	heap_close(rel, NoLock);
 
 	return tablespaceoid;
@@ -1398,8 +1399,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))
@@ -1407,7 +1408,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)
@@ -1444,8 +1445,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))
@@ -1453,7 +1454,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 175ecc8b48..6bd67639d1 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"
@@ -2383,8 +2384,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;
 
@@ -2413,7 +2414,7 @@ AlterDomainNotNull(List *names, bool notNull)
 					}
 				}
 			}
-			heap_endscan(scan);
+			table_endscan(scan);
 			UnregisterSnapshot(snapshot);
 
 			/* Close each rel after processing, but keep lock */
@@ -2779,8 +2780,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;
 
@@ -2823,7 +2824,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 d90cb9a902..5f2069902a 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;
@@ -1190,9 +1191,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;
@@ -1229,7 +1230,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 486d6986eb..26c8e5065e 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			indnkeyatts = IndexRelationGetNumberOfKeyAttributes(index);
 	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 53410c4cbf..b4fa287933 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;
@@ -234,8 +234,7 @@ RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 						 TupleTableSlot *searchslot, TupleTableSlot *outslot)
 {
 	TupleTableSlot *scanslot;
-	HeapTuple	scantuple;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	SnapshotData snap;
 	TransactionId xwait;
 	bool		found;
@@ -245,19 +244,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;
 
@@ -325,7 +324,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 77fdc26fd0..bc7dd3ecc1 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -720,7 +720,7 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 Datum
 ExecFetchSlotTupleDatum(TupleTableSlot *slot)
 {
-	HeapTuple	tup;
+	TableTuple tup;
 	TupleDesc	tupdesc;
 
 	/* Fetch slot's contents in regular-physical-tuple form */
@@ -804,7 +804,7 @@ ExecHeapifySlot(TupleTableSlot *slot)
 TupleTableSlot *
 ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
 {
-	HeapTuple	newTuple;
+	TableTuple newTuple;
 	MemoryContext oldContext;
 
 	/*
@@ -1145,7 +1145,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;
@@ -1153,7 +1153,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 23545896d4..b19abe6783 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)
 {
@@ -597,7 +597,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 0fe0c22c1e..6366a3fb1f 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -2517,7 +2517,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,
@@ -2642,7 +2642,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 bdb82db149..45c9baf6c8 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"
@@ -431,8 +432,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);
 		}
 	}
@@ -742,7 +743,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)
@@ -832,7 +833,7 @@ ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 	/*
 	 * close heap scan
 	 */
-	heap_endscan(scanDesc);
+	table_endscan(scanDesc);
 
 	/*
 	 * close the heap relation.
@@ -963,10 +964,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);
 
 	/*
 	 * all done.
@@ -1114,5 +1115,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 869eebf274..a2e6470126 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 cdc9c51bd1..5a0e33a752 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);
 
 
@@ -245,7 +245,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)
 	{
@@ -289,7 +289,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;
@@ -297,7 +297,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 a0b3334bed..158549dd6c 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);
@@ -399,7 +399,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] =
@@ -616,7 +616,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
@@ -691,12 +691,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 3a02a99621..379f992a80 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 d6012192a1..1da9ba8908 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;
 
 	/*
@@ -184,7 +184,7 @@ IndexNextWithReorder(IndexScanState *node)
 	EState	   *estate;
 	ExprContext *econtext;
 	IndexScanDesc scandesc;
-	HeapTuple	tuple;
+	TableTuple tuple;
 	TupleTableSlot *slot;
 	ReorderTuple *topmost = NULL;
 	bool		was_exact;
@@ -478,7 +478,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;
@@ -511,10 +511,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 950f88100c..b3eda4e751 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -28,10 +28,12 @@
 
 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
  * ----------------------------------------------------------------
@@ -46,7 +48,7 @@ static bool SampleTupleVisible(HeapTuple tuple, OffsetNumber tupoffset,
 static TupleTableSlot *
 SampleNext(SampleScanState *node)
 {
-	HeapTuple	tuple;
+	TableTuple tuple;
 	TupleTableSlot *slot;
 
 	/*
@@ -222,7 +224,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.
@@ -327,19 +329,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);
@@ -354,7 +356,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;
@@ -532,7 +534,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 9db368922a..758dbeb9c7 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"
@@ -48,8 +49,7 @@ static TupleTableSlot *SeqNext(SeqScanState *node);
 static TupleTableSlot *
 SeqNext(SeqScanState *node)
 {
-	HeapTuple	tuple;
-	HeapScanDesc scandesc;
+	TableScanDesc scandesc;
 	EState	   *estate;
 	ScanDirection direction;
 	TupleTableSlot *slot;
@@ -68,35 +68,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);
 }
 
 /*
@@ -203,7 +184,7 @@ void
 ExecEndSeqScan(SeqScanState *node)
 {
 	Relation	relation;
-	HeapScanDesc scanDesc;
+	TableScanDesc scanDesc;
 
 	/*
 	 * get information from node
@@ -226,7 +207,7 @@ ExecEndSeqScan(SeqScanState *node)
 	 * close heap scan
 	 */
 	if (scanDesc != NULL)
-		heap_endscan(scanDesc);
+		table_endscan(scanDesc);
 
 	/*
 	 * close the heap relation.
@@ -248,13 +229,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);
 }
@@ -301,7 +282,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);
 }
 
 /* ----------------------------------------------------------------
@@ -333,5 +314,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 968d5d3771..84f9c94cae 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -2590,7 +2590,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;
@@ -2673,7 +2673,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 22dd55c378..79e6680216 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -723,11 +723,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)
 	{
@@ -751,7 +751,7 @@ SPI_copytuple(HeapTuple tuple)
 }
 
 HeapTupleHeader
-SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc)
+SPI_returntuple(TableTuple tuple, TupleDesc tupdesc)
 {
 	MemoryContext oldcxt;
 	HeapTupleHeader dtup;
@@ -782,7 +782,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)
 {
@@ -950,7 +950,7 @@ char *
 SPI_gettype(TupleDesc tupdesc, int fnumber)
 {
 	Oid			typoid;
-	HeapTuple	typeTuple;
+	TableTuple typeTuple;
 	char	   *result;
 
 	SPI_result = 0;
@@ -1058,7 +1058,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);
@@ -1779,7 +1779,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);
@@ -1810,8 +1810,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/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
index b19c76acc8..8728d90b1c 100644
--- a/src/backend/partitioning/partbounds.c
+++ b/src/backend/partitioning/partbounds.c
@@ -13,6 +13,7 @@
 */
 #include "postgres.h"
 
+#include "access/tableam.h"
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
@@ -704,7 +705,7 @@ check_default_partition_contents(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);
 
 		/*
@@ -713,7 +714,7 @@ check_default_partition_contents(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;
@@ -729,7 +730,7 @@ check_default_partition_contents(Relation parent, Relation default_rel,
 		}
 
 		MemoryContextSwitchTo(oldCxt);
-		heap_endscan(scan);
+		table_endscan(scan);
 		UnregisterSnapshot(snapshot);
 		ExecDropSingleTupleTableSlot(tupslot);
 		FreeExecutorState(estate);
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 02e6d8131e..6a875c6d82 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1896,9 +1896,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;
@@ -1925,7 +1925,7 @@ get_database_list(void)
 		MemoryContextSwitchTo(oldcxt);
 	}
 
-	heap_endscan(scan);
+	table_endscan(scan);
 	heap_close(rel, AccessShareLock);
 
 	CommitTransactionCommand();
@@ -2056,13 +2056,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;
@@ -2148,7 +2148,7 @@ do_autovacuum(void)
 		}
 	}
 
-	heap_endscan(relScan);
+	table_endscan(relScan);
 
 	/* second pass: check TOAST tables */
 	ScanKeyInit(&key,
@@ -2156,8 +2156,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;
@@ -2203,7 +2203,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 084573e77c..a0ca9ee127 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 6ef333b725..d98a3e891f 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 d81a2ea342..0992fb7fd8 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 09e0df290d..99841b669b 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"
@@ -1218,10 +1219,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 f660807147..429b065634 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 1df7adf209..f3982c3c13 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 e5cc461bd8..36b72e9767 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 8b9812068a..2ab3ba62d1 100644
--- a/src/include/access/tableamapi.h
+++ b/src/include/access/tableamapi.h
@@ -82,6 +82,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.
@@ -115,6 +148,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 a309809ba8..fbf3c898f7 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 143a89a16c..cbf372ffcb 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;
@@ -120,10 +120,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);
@@ -136,7 +136,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 01aa208c5e..0f3e86d81f 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -238,7 +238,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.16.1.windows.4

0012-Table-AM-shared-memory-API.patchapplication/octet-stream; name=0012-Table-AM-shared-memory-API.patchDownload
From 05ff50ecb50e7016cf574801f7142c88a7486c0c Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:17:54 +1100
Subject: [PATCH 12/16] 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 062e184095..1bfb1942b5 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -509,6 +509,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)
@@ -543,6 +554,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 39b0f990c4..37a1c12c86 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
  */
@@ -99,6 +100,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 581494df04..bf3cf688cb 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -490,7 +490,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 26d882eb00..c6dfd80c04 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 7227f834a5..d29559395b 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 4897f1407e..982ba77ec2 100644
--- a/src/include/access/tableamapi.h
+++ b/src/include/access/tableamapi.h
@@ -107,7 +107,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? */
@@ -132,7 +132,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.
@@ -179,6 +178,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.16.1.windows.4

0008-Remove-HeapScanDesc-usage-outside-heap.patchapplication/octet-stream; name=0008-Remove-HeapScanDesc-usage-outside-heap.patchDownload
From 5c87ea2183ce0f2bfae4e6aa11d2583fabbf3dcd Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:59:20 +1100
Subject: [PATCH 08/16] 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/amcheck/verify_nbtree.c            |   2 +-
 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/nbtree/nbtsort.c        |   2 +-
 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                |  10 +-
 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/partitioning/partbounds.c      |   2 +-
 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/catalog/index.h                |   4 +-
 src/include/nodes/execnodes.h              |   4 +-
 43 files changed, 523 insertions(+), 404 deletions(-)

diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index 70dbdcbd30..e30513ddd9 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -478,7 +478,7 @@ bt_check_every_level(Relation rel, Relation heaprel, bool readonly,
 	if (state->heapallindexed)
 	{
 		IndexInfo  *indexinfo = BuildIndexInfo(state->rel);
-		HeapScanDesc scan;
+		TableScanDesc scan;
 
 		/* Report on extra downlink checks performed in readonly case */
 		if (state->readonly)
diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index 6d47a446ea..cba2e63f13 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 d9b08796c8..bd022e031a 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -317,7 +317,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 */
@@ -331,7 +332,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)
@@ -367,7 +369,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);
@@ -380,7 +382,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 c8fee6a4cb..c605d8a2b8 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -224,9 +224,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
@@ -240,8 +240,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;
@@ -252,20 +252,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)
 	{
@@ -274,25 +274,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 */
 
@@ -300,7 +300,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
@@ -308,7 +308,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);
 }
 
 /*
@@ -318,16 +318,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;
 }
 
 /*
@@ -338,8 +341,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;
@@ -349,13 +353,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;
 	}
 
 	/*
@@ -366,20 +370,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
@@ -389,7 +393,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;
 
@@ -424,7 +428,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);
@@ -432,20 +436,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;
 }
 
 /* ----------------
@@ -478,7 +482,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;
@@ -493,14 +497,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;
 			}
@@ -513,29 +517,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 */
 
@@ -546,14 +550,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;
 			}
@@ -564,30 +568,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
 		{
@@ -603,20 +607,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));
@@ -647,21 +651,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;
 				}
 			}
@@ -686,17 +690,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)
@@ -707,10 +711,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
@@ -724,8 +728,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);
 		}
 
 		/*
@@ -733,21 +737,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)
@@ -798,14 +802,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;
 			}
@@ -818,28 +822,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;
@@ -849,14 +853,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;
 			}
@@ -867,33 +871,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 */
 
@@ -904,20 +908,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));
@@ -926,8 +930,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;
 	}
@@ -940,7 +944,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));
 
@@ -951,7 +955,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.
@@ -960,19 +964,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;
 				}
 			}
@@ -991,7 +995,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
@@ -999,10 +1003,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)
@@ -1013,10 +1017,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
@@ -1030,8 +1034,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);
 		}
 
 		/*
@@ -1039,21 +1043,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;
@@ -1383,7 +1387,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,
@@ -1410,12 +1414,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;
@@ -1424,7 +1428,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
@@ -1448,13 +1452,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;
 }
 
 /* ----------------
@@ -1462,21 +1466,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
@@ -1507,29 +1513,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);
 }
@@ -1634,7 +1642,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;
 		}
 	}
@@ -1676,10 +1684,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.
@@ -1688,12 +1696,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;
@@ -1706,12 +1714,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;
 }
 
@@ -1739,17 +1749,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)
 	{
@@ -1763,7 +1775,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));
 }
@@ -1771,7 +1783,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 \
@@ -1783,7 +1795,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;
 
@@ -1791,11 +1803,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)
 	{
@@ -1810,7 +1822,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 010fef208e..e52ff51190 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 4b709a65ac..736701bfe9 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -481,10 +481,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/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c
index 106373fb7d..1187900373 100644
--- a/src/backend/access/nbtree/nbtsort.c
+++ b/src/backend/access/nbtree/nbtsort.c
@@ -1632,7 +1632,7 @@ _bt_parallel_scan_and_sort(BTSpool *btspool, BTSpool *btspool2,
 {
 	SortCoordinate coordinate;
 	BTBuildState buildstate;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	double		reltuples;
 	IndexInfo  *indexInfo;
 
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index ace187ba24..ec5fe5f45a 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;
@@ -79,6 +79,25 @@ table_beginscan_parallel(Relation relation, ParallelHeapScanDesc parallel_scan)
 												true, true, true, false, false, !parallel_scan->phs_snapshot_any);
 }
 
+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
  *
@@ -86,7 +105,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);
 }
@@ -105,18 +124,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)
 {
@@ -124,7 +143,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);
@@ -134,7 +153,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)
@@ -144,7 +163,7 @@ table_beginscan_strat(Relation relation, Snapshot snapshot,
 												false, false, false);
 }
 
-HeapScanDesc
+TableScanDesc
 table_beginscan_bm(Relation relation, Snapshot snapshot,
 					 int nkeys, ScanKey key)
 {
@@ -152,7 +171,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)
@@ -167,7 +186,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);
@@ -183,7 +202,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,
@@ -198,7 +217,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);
 }
@@ -211,23 +230,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 dde8c1f0a7..988eb4f6b3 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -579,7 +579,7 @@ boot_openrel(char *relname)
 	int			i;
 	struct typmap **app;
 	Relation	rel;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tup;
 
 	if (strlen(relname) >= NAMEDATALEN)
@@ -895,7 +895,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 08ef93a8eb..afae614f91 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -823,7 +823,7 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
 					ScanKeyData key[2];
 					int			keycount;
 					Relation	rel;
-					HeapScanDesc scan;
+					TableScanDesc scan;
 					HeapTuple	tuple;
 
 					keycount = 0;
@@ -877,7 +877,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 0f31815638..2691095b87 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2124,7 +2124,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],
@@ -2422,7 +2422,7 @@ IndexBuildHeapScan(Relation heapRelation,
 				   bool allow_sync,
 				   IndexBuildCallback callback,
 				   void *callback_state,
-				   HeapScanDesc scan)
+				   TableScanDesc scan)
 {
 	return IndexBuildHeapRangeScan(heapRelation, indexRelation,
 								   indexInfo, allow_sync,
@@ -2451,7 +2451,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 						BlockNumber numblocks,
 						IndexBuildCallback callback,
 						void *callback_state,
-						HeapScanDesc scan)
+						TableScanDesc scan)
 {
 	bool		is_system_catalog;
 	bool		checking_uniqueness;
@@ -2959,7 +2959,7 @@ IndexCheckExclusion(Relation heapRelation,
 					Relation indexRelation,
 					IndexInfo *indexInfo)
 {
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
@@ -3273,7 +3273,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/pg_conversion.c b/src/backend/catalog/pg_conversion.c
index 86f426ef32..8ae4b6629b 100644
--- a/src/backend/catalog/pg_conversion.c
+++ b/src/backend/catalog/pg_conversion.c
@@ -150,7 +150,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 e565a14418..cf55518137 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -323,7 +323,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 4e42b10c47..9150439861 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -392,7 +392,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 fcaec72026..2148b44ee3 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -763,7 +763,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;
@@ -1693,7 +1693,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 0fbfcf1c78..f9af5acda6 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2046,7 +2046,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 1ccc123b61..b9f33f1e29 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 b830dc9992..8533f37212 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2292,7 +2292,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 398d5d27e3..bd9280b20a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4654,7 +4654,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;
@@ -5230,7 +5230,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;
 
@@ -8435,7 +8435,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	Expr	   *origexpr;
 	ExprState  *exprstate;
 	TupleDesc	tupdesc;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tuple;
 	ExprContext *econtext;
 	MemoryContext oldcxt;
@@ -8518,7 +8518,7 @@ validateForeignKeyConstraint(char *conname,
 							 Oid pkindOid,
 							 Oid constraintOid)
 {
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tuple;
 	Trigger		trig;
 	Snapshot	snapshot;
@@ -11071,7 +11071,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 dd721c63a9..0e7e2d65d2 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -404,7 +404,7 @@ DropTableSpace(DropTableSpaceStmt *stmt)
 {
 #ifdef HAVE_SYMLINK
 	char	   *tablespacename = stmt->tablespacename;
-	HeapScanDesc scandesc;
+	TableScanDesc scandesc;
 	Relation	rel;
 	HeapTuple	tuple;
 	ScanKeyData entry[1];
@@ -915,7 +915,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;
@@ -990,7 +990,7 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt)
 {
 	Relation	rel;
 	ScanKeyData entry[1];
-	HeapScanDesc scandesc;
+	TableScanDesc scandesc;
 	HeapTuple	tup;
 	Oid			tablespaceoid;
 	Datum		datum;
@@ -1384,7 +1384,7 @@ get_tablespace_oid(const char *tablespacename, bool missing_ok)
 {
 	Oid			result;
 	Relation	rel;
-	HeapScanDesc scandesc;
+	TableScanDesc scandesc;
 	HeapTuple	tuple;
 	ScanKeyData entry[1];
 
@@ -1430,7 +1430,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 6bd67639d1..661ebda56b 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2378,7 +2378,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;
 
@@ -2774,7 +2774,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 5f2069902a..e3da71f9bc 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);
@@ -1160,7 +1160,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 45c9baf6c8..7e29030dfd 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);
@@ -381,8 +373,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;
@@ -391,7 +385,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,
@@ -434,7 +428,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
@@ -450,23 +444,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);
 		}
 	}
@@ -474,7 +466,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 
 	Assert(ntup <= MaxHeapTuplesPerPage);
-	scan->rs_ntuples = ntup;
+	pagescan->rs_ntuples = ntup;
 }
 
 /*
@@ -600,7 +592,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;
@@ -788,7 +780,7 @@ void
 ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 {
 	Relation	relation;
-	HeapScanDesc scanDesc;
+	TableScanDesc scanDesc;
 
 	/*
 	 * extract information from the node
@@ -969,6 +961,8 @@ ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags)
 															0,
 															NULL);
 
+	scanstate->pagescan = tableam_get_heappagescandesc(scanstate->ss.ss_currentScanDesc);
+
 	/*
 	 * all done.
 	 */
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index b3eda4e751..581494df04 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -30,9 +30,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
@@ -335,6 +334,7 @@ tablesample_init(SampleScanState *scanstate)
 									   scanstate->use_bulkread,
 									   allow_sync,
 									   scanstate->use_pagemode);
+		scanstate->pagescan = tableam_get_heappagescandesc(scanstate->ss.ss_currentScanDesc);
 	}
 	else
 	{
@@ -360,10 +360,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;
@@ -374,10 +375,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)
@@ -385,13 +385,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;
 	}
@@ -434,14 +433,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)
@@ -472,14 +469,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;
 
 			/*
@@ -492,10 +489,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);
 		}
 
 		/*
@@ -507,12 +504,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 */
@@ -527,16 +523,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,
@@ -548,12 +547,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 758dbeb9c7..b2b0a30343 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -295,9 +295,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 8369e3ad62..f3cd64cf62 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"
@@ -272,7 +273,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/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
index 8728d90b1c..4beb41f48f 100644
--- a/src/backend/partitioning/partbounds.c
+++ b/src/backend/partitioning/partbounds.c
@@ -646,7 +646,7 @@ check_default_partition_contents(Relation parent, Relation default_rel,
 		Snapshot	snapshot;
 		TupleDesc	tupdesc;
 		ExprContext *econtext;
-		HeapScanDesc scan;
+		TableScanDesc scan;
 		MemoryContext oldCxt;
 		TupleTableSlot *tupslot;
 
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6a875c6d82..a38ba57a04 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1878,7 +1878,7 @@ get_database_list(void)
 {
 	List	   *dblist = NIL;
 	Relation	rel;
-	HeapScanDesc scan;
+	TableScanDesc scan;
 	HeapTuple	tup;
 	MemoryContext resultcxt;
 
@@ -1944,7 +1944,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 a0ca9ee127..dc292aa0e0 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 d98a3e891f..8a8438286c 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 0992fb7fd8..2a2d890c0b 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 99841b669b..0c5551730f 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -1214,7 +1214,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 429b065634..6aca0af7d4 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 e5289b8aa7..6a756abbe2 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"
@@ -43,40 +44,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;
 
 /*
@@ -150,12 +165,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 f3982c3c13..afe8eeb4fe 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 36b72e9767..e5cc461bd8 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 2ab3ba62d1..29f83e0ab2 100644
--- a/src/include/access/tableamapi.h
+++ b/src/include/access/tableamapi.h
@@ -83,7 +83,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,
@@ -93,22 +93,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,
@@ -150,9 +157,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/catalog/index.h b/src/include/catalog/index.h
index f20c5f789b..6cab64df10 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -116,7 +116,7 @@ extern double IndexBuildHeapScan(Relation heapRelation,
 				   bool allow_sync,
 				   IndexBuildCallback callback,
 				   void *callback_state,
-				   HeapScanDesc scan);
+				   TableScanDesc scan);
 extern double IndexBuildHeapRangeScan(Relation heapRelation,
 						Relation indexRelation,
 						IndexInfo *indexInfo,
@@ -126,7 +126,7 @@ extern double IndexBuildHeapRangeScan(Relation heapRelation,
 						BlockNumber end_blockno,
 						IndexBuildCallback callback,
 						void *callback_state,
-						HeapScanDesc scan);
+						TableScanDesc scan);
 
 extern void validate_index(Oid heapId, Oid indexId, Snapshot snapshot);
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 1c6778b3b8..023b7327ed 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1191,7 +1191,7 @@ typedef struct ScanState
 {
 	PlanState	ps;				/* its first field is NodeTag */
 	Relation	ss_currentRelation;
-	HeapScanDesc ss_currentScanDesc;
+	TableScanDesc ss_currentScanDesc;
 	TupleTableSlot *ss_ScanTupleSlot;
 } ScanState;
 
@@ -1212,6 +1212,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 */
@@ -1438,6 +1439,7 @@ typedef struct ParallelBitmapHeapState
 typedef struct BitmapHeapScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
+	HeapPageScanDesc pagescan;
 	ExprState  *bitmapqualorig;
 	TIDBitmap  *tbm;
 	TBMIterator *tbmiterator;
-- 
2.16.1.windows.4

0009-BulkInsertState-is-added-into-table-AM.patchapplication/octet-stream; name=0009-BulkInsertState-is-added-into-table-AM.patchDownload
From 4ea85155d358a1dc72109eb6057b1172f3fbdd59 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:59:20 +1100
Subject: [PATCH 09/16] 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 c605d8a2b8..4caa52c20f 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2332,7 +2332,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 e52ff51190..cb9e7c0730 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 ec5fe5f45a..dc56749e11 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -389,3 +389,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 f9af5acda6..267225bcfd 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2316,7 +2316,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;
@@ -2556,7 +2556,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 */
@@ -2636,7 +2636,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;
 			}
 
@@ -2836,7 +2836,7 @@ next_tuple:
 	/* 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 b156f27259..97411fc8bf 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -53,7 +53,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;
@@ -466,7 +466,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);
@@ -509,7 +509,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 bd9280b20a..6bd32a1300 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4546,7 +4546,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	ListCell   *l;
 	EState	   *estate;
 	CommandId	mycid;
-	BulkInsertState bistate;
+	void       *bistate;
 	int			hi_options;
 	ExprState  *partqualstate = NULL;
 
@@ -4572,7 +4572,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())
@@ -4847,7 +4847,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 6aca0af7d4..dd7040a71d 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 afe8eeb4fe..f82f97b77d 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 e5cc461bd8..3c2ce82df3 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 29f83e0ab2..dace6cc032 100644
--- a/src/include/access/tableamapi.h
+++ b/src/include/access/tableamapi.h
@@ -82,6 +82,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,
@@ -155,6 +158,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.16.1.windows.4

0010-table-rewrite-functionality.patchapplication/octet-stream; name=0010-table-rewrite-functionality.patchDownload
From def2e7df038a7b71d68b733ed54801edc640b909 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:59:20 +1100
Subject: [PATCH 10/16] 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 cb9e7c0730..f2de024fdb 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 dc56749e11..576afe5de2 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -412,3 +412,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 2148b44ee3..b954da9865 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);
 
 
 /*---------------------------------------------------------------------------
@@ -893,7 +892,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);
 
 	/*
@@ -1043,7 +1042,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;
@@ -1057,9 +1056,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)
@@ -1086,16 +1084,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;
@@ -1759,10 +1756,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;
 
@@ -1778,11 +1776,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 dd7040a71d..13658f9e93 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 f82f97b77d..77d08eae14 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 3c2ce82df3..b8fdfddb31 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 dace6cc032..f6fae31449 100644
--- a/src/include/access/tableamapi.h
+++ b/src/include/access/tableamapi.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 TableScanDesc (*ScanBegin_function) (Relation relation,
 											Snapshot snapshot,
 											int nkeys, ScanKey key,
@@ -162,6 +170,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.16.1.windows.4

0011-Improve-tuple-locking-interface.patchapplication/octet-stream; name=0011-Improve-tuple-locking-interface.patchDownload
From ba5d8fd885894562e4a077aa5b6c04948ff83a91 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:59:20 +1100
Subject: [PATCH 11/16] Improve tuple locking interface

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

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 4caa52c20f..5cd1d1b2de 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3210,6 +3210,7 @@ l1:
 	{
 		Assert(result == HeapTupleSelfUpdated ||
 			   result == HeapTupleUpdated ||
+			   result == HeapTupleDeleted ||
 			   result == HeapTupleBeingUpdated);
 		Assert(!(tp.t_data->t_infomask & HEAP_XMAX_INVALID));
 		hufd->ctid = tp.t_data->t_ctid;
@@ -3223,6 +3224,8 @@ l1:
 			UnlockTupleTuplock(relation, &(tp.t_self), LockTupleExclusive);
 		if (vmbuffer != InvalidBuffer)
 			ReleaseBuffer(vmbuffer);
+		if (result == HeapTupleUpdated && ItemPointerEquals(tid, &hufd->ctid))
+			result = HeapTupleDeleted;
 		return result;
 	}
 
@@ -3440,6 +3443,10 @@ simple_heap_delete(Relation relation, ItemPointer tid)
 			elog(ERROR, "tuple concurrently updated");
 			break;
 
+		case HeapTupleDeleted:
+			elog(ERROR, "tuple concurrently deleted");
+			break;
+
 		default:
 			elog(ERROR, "unrecognized heap_delete status: %u", result);
 			break;
@@ -3860,6 +3867,7 @@ l2:
 	{
 		Assert(result == HeapTupleSelfUpdated ||
 			   result == HeapTupleUpdated ||
+			   result == HeapTupleDeleted ||
 			   result == HeapTupleBeingUpdated);
 		Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID));
 		hufd->ctid = oldtup.t_data->t_ctid;
@@ -3879,6 +3887,8 @@ l2:
 		bms_free(id_attrs);
 		bms_free(modified_attrs);
 		bms_free(interesting_attrs);
+		if (result == HeapTupleUpdated && ItemPointerEquals(otid, &hufd->ctid))
+			result = HeapTupleDeleted;
 		return result;
 	}
 
@@ -4585,6 +4595,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;
@@ -4637,6 +4651,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
@@ -4705,7 +4720,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;
@@ -4985,7 +5000,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;
@@ -5145,6 +5160,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;
 	}
@@ -5153,7 +5170,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);
@@ -6041,6 +6058,10 @@ next:
 	result = HeapTupleMayBeUpdated;
 
 out_locked:
+
+	if (result == HeapTupleUpdated && ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid))
+		result = HeapTupleDeleted;
+
 	UnlockReleaseBuffer(buf);
 
 out_unlocked:
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index f2de024fdb..062e184095 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -174,6 +174,7 @@ heapam_heap_delete(Relation relation, ItemPointer tid, CommandId cid,
  *	HeapTupleInvisible: lock failed because tuple was never visible to us
  *	HeapTupleSelfUpdated: lock failed because tuple updated by self
  *	HeapTupleUpdated: lock failed because tuple updated by other xact
+ *	HeapTupleDeleted: lock failed because tuple deleted by other xact
  *	HeapTupleWouldBlock: lock couldn't be acquired and wait_policy is skip
  *
  * In the failure cases other than HeapTupleInvisible, the routine fills
@@ -184,21 +185,191 @@ heapam_heap_delete(Relation relation, ItemPointer tid, CommandId cid,
  * See comments for struct HeapUpdateFailureData for additional info.
  */
 static HTSU_Result
-heapam_lock_tuple(Relation relation, ItemPointer tid, TableTuple *stuple,
-				CommandId cid, LockTupleMode mode,
-				LockWaitPolicy wait_policy, bool follow_updates, Buffer *buffer,
+heapam_lock_tuple(Relation relation, ItemPointer tid, Snapshot snapshot,
+				TableTuple *stuple, CommandId cid, LockTupleMode mode,
+				LockWaitPolicy wait_policy, uint8 flags,
 				HeapUpdateFailureData *hufd)
 {
 	HTSU_Result		result;
 	HeapTupleData	tuple;
+	Buffer			buffer;
 
 	Assert(stuple != NULL);
 	*stuple = NULL;
 
+	hufd->traversed = false;
+
+retry:
 	tuple.t_self = *tid;
-	result = heap_lock_tuple(relation, &tuple, cid, mode, wait_policy, follow_updates, buffer, hufd);
+	result = heap_lock_tuple(relation, &tuple, cid, mode, wait_policy,
+		(flags & TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS) ? true : false,
+		&buffer, hufd);
+
+	if (result == HeapTupleUpdated &&
+		(flags & TUPLE_LOCK_FLAG_FIND_LAST_VERSION))
+	{
+		ReleaseBuffer(buffer);
+		/* Should not encounter speculative tuple on recheck */
+		Assert(!HeapTupleHeaderIsSpeculative(tuple.t_data));
+
+		if (!ItemPointerEquals(&hufd->ctid, &tuple.t_self))
+		{
+			SnapshotData	SnapshotDirty;
+			TransactionId	priorXmax;
+
+			/* it was updated, so look at the updated version */
+			*tid = hufd->ctid;
+			/* updated row should have xmin matching this xmax */
+			priorXmax = hufd->xmax;
+
+			/*
+			 * fetch target tuple
+			 *
+			 * Loop here to deal with updated or busy tuples
+			 */
+			InitDirtySnapshot(SnapshotDirty);
+			for (;;)
+			{
+				if (ItemPointerIndicatesMovedPartitions(tid))
+					ereport(ERROR,
+							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+							 errmsg("tuple to be locked was already moved to another partition due to concurrent update")));
+
+
+				if (heap_fetch(relation, tid, &SnapshotDirty, &tuple, &buffer, true, NULL))
+				{
+					/*
+					 * If xmin isn't what we're expecting, the slot must have been
+					 * recycled and reused for an unrelated tuple.  This implies that
+					 * the latest version of the row was deleted, so we need do
+					 * nothing.  (Should be safe to examine xmin without getting
+					 * buffer's content lock.  We assume reading a TransactionId to be
+					 * atomic, and Xmin never changes in an existing tuple, except to
+					 * invalid or frozen, and neither of those can match priorXmax.)
+					 */
+					if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple.t_data),
+											 priorXmax))
+					{
+						ReleaseBuffer(buffer);
+						return HeapTupleDeleted;
+					}
+
+					/* otherwise xmin should not be dirty... */
+					if (TransactionIdIsValid(SnapshotDirty.xmin))
+						elog(ERROR, "t_xmin is uncommitted in tuple to be updated");
+
+					/*
+					 * If tuple is being updated by other transaction then we have to
+					 * wait for its commit/abort, or die trying.
+					 */
+					if (TransactionIdIsValid(SnapshotDirty.xmax))
+					{
+						ReleaseBuffer(buffer);
+						switch (wait_policy)
+						{
+							case LockWaitBlock:
+								XactLockTableWait(SnapshotDirty.xmax,
+												  relation, &tuple.t_self,
+												  XLTW_FetchUpdated);
+								break;
+							case LockWaitSkip:
+								if (!ConditionalXactLockTableWait(SnapshotDirty.xmax))
+									return result;	/* skip instead of waiting */
+								break;
+							case LockWaitError:
+								if (!ConditionalXactLockTableWait(SnapshotDirty.xmax))
+									ereport(ERROR,
+											(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+											 errmsg("could not obtain lock on row in relation \"%s\"",
+													RelationGetRelationName(relation))));
+								break;
+						}
+						continue;		/* loop back to repeat heap_fetch */
+					}
+
+					/*
+					 * If tuple was inserted by our own transaction, we have to check
+					 * cmin against es_output_cid: cmin >= current CID means our
+					 * command cannot see the tuple, so we should ignore it. Otherwise
+					 * heap_lock_tuple() will throw an error, and so would any later
+					 * attempt to update or delete the tuple.  (We need not check cmax
+					 * because HeapTupleSatisfiesDirty will consider a tuple deleted
+					 * by our transaction dead, regardless of cmax.) We just checked
+					 * that priorXmax == xmin, so we can test that variable instead of
+					 * doing HeapTupleHeaderGetXmin again.
+					 */
+					if (TransactionIdIsCurrentTransactionId(priorXmax) &&
+						HeapTupleHeaderGetCmin(tuple.t_data) >= cid)
+					{
+						ReleaseBuffer(buffer);
+						return result;
+					}
+
+					hufd->traversed = true;
+					*tid = tuple.t_data->t_ctid;
+					ReleaseBuffer(buffer);
+					goto retry;
+				}
+
+				/*
+				 * If the referenced slot was actually empty, the latest version of
+				 * the row must have been deleted, so we need do nothing.
+				 */
+				if (tuple.t_data == NULL)
+				{
+					ReleaseBuffer(buffer);
+					return HeapTupleDeleted;
+				}
+
+				/*
+				 * As above, if xmin isn't what we're expecting, do nothing.
+				 */
+				if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple.t_data),
+										 priorXmax))
+				{
+					ReleaseBuffer(buffer);
+					return HeapTupleDeleted;
+				}
+
+				/*
+				 * If we get here, the tuple was found but failed SnapshotDirty.
+				 * Assuming the xmin is either a committed xact or our own xact (as it
+				 * certainly should be if we're trying to modify the tuple), this must
+				 * mean that the row was updated or deleted by either a committed xact
+				 * or our own xact.  If it was deleted, we can ignore it; if it was
+				 * updated then chain up to the next version and repeat the whole
+				 * process.
+				 *
+				 * As above, it should be safe to examine xmax and t_ctid without the
+				 * buffer content lock, because they can't be changing.
+				 */
+				if (ItemPointerEquals(&tuple.t_self, &tuple.t_data->t_ctid))
+				{
+					/* deleted, so forget about it */
+					ReleaseBuffer(buffer);
+					return HeapTupleDeleted;
+				}
+
+				/* updated, so look at the updated row */
+				*tid = tuple.t_data->t_ctid;
+				/* updated row should have xmin matching this xmax */
+				priorXmax = HeapTupleHeaderGetUpdateXid(tuple.t_data);
+				ReleaseBuffer(buffer);
+				/* loop back to fetch next in chain */
+			}
+		}
+		else
+		{
+			/* tuple was deleted, so give up */
+			return HeapTupleDeleted;
+		}
+	}
+
+	Assert((flags & TUPLE_LOCK_FLAG_FIND_LAST_VERSION) ||
+			HeapTupleSatisfies((TableTuple) &tuple, snapshot, InvalidBuffer));
 
 	*stuple = heap_copytuple(&tuple);
+	ReleaseBuffer(buffer);
 
 	return result;
 }
diff --git a/src/backend/access/heap/heapam_visibility.c b/src/backend/access/heap/heapam_visibility.c
index 9051d4be88..1d45f98a2e 100644
--- a/src/backend/access/heap/heapam_visibility.c
+++ b/src/backend/access/heap/heapam_visibility.c
@@ -616,7 +616,11 @@ HeapTupleSatisfiesUpdate(TableTuple stup, CommandId curcid,
 	{
 		if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
 			return HeapTupleMayBeUpdated;
-		return HeapTupleUpdated;	/* updated by other */
+		/* updated by other */
+		if (ItemPointerEquals(&htup->t_self, &tuple->t_ctid))
+			return HeapTupleDeleted;
+		else
+			return HeapTupleUpdated;
 	}
 
 	if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
@@ -657,7 +661,12 @@ HeapTupleSatisfiesUpdate(TableTuple stup, CommandId curcid,
 			return HeapTupleBeingUpdated;
 
 		if (TransactionIdDidCommit(xmax))
-			return HeapTupleUpdated;
+		{
+			if (ItemPointerEquals(&htup->t_self, &tuple->t_ctid))
+				return HeapTupleDeleted;
+			else
+				return HeapTupleUpdated;
+		}
 
 		/*
 		 * By here, the update in the Xmax is either aborted or crashed, but
@@ -713,7 +722,12 @@ HeapTupleSatisfiesUpdate(TableTuple stup, CommandId curcid,
 
 	SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
 				HeapTupleHeaderGetRawXmax(tuple));
-	return HeapTupleUpdated;	/* updated by other */
+
+	/* updated by other */
+	if (ItemPointerEquals(&htup->t_self, &tuple->t_ctid))
+		return HeapTupleDeleted;
+	else
+		return HeapTupleUpdated;
 }
 
 /*
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index 576afe5de2..39b0f990c4 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -41,13 +41,14 @@ table_fetch(Relation relation,
  *	table_lock_tuple - lock a tuple in shared or exclusive mode
  */
 HTSU_Result
-table_lock_tuple(Relation relation, ItemPointer tid, TableTuple * stuple,
-				   CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
-				   bool follow_updates, Buffer *buffer, HeapUpdateFailureData *hufd)
+table_lock_tuple(Relation relation, ItemPointer tid, Snapshot snapshot,
+				   TableTuple *stuple, CommandId cid, LockTupleMode mode,
+				   LockWaitPolicy wait_policy, uint8 flags,
+				   HeapUpdateFailureData *hufd)
 {
-	return relation->rd_tableamroutine->tuple_lock(relation, tid, stuple,
+	return relation->rd_tableamroutine->tuple_lock(relation, tid, snapshot, stuple,
 												cid, mode, wait_policy,
-												follow_updates, buffer, hufd);
+												flags, hufd);
 }
 
 /* ----------------
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index a4ea981442..75deabe0fe 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3251,8 +3251,6 @@ GetTupleForTrigger(EState *estate,
 	Relation	relation = relinfo->ri_RelationDesc;
 	TableTuple tuple;
 	HeapTuple	result;
-	Buffer		buffer;
-	tuple_data	t_data;
 
 	if (newSlot != NULL)
 	{
@@ -3267,11 +3265,11 @@ GetTupleForTrigger(EState *estate,
 		/*
 		 * lock tuple for update
 		 */
-ltrmark:;
-		test = table_lock_tuple(relation, tid, &tuple,
+		test = table_lock_tuple(relation, tid, estate->es_snapshot, &tuple,
 								  estate->es_output_cid,
 								  lockmode, LockWaitBlock,
-								  false, &buffer, &hufd);
+								  IsolationUsesXactSnapshot() ? 0 : TUPLE_LOCK_FLAG_FIND_LAST_VERSION,
+								  &hufd);
 		result = tuple;
 		switch (test)
 		{
@@ -3292,55 +3290,42 @@ ltrmark:;
 							 errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));
 
 				/* treat it as deleted; do not process */
-				ReleaseBuffer(buffer);
 				return NULL;
 
 			case HeapTupleMayBeUpdated:
-				break;
-
-			case HeapTupleUpdated:
-				ReleaseBuffer(buffer);
-				if (IsolationUsesXactSnapshot())
-					ereport(ERROR,
-							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-							 errmsg("could not serialize access due to concurrent update")));
-				if (ItemPointerIndicatesMovedPartitions(&hufd.ctid))
-					ereport(ERROR,
-							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-							 errmsg("tuple to be locked was already moved to another partition due to concurrent update")));
 
-				t_data = relation->rd_tableamroutine->get_tuple_data(tuple, TID);
-				if (!ItemPointerEquals(&hufd.ctid, &(t_data.tid)))
+				if (hufd.traversed)
 				{
-					/* it was updated, so look at the updated version */
 					TupleTableSlot *epqslot;
 
 					epqslot = EvalPlanQual(estate,
 										   epqstate,
 										   relation,
 										   relinfo->ri_RangeTableIndex,
-										   lockmode,
-										   &hufd.ctid,
-										   hufd.xmax);
-					if (!TupIsNull(epqslot))
-					{
-						*tid = hufd.ctid;
-						*newSlot = epqslot;
-
-						/*
-						 * EvalPlanQual already locked the tuple, but we
-						 * re-call heap_lock_tuple anyway as an easy way of
-						 * re-fetching the correct tuple.  Speed is hardly a
-						 * criterion in this path anyhow.
-						 */
-						goto ltrmark;
-					}
+										   tuple);
+
+					/* If PlanQual failed for updated tuple - we must not process this tuple!*/
+					if (TupIsNull(epqslot))
+						return NULL;
+
+					*newSlot = epqslot;
 				}
+				break;
 
-				/*
-				 * if tuple was deleted or PlanQual failed for updated tuple -
-				 * we must not process this tuple!
-				 */
+			case HeapTupleUpdated:
+				if (IsolationUsesXactSnapshot())
+					ereport(ERROR,
+							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+							 errmsg("could not serialize access due to concurrent update")));
+				elog(ERROR, "wrong heap_lock_tuple status: %u", test);
+				break;
+
+			case HeapTupleDeleted:
+				if (IsolationUsesXactSnapshot())
+					ereport(ERROR,
+							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+							 errmsg("could not serialize access due to concurrent update")));
+				/* tuple was deleted */
 				return NULL;
 
 			case HeapTupleInvisible:
@@ -3348,13 +3333,13 @@ ltrmark:;
 				break;
 
 			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;
@@ -3384,9 +3369,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 c677e4b19a..745a9fa491 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -2488,9 +2488,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)
@@ -2503,32 +2501,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.
 	 */
@@ -2538,7 +2516,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
@@ -2570,271 +2548,6 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
 	return slot;
 }
 
-/*
- * Fetch a copy of the newest version of an outdated tuple
- *
- *	estate - executor state data
- *	relation - table containing tuple
- *	lockmode - requested tuple lock mode
- *	wait_policy - requested lock wait policy
- *	*tid - t_ctid from the outdated tuple (ie, next updated version)
- *	priorXmax - t_xmax from the outdated tuple
- *
- * Returns a palloc'd copy of the newest tuple version, or NULL if we find
- * that there is no newest version (ie, the row was deleted not updated).
- * We also return NULL if the tuple is locked and the wait policy is to skip
- * such tuples.
- *
- * If successful, we have locked the newest tuple version, so caller does not
- * need to worry about it changing anymore.
- *
- * Note: properly, lockmode should be declared as enum LockTupleMode,
- * but we use "int" to avoid having to include heapam.h in executor.h.
- */
-TableTuple
-EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
-				  LockWaitPolicy wait_policy,
-				  ItemPointer tid, TransactionId priorXmax)
-{
-	TableTuple tuple = NULL;
-	SnapshotData SnapshotDirty;
-	tuple_data	t_data;
-
-	/*
-	 * fetch target tuple
-	 *
-	 * Loop here to deal with updated or busy tuples
-	 */
-	InitDirtySnapshot(SnapshotDirty);
-	for (;;)
-	{
-		Buffer		buffer;
-		ItemPointerData ctid;
-
-		if (table_fetch(relation, tid, &SnapshotDirty, &tuple, &buffer, true, NULL))
-		{
-			HTSU_Result test;
-			HeapUpdateFailureData hufd;
-
-			/*
-			 * If xmin isn't what we're expecting, the slot must have been
-			 * recycled and reused for an unrelated tuple.  This implies that
-			 * the latest version of the row was deleted, so we need do
-			 * nothing.  (Should be safe to examine xmin without getting
-			 * buffer's content lock.  We assume reading a TransactionId to be
-			 * atomic, and Xmin never changes in an existing tuple, except to
-			 * invalid or frozen, and neither of those can match priorXmax.)
-			 */
-			if (!TransactionIdEquals(HeapTupleHeaderGetXmin(((HeapTuple) tuple)->t_data),
-									 priorXmax))
-			{
-				ReleaseBuffer(buffer);
-				return NULL;
-			}
-
-			/* otherwise xmin should not be dirty... */
-			if (TransactionIdIsValid(SnapshotDirty.xmin))
-				elog(ERROR, "t_xmin is uncommitted in tuple to be updated");
-
-			/*
-			 * If tuple is being updated by other transaction then we have to
-			 * wait for its commit/abort, or die trying.
-			 */
-			if (TransactionIdIsValid(SnapshotDirty.xmax))
-			{
-				ReleaseBuffer(buffer);
-				switch (wait_policy)
-				{
-					case LockWaitBlock:
-						XactLockTableWait(SnapshotDirty.xmax,
-										  relation,
-										  tid,
-										  XLTW_FetchUpdated);
-						break;
-					case LockWaitSkip:
-						if (!ConditionalXactLockTableWait(SnapshotDirty.xmax))
-							return NULL;	/* skip instead of waiting */
-						break;
-					case LockWaitError:
-						if (!ConditionalXactLockTableWait(SnapshotDirty.xmax))
-							ereport(ERROR,
-									(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-									 errmsg("could not obtain lock on row in relation \"%s\"",
-											RelationGetRelationName(relation))));
-						break;
-				}
-				continue;		/* loop back to repeat heap_fetch */
-			}
-
-			/*
-			 * If tuple was inserted by our own transaction, we have to check
-			 * cmin against es_output_cid: cmin >= current CID means our
-			 * command cannot see the tuple, so we should ignore it. Otherwise
-			 * heap_lock_tuple() will throw an error, and so would any later
-			 * attempt to update or delete the tuple.  (We need not check cmax
-			 * because HeapTupleSatisfiesDirty will consider a tuple deleted
-			 * by our transaction dead, regardless of cmax.) We just checked
-			 * that priorXmax == xmin, so we can test that variable instead of
-			 * doing HeapTupleHeaderGetXmin again.
-			 */
-			if (TransactionIdIsCurrentTransactionId(priorXmax))
-			{
-				t_data = table_tuple_get_data(relation, tuple, CMIN);
-				if (t_data.cid >= estate->es_output_cid)
-				{
-					ReleaseBuffer(buffer);
-					return NULL;
-				}
-			}
-
-			/*
-			 * This is a live tuple, so now try to lock it.
-			 */
-			test = table_lock_tuple(relation, tid, &tuple,
-									  estate->es_output_cid,
-									  lockmode, wait_policy,
-									  false, &buffer, &hufd);
-			/* We now have two pins on the buffer, get rid of one */
-			ReleaseBuffer(buffer);
-
-			switch (test)
-			{
-				case HeapTupleSelfUpdated:
-
-					/*
-					 * The target tuple was already updated or deleted by the
-					 * current command, or by a later command in the current
-					 * transaction.  We *must* ignore the tuple in the former
-					 * case, so as to avoid the "Halloween problem" of
-					 * repeated update attempts.  In the latter case it might
-					 * be sensible to fetch the updated tuple instead, but
-					 * doing so would require changing heap_update and
-					 * heap_delete to not complain about updating "invisible"
-					 * tuples, which seems pretty scary (heap_lock_tuple will
-					 * not complain, but few callers expect
-					 * HeapTupleInvisible, and we're not one of them).  So for
-					 * now, treat the tuple as deleted and do not process.
-					 */
-					ReleaseBuffer(buffer);
-					return NULL;
-
-				case HeapTupleMayBeUpdated:
-					/* successfully locked */
-					break;
-
-				case HeapTupleUpdated:
-					ReleaseBuffer(buffer);
-					if (IsolationUsesXactSnapshot())
-						ereport(ERROR,
-								(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-								 errmsg("could not serialize access due to concurrent update")));
-					if (ItemPointerIndicatesMovedPartitions(&hufd.ctid))
-						ereport(ERROR,
-								(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-								 errmsg("tuple to be locked was already moved to another partition due to concurrent update")));
-
-#if 0 //hari
-					/* Should not encounter speculative tuple on recheck */
-					Assert(!HeapTupleHeaderIsSpeculative(tuple.t_data));
-#endif
-					t_data = table_tuple_get_data(relation, tuple, TID);
-					if (!ItemPointerEquals(&hufd.ctid, &t_data.tid))
-					{
-						/* it was updated, so look at the updated version */
-						*tid = hufd.ctid;
-						/* updated row should have xmin matching this xmax */
-						priorXmax = hufd.xmax;
-						continue;
-					}
-					/* tuple was deleted, so give up */
-					return NULL;
-
-				case HeapTupleWouldBlock:
-					ReleaseBuffer(buffer);
-					return NULL;
-
-				case HeapTupleInvisible:
-					elog(ERROR, "attempted to lock invisible tuple");
-					break;
-
-				default:
-					ReleaseBuffer(buffer);
-					elog(ERROR, "unrecognized heap_lock_tuple status: %u",
-						 test);
-					return NULL;	/* keep compiler quiet */
-			}
-
-			ReleaseBuffer(buffer);
-			break;
-		}
-
-		/*
-		 * If the referenced slot was actually empty, the latest version of
-		 * the row must have been deleted, so we need do nothing.
-		 */
-		if (tuple == NULL)
-		{
-			ReleaseBuffer(buffer);
-			return NULL;
-		}
-
-		/*
-		 * As above, if xmin isn't what we're expecting, do nothing.
-		 */
-		if (!TransactionIdEquals(HeapTupleHeaderGetXmin(((HeapTuple) tuple)->t_data),
-								 priorXmax))
-		{
-			ReleaseBuffer(buffer);
-			return NULL;
-		}
-
-		/*
-		 * If we get here, the tuple was found but failed SnapshotDirty.
-		 * Assuming the xmin is either a committed xact or our own xact (as it
-		 * certainly should be if we're trying to modify the tuple), this must
-		 * mean that the row was updated or deleted by either a committed xact
-		 * or our own xact.  If it was deleted, we can ignore it; if it was
-		 * updated then chain up to the next version and repeat the whole
-		 * process.
-		 *
-		 * As above, it should be safe to examine xmax and t_ctid without the
-		 * buffer content lock, because they can't be changing.
-		 */
-
-		/* check whether next version would be in a different partition */
-		/* hari: FIXME: use a new table API */
-		if (HeapTupleHeaderIndicatesMovedPartitions(((HeapTuple) tuple)->t_data))
-			ereport(ERROR,
-					(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-					 errmsg("tuple to be locked was already moved to another partition due to concurrent update")));
-
-		t_data = table_tuple_get_data(relation, tuple, CTID);
-		ctid = t_data.tid;
-
-		/* check whether tuple has been deleted */
-		if (ItemPointerEquals(tid, &ctid))
-		{
-			/* deleted, so forget about it */
-			ReleaseBuffer(buffer);
-			return NULL;
-		}
-
-		/* updated, so look at the updated row */
-		*tid = ctid;
-
-		/* updated row should have xmin matching this xmax */
-		t_data = table_tuple_get_data(relation, tuple, UPDATED_XID);
-		priorXmax = t_data.xid;
-		ReleaseBuffer(buffer);
-		/* loop back to fetch next in chain */
-	}
-
-	/*
-	 * Return the tuple
-	 */
-	return tuple;
-}
-
 /*
  * EvalPlanQualInit -- initialize during creation of a plan state node
  * that might need to invoke EPQ processing.
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index b4fa287933..3297fd7392 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -167,21 +167,19 @@ retry:
 	/* Found tuple, try to lock it in the lockmode. */
 	if (found)
 	{
-		Buffer		buf;
 		HeapUpdateFailureData hufd;
 		HTSU_Result res;
 		TableTuple locktup;
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
-		res = table_lock_tuple(rel, &(outslot->tts_tid), &locktup, GetCurrentCommandId(false),
+		res = table_lock_tuple(rel, &(outslot->tts_tid), GetLatestSnapshot(),
+								 &locktup,
+								 GetCurrentCommandId(false),
 								 lockmode,
 								 LockWaitBlock,
-								 false /* don't follow updates */ ,
-								 &buf, &hufd);
-		/* the tuple slot already has the buffer pinned */
-		if (BufferIsValid(buf))
-			ReleaseBuffer(buf);
+								 0 /* don't follow updates */ ,
+								 &hufd);
 		pfree(locktup);
 
 		PopActiveSnapshot();
@@ -201,6 +199,12 @@ retry:
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("concurrent update, retrying")));
 				goto retry;
+			case HeapTupleDeleted:
+				/* XXX: Improve handling here */
+				ereport(LOG,
+						(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+						 errmsg("concurrent delete, retrying")));
+				goto retry;
 			case HeapTupleInvisible:
 				elog(ERROR, "attempted to lock invisible tuple");
 				break;
@@ -280,21 +284,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);
 
@@ -315,6 +317,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");
 				break;
diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c
index 5c75e46547..26296f3355 100644
--- a/src/backend/executor/nodeLockRows.c
+++ b/src/backend/executor/nodeLockRows.c
@@ -79,13 +79,11 @@ lnext:
 		Datum		datum;
 		bool		isNull;
 		TableTuple tuple;
-		Buffer		buffer;
 		HeapUpdateFailureData hufd;
 		LockTupleMode lockmode;
 		HTSU_Result test;
 		TableTuple copyTuple;
 		ItemPointerData tid;
-		tuple_data	t_data;
 
 		/* clear any leftover test tuple for this rel */
 		testTuple = (TableTuple) (&(node->lr_curtuples[erm->rti - 1]));
@@ -183,12 +181,12 @@ lnext:
 				break;
 		}
 
-		test = table_lock_tuple(erm->relation, &tid, &tuple,
-								  estate->es_output_cid,
-								  lockmode, erm->waitPolicy, true,
-								  &buffer, &hufd);
-		if (BufferIsValid(buffer))
-			ReleaseBuffer(buffer);
+		test = table_lock_tuple(erm->relation, &tid, estate->es_snapshot,
+								  &tuple, estate->es_output_cid,
+								  lockmode, erm->waitPolicy,
+								  (IsolationUsesXactSnapshot() ? 0 : TUPLE_LOCK_FLAG_FIND_LAST_VERSION)
+								  | TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS,
+								  &hufd);
 
 		switch (test)
 		{
@@ -216,6 +214,16 @@ lnext:
 
 			case HeapTupleMayBeUpdated:
 				/* got the lock successfully */
+				if (hufd.traversed)
+				{
+					/* Save locked tuple for EvalPlanQual testing below */
+					*testTuple = tuple;
+
+					/* Remember we need to do EPQ testing */
+					epq_needed = true;
+
+					/* Continue loop until we have all target tuples */
+				}
 				break;
 
 			case HeapTupleUpdated:
@@ -223,43 +231,19 @@ lnext:
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access due to concurrent update")));
-				if (ItemPointerIndicatesMovedPartitions(&hufd.ctid))
+				/* skip lock */
+				goto lnext;
+
+			case HeapTupleDeleted:
+				if (IsolationUsesXactSnapshot())
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-							 errmsg("tuple to be locked was already moved to another partition due to concurrent update")));
-
-				t_data = erm->relation->rd_tableamroutine->get_tuple_data(tuple, TID);
-				if (ItemPointerEquals(&hufd.ctid, &(t_data.tid)))
-				{
-					/* Tuple was deleted, so don't return it */
-					goto lnext;
-				}
-
-				/* updated, so fetch and lock the updated version */
-				copyTuple = EvalPlanQualFetch(estate, erm->relation,
-											  lockmode, erm->waitPolicy,
-											  &hufd.ctid, hufd.xmax);
-
-				if (copyTuple == NULL)
-				{
-					/*
-					 * Tuple was deleted; or it's locked and we're under SKIP
-					 * LOCKED policy, so don't return it
-					 */
-					goto lnext;
-				}
-				/* remember the actually locked tuple's TID */
-				t_data = erm->relation->rd_tableamroutine->get_tuple_data(copyTuple, TID);
-				tid = t_data.tid;
-
-				/* Save locked tuple for EvalPlanQual testing below */
-				*testTuple = copyTuple;
-
-				/* Remember we need to do EPQ testing */
-				epq_needed = true;
-
-				/* Continue loop until we have all target tuples */
-				break;
+							 errmsg("could not serialize access due to concurrent update")));
+				/*
+				 * Tuple was deleted; or it's locked and we're under SKIP
+				 * LOCKED policy, so don't return it
+				 */
+				goto lnext;
 
 			case HeapTupleInvisible:
 				elog(ERROR, "attempted to lock invisible tuple");
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 99347845da..9560a2fa51 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -211,7 +211,8 @@ ExecCheckHeapTupleVisible(EState *estate,
 	 * We need buffer pin and lock to call HeapTupleSatisfiesVisibility.
 	 * Caller should be holding pin, but not lock.
 	 */
-	LockBuffer(buffer, BUFFER_LOCK_SHARE);
+	if (BufferIsValid(buffer))
+		LockBuffer(buffer, BUFFER_LOCK_SHARE);
 	if (!HeapTupleSatisfiesVisibility(rel->rd_tableamroutine, tuple, estate->es_snapshot, buffer))
 	{
 		tuple_data	t_data = table_tuple_get_data(rel, tuple, XMIN);
@@ -227,7 +228,8 @@ ExecCheckHeapTupleVisible(EState *estate,
 					(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 					 errmsg("could not serialize access due to concurrent update")));
 	}
-	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+	if (BufferIsValid(buffer))
+		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 }
 
 /*
@@ -617,6 +619,7 @@ ExecDelete(ModifyTableState *mtstate,
 	HeapUpdateFailureData hufd;
 	TupleTableSlot *slot = NULL;
 	TransitionCaptureState *ar_delete_trig_tcs;
+	TableTuple	tuple;
 
 	if (tupleDeleted)
 		*tupleDeleted = false;
@@ -703,6 +706,35 @@ ldelete:;
 							 NULL,
 							 &hufd,
 							 changingPart);
+
+		if (result == HeapTupleUpdated && !IsolationUsesXactSnapshot())
+		{
+			result = table_lock_tuple(resultRelationDesc, tupleid,
+										estate->es_snapshot,
+										&tuple, estate->es_output_cid,
+										LockTupleExclusive, LockWaitBlock,
+										TUPLE_LOCK_FLAG_FIND_LAST_VERSION,
+										&hufd);
+			/*hari FIXME*/
+			/*Assert(result != HeapTupleUpdated && hufd.traversed);*/
+			if (result == HeapTupleMayBeUpdated)
+			{
+				TupleTableSlot *epqslot;
+
+				epqslot = EvalPlanQual(estate,
+									   epqstate,
+									   resultRelationDesc,
+									   resultRelInfo->ri_RangeTableIndex,
+									   tuple);
+				if (TupIsNull(epqslot))
+				{
+					/* Tuple no more passing quals, exiting... */
+					return NULL;
+				}
+				goto ldelete;
+			}
+		}
+
 		switch (result)
 		{
 			case HeapTupleSelfUpdated:
@@ -748,28 +780,16 @@ ldelete:;
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access due to concurrent update")));
-				if (ItemPointerIndicatesMovedPartitions(&hufd.ctid))
+				else
+					/* shouldn't get there */
+					elog(ERROR, "wrong heap_delete status: %u", result);
+				break;
+
+			case HeapTupleDeleted:
+				if (IsolationUsesXactSnapshot())
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-							 errmsg("tuple to be deleted was already moved to another partition due to concurrent update")));
-
-				if (!ItemPointerEquals(tupleid, &hufd.ctid))
-				{
-					TupleTableSlot *epqslot;
-
-					epqslot = EvalPlanQual(estate,
-										   epqstate,
-										   resultRelationDesc,
-										   resultRelInfo->ri_RangeTableIndex,
-										   LockTupleExclusive,
-										   &hufd.ctid,
-										   hufd.xmax);
-					if (!TupIsNull(epqslot))
-					{
-						*tupleid = hufd.ctid;
-						goto ldelete;
-					}
-				}
+							 errmsg("could not serialize access due to concurrent delete")));
 				/* tuple already deleted; nothing to do */
 				return NULL;
 
@@ -1166,6 +1186,37 @@ lreplace:;
 								&hufd, &lockmode,
 								ExecInsertIndexTuples,
 								&recheckIndexes);
+
+		if (result == HeapTupleUpdated && !IsolationUsesXactSnapshot())
+		{
+			result = table_lock_tuple(resultRelationDesc, tupleid,
+										estate->es_snapshot,
+										&tuple, estate->es_output_cid,
+										lockmode, LockWaitBlock,
+										TUPLE_LOCK_FLAG_FIND_LAST_VERSION,
+										&hufd);
+			/* hari FIXME*/
+			/*Assert(result != HeapTupleUpdated && hufd.traversed);*/
+			if (result == HeapTupleMayBeUpdated)
+			{
+				TupleTableSlot *epqslot;
+
+				epqslot = EvalPlanQual(estate,
+									   epqstate,
+									   resultRelationDesc,
+									   resultRelInfo->ri_RangeTableIndex,
+									   tuple);
+				if (TupIsNull(epqslot))
+				{
+					/* Tuple no more passing quals, exiting... */
+					return NULL;
+				}
+				slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
+				tuple = ExecHeapifySlot(slot);
+				goto lreplace;
+			}
+		}
+
 		switch (result)
 		{
 			case HeapTupleSelfUpdated:
@@ -1210,30 +1261,16 @@ lreplace:;
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 							 errmsg("could not serialize access due to concurrent update")));
-				if (ItemPointerIndicatesMovedPartitions(&hufd.ctid))
+				else
+					/* shouldn't get there */
+					elog(ERROR, "wrong heap_delete status: %u", result);
+				break;
+
+			case HeapTupleDeleted:
+				if (IsolationUsesXactSnapshot())
 					ereport(ERROR,
 							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-							 errmsg("tuple to be updated was already moved to another partition due to concurrent update")));
-
-				if (!ItemPointerEquals(tupleid, &hufd.ctid))
-				{
-					TupleTableSlot *epqslot;
-
-					epqslot = EvalPlanQual(estate,
-										   epqstate,
-										   resultRelationDesc,
-										   resultRelInfo->ri_RangeTableIndex,
-										   lockmode,
-										   &hufd.ctid,
-										   hufd.xmax);
-					if (!TupIsNull(epqslot))
-					{
-						*tupleid = hufd.ctid;
-						slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
-						tuple = ExecHeapifySlot(slot);
-						goto lreplace;
-					}
-				}
+							 errmsg("could not serialize access due to concurrent delete")));
 				/* tuple already deleted; nothing to do */
 				return NULL;
 
@@ -1302,8 +1339,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);
@@ -1314,8 +1351,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:
@@ -1382,8 +1423,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;
 
@@ -1412,10 +1460,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
@@ -1430,8 +1478,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 */
@@ -1477,8 +1523,6 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 							&mtstate->mt_epqstate, mtstate->ps.state,
 							canSetTag);
 
-	if (BufferIsValid(buffer))
-		ReleaseBuffer(buffer);
 	pfree(tuple);
 	return true;
 }
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 13658f9e93..26d882eb00 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -69,6 +69,7 @@ typedef struct HeapUpdateFailureData
 	ItemPointerData ctid;
 	TransactionId xmax;
 	CommandId	cmax;
+	bool		traversed;
 } HeapUpdateFailureData;
 
 
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 77d08eae14..7227f834a5 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -87,10 +87,10 @@ extern bool table_hot_search_buffer(ItemPointer tid, Relation relation, Buffer b
 extern bool table_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot,
 				   bool *all_dead);
 
-extern HTSU_Result table_lock_tuple(Relation relation, ItemPointer tid, TableTuple * stuple,
-				   CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy,
-				   bool follow_updates,
-				   Buffer *buffer, HeapUpdateFailureData *hufd);
+extern HTSU_Result table_lock_tuple(Relation relation, ItemPointer tid, Snapshot snapshot,
+				   TableTuple *stuple, CommandId cid, LockTupleMode mode,
+				   LockWaitPolicy wait_policy, uint8 flags,
+				   HeapUpdateFailureData *hufd);
 
 extern Oid table_insert(Relation relation, TupleTableSlot *slot, CommandId cid,
 			   int options, BulkInsertState bistate, InsertIndexTuples IndexFunc,
diff --git a/src/include/access/tableamapi.h b/src/include/access/tableamapi.h
index f6fae31449..4897f1407e 100644
--- a/src/include/access/tableamapi.h
+++ b/src/include/access/tableamapi.h
@@ -61,12 +61,12 @@ typedef bool (*TupleFetch_function) (Relation relation,
 
 typedef HTSU_Result (*TupleLock_function) (Relation relation,
 										   ItemPointer tid,
+										   Snapshot snapshot,
 										   TableTuple * tuple,
 										   CommandId cid,
 										   LockTupleMode mode,
 										   LockWaitPolicy wait_policy,
-										   bool follow_update,
-										   Buffer *buffer,
+										   uint8 flags,
 										   HeapUpdateFailureData *hufd);
 
 typedef void (*MultiInsert_function) (Relation relation, HeapTuple *tuples, int ntuples,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 406572771b..964f3e8fd8 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -191,11 +191,7 @@ extern LockTupleMode ExecUpdateLockMode(EState *estate, ResultRelInfo *relinfo);
 extern ExecRowMark *ExecFindRowMark(EState *estate, Index rti, bool missing_ok);
 extern ExecAuxRowMark *ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist);
 extern TupleTableSlot *EvalPlanQual(EState *estate, EPQState *epqstate,
-			 Relation relation, Index rti, int lockmode,
-			 ItemPointer tid, TransactionId priorXmax);
-extern TableTuple EvalPlanQualFetch(EState *estate, Relation relation,
-									  int lockmode, LockWaitPolicy wait_policy, ItemPointer tid,
-									  TransactionId priorXmax);
+			 Relation relation, Index rti, TableTuple tuple);
 extern void EvalPlanQualInit(EPQState *epqstate, EState *estate,
 				 Plan *subplan, List *auxrowmarks, int epqParam);
 extern void EvalPlanQualSetPlan(EPQState *epqstate,
diff --git a/src/include/nodes/lockoptions.h b/src/include/nodes/lockoptions.h
index 24afd6efd4..bcde234614 100644
--- a/src/include/nodes/lockoptions.h
+++ b/src/include/nodes/lockoptions.h
@@ -43,4 +43,9 @@ typedef enum LockWaitPolicy
 	LockWaitError
 } LockWaitPolicy;
 
+/* Follow tuples whose update is in progress if lock modes don't conflict  */
+#define TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS	0x01
+/* Follow update chain and lock lastest version of tuple */
+#define TUPLE_LOCK_FLAG_FIND_LAST_VERSION		0x02
+
 #endif							/* LOCKOPTIONS_H */
diff --git a/src/include/utils/snapshot.h b/src/include/utils/snapshot.h
index ca96fd00fa..95a91db03c 100644
--- a/src/include/utils/snapshot.h
+++ b/src/include/utils/snapshot.h
@@ -136,6 +136,7 @@ typedef enum
 	HeapTupleInvisible,
 	HeapTupleSelfUpdated,
 	HeapTupleUpdated,
+	HeapTupleDeleted,
 	HeapTupleBeingUpdated,
 	HeapTupleWouldBlock			/* can be returned by heap_tuple_lock */
 } HTSU_Result;
diff --git a/src/test/isolation/expected/partition-key-update-1.out b/src/test/isolation/expected/partition-key-update-1.out
index 37fe6a7b27..a632d7f7ba 100644
--- a/src/test/isolation/expected/partition-key-update-1.out
+++ b/src/test/isolation/expected/partition-key-update-1.out
@@ -15,7 +15,7 @@ step s1u: UPDATE foo SET a=2 WHERE a=1;
 step s2d: DELETE FROM foo WHERE a=1; <waiting ...>
 step s1c: COMMIT;
 step s2d: <... completed>
-error in steps s1c s2d: ERROR:  tuple to be deleted was already moved to another partition due to concurrent update
+error in steps s1c s2d: ERROR:  tuple to be locked was already moved to another partition due to concurrent update
 step s2c: COMMIT;
 
 starting permutation: s1b s2b s2d s1u s2c s1c
-- 
2.16.1.windows.4

0013-Using-access-method-syntax-addition-to-create-table.patchapplication/octet-stream; name=0013-Using-access-method-syntax-addition-to-create-table.patchDownload
From 3772ef8ee28f7544cde76cca533587222dc61691 Mon Sep 17 00:00:00 2001
From: Hari Babu <kommi.haribabu@gmail.com>
Date: Thu, 29 Mar 2018 16:17:54 +1100
Subject: [PATCH 13/16] Using "access method" syntax addition to create table

With the pluggable storage support, user can select the
table access method that he needs for the corresponding table.
The syntax addition is similar like CREATE INDEX.

CREATE TABLE ... [USING ACCESSMETHOD] ...

All the catalog relations are by default uses the HEAP method.
Currently the access method syntax support is added only for
main relations.

Currently Default access method HEAP is used for TOAST, VIEW,
SEQUENCE AND MATERIALIZED VIEWS.

Pending items: support of displaying access method with \d commands
---
 src/backend/bootstrap/bootparse.y         |  2 +
 src/backend/catalog/heap.c                |  4 ++
 src/backend/catalog/index.c               |  2 +-
 src/backend/catalog/toasting.c            |  1 +
 src/backend/commands/cluster.c            |  1 +
 src/backend/commands/tablecmds.c          | 28 ++++++++++++
 src/backend/nodes/copyfuncs.c             |  1 +
 src/backend/parser/gram.y                 | 72 ++++++++++++++++++-------------
 src/backend/utils/cache/relcache.c        | 12 +++---
 src/include/access/tableam.h              |  2 +
 src/include/catalog/heap.h                |  2 +
 src/include/nodes/parsenodes.h            |  1 +
 src/include/utils/relcache.h              |  1 +
 src/test/regress/expected/type_sanity.out | 16 ++++---
 src/test/regress/sql/type_sanity.sql      |  6 +--
 15 files changed, 107 insertions(+), 44 deletions(-)

diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 4c72989cc2..598a8ccc07 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -220,6 +220,7 @@ Boot_CreateStmt:
 												   shared_relation ? GLOBALTABLESPACE_OID : 0,
 												   $3,
 												   InvalidOid,
+												   HEAP_TABLE_AM_OID,
 												   tupdesc,
 												   RELKIND_RELATION,
 												   RELPERSISTENCE_PERMANENT,
@@ -239,6 +240,7 @@ Boot_CreateStmt:
 													  $7,
 													  InvalidOid,
 													  BOOTSTRAP_SUPERUSERID,
+													  HEAP_TABLE_AM_OID,
 													  tupdesc,
 													  NIL,
 													  RELKIND_RELATION,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index d59bd5bb00..a984c4a98a 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -255,6 +255,7 @@ heap_create(const char *relname,
 			Oid reltablespace,
 			Oid relid,
 			Oid relfilenode,
+			Oid accessmtd,
 			TupleDesc tupDesc,
 			char relkind,
 			char relpersistence,
@@ -349,6 +350,7 @@ heap_create(const char *relname,
 									 relnamespace,
 									 tupDesc,
 									 relid,
+									 accessmtd,
 									 relfilenode,
 									 reltablespace,
 									 shared_relation,
@@ -1031,6 +1033,7 @@ heap_create_with_catalog(const char *relname,
 						 Oid reltypeid,
 						 Oid reloftypeid,
 						 Oid ownerid,
+						 Oid accessmtd,
 						 TupleDesc tupdesc,
 						 List *cooked_constraints,
 						 char relkind,
@@ -1174,6 +1177,7 @@ heap_create_with_catalog(const char *relname,
 							   reltablespace,
 							   relid,
 							   InvalidOid,
+							   accessmtd,
 							   tupdesc,
 							   relkind,
 							   relpersistence,
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 2691095b87..26ed2cf702 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -912,6 +912,7 @@ index_create(Relation heapRelation,
 								tableSpaceId,
 								indexRelationId,
 								relFileNode,
+								accessMethodObjectId,
 								indexTupDesc,
 								relkind,
 								relpersistence,
@@ -935,7 +936,6 @@ index_create(Relation heapRelation,
 	 * XXX should have a cleaner way to create cataloged indexes
 	 */
 	indexRelation->rd_rel->relowner = heapRelation->rd_rel->relowner;
-	indexRelation->rd_rel->relam = accessMethodObjectId;
 	indexRelation->rd_rel->relhasoids = false;
 	indexRelation->rd_rel->relispartition = OidIsValid(parentIndexRelid);
 
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 3baaa08238..e2dd63b0c3 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -266,6 +266,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 										   toast_typid,
 										   InvalidOid,
 										   rel->rd_rel->relowner,
+										   rel->rd_rel->relam,
 										   tupdesc,
 										   NIL,
 										   RELKIND_TOASTVALUE,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b954da9865..2fd374ce61 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -679,6 +679,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
 										  InvalidOid,
 										  InvalidOid,
 										  OldHeap->rd_rel->relowner,
+										  OldHeap->rd_rel->relam,
 										  OldHeapDesc,
 										  NIL,
 										  RELKIND_RELATION,
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 6bd32a1300..3f67a02238 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -538,6 +538,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
 	Oid			ofTypeId;
 	ObjectAddress address;
+	Oid			accessMethodId = InvalidOid;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -742,6 +743,32 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			attr->attidentity = colDef->identity;
 	}
 
+	/*
+	 * look up the access method, verify it can handle the requested features
+	 */
+	if (stmt->accessMethod != NULL)
+	{
+		HeapTuple	tuple;
+
+		tuple = SearchSysCache1(AMNAME, PointerGetDatum(stmt->accessMethod));
+		if (!HeapTupleIsValid(tuple))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("table access method \"%s\" does not exist",
+								 stmt->accessMethod)));
+		accessMethodId = HeapTupleGetOid(tuple);
+		ReleaseSysCache(tuple);
+	}
+	else if (relkind == RELKIND_RELATION ||
+			relkind == RELKIND_SEQUENCE ||
+			relkind == RELKIND_TOASTVALUE ||
+			relkind == RELKIND_VIEW ||
+			relkind == RELKIND_MATVIEW ||
+			relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		accessMethodId = HEAP_TABLE_AM_OID;
+	}
+
 	/*
 	 * Create the relation.  Inherited defaults and constraints are passed in
 	 * for immediate handling --- since they don't need parsing, they can be
@@ -754,6 +781,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 										  InvalidOid,
 										  ofTypeId,
 										  ownerId,
+										  accessMethodId,
 										  descriptor,
 										  list_concat(cookedDefaults,
 													  old_constraints),
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1c12075b01..9acc157210 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3323,6 +3323,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_NODE_FIELD(options);
 	COPY_SCALAR_FIELD(oncommit);
 	COPY_STRING_FIELD(tablespacename);
+	COPY_STRING_FIELD(accessMethod);
 	COPY_SCALAR_FIELD(if_not_exists);
 }
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c29a50194d..9759b99e2d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -48,6 +48,7 @@
 #include <ctype.h>
 #include <limits.h>
 
+#include "access/tableam.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_am.h"
@@ -339,7 +340,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <str>		copy_file_name
 				database_name access_method_clause access_method attr_name
-				name cursor_name file_name
+				table_access_method_clause name cursor_name file_name
 				index_name opt_index_name cluster_index_specification
 
 %type <list>	func_name handler_name qual_Op qual_all_Op subquery_Op
@@ -3194,7 +3195,8 @@ copy_generic_opt_arg_list_item:
  *****************************************************************************/
 
 CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
-			OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
+			OptInherit OptPartitionSpec table_access_method_clause OptWith
+			OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
@@ -3204,15 +3206,16 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $10;
-					n->oncommit = $11;
-					n->tablespacename = $12;
+					n->accessMethod = $10;
+					n->options = $11;
+					n->oncommit = $12;
+					n->tablespacename = $13;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
-			OptTableElementList ')' OptInherit OptPartitionSpec OptWith
-			OnCommitOption OptTableSpace
+			OptTableElementList ')' OptInherit OptPartitionSpec table_access_method_clause
+			OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
@@ -3222,15 +3225,16 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $13;
-					n->oncommit = $14;
-					n->tablespacename = $15;
+					n->accessMethod = $13;
+					n->options = $14;
+					n->oncommit = $15;
+					n->tablespacename = $16;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name OF any_name
-			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
-			OptTableSpace
+			OptTypedTableElementList OptPartitionSpec table_access_method_clause
+			OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
@@ -3241,15 +3245,16 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $9;
-					n->oncommit = $10;
-					n->tablespacename = $11;
+					n->accessMethod = $9;
+					n->options = $10;
+					n->oncommit = $11;
+					n->tablespacename = $12;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
-			OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
-			OptTableSpace
+			OptTypedTableElementList OptPartitionSpec table_access_method_clause
+			OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
@@ -3260,15 +3265,16 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
-					n->options = $12;
-					n->oncommit = $13;
-					n->tablespacename = $14;
+					n->accessMethod = $12;
+					n->options = $13;
+					n->oncommit = $14;
+					n->tablespacename = $15;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
-			OptTypedTableElementList PartitionBoundSpec OptPartitionSpec OptWith
-			OnCommitOption OptTableSpace
+			OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
+			table_access_method_clause OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->relpersistence = $2;
@@ -3279,15 +3285,16 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->partspec = $10;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $11;
-					n->oncommit = $12;
-					n->tablespacename = $13;
+					n->accessMethod = $11;
+					n->options = $12;
+					n->oncommit = $13;
+					n->tablespacename = $14;
 					n->if_not_exists = false;
 					$$ = (Node *)n;
 				}
 		| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
 			qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
-			OptWith OnCommitOption OptTableSpace
+			table_access_method_clause OptWith OnCommitOption OptTableSpace
 				{
 					CreateStmt *n = makeNode(CreateStmt);
 					$7->relpersistence = $2;
@@ -3298,9 +3305,10 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->partspec = $13;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
-					n->options = $14;
-					n->oncommit = $15;
-					n->tablespacename = $16;
+					n->accessMethod = $14;
+					n->options = $15;
+					n->oncommit = $16;
+					n->tablespacename = $17;
 					n->if_not_exists = true;
 					$$ = (Node *)n;
 				}
@@ -3948,6 +3956,12 @@ part_elem: ColId opt_collate opt_class
 					$$ = n;
 				}
 		;
+
+table_access_method_clause:
+			USING access_method					{ $$ = $2; }
+			| /*EMPTY*/							{ $$ = DEFAULT_TABLE_ACCESS_METHOD; }
+		;
+
 /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
 OptWith:
 			WITH reloptions				{ $$ = $2; }
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 4ad19db3e4..75475060f9 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1760,11 +1760,8 @@ 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))
+	if (IsCatalogRelation(relation) ||
+			!OidIsValid(relation->rd_rel->relam))
 	{
 		relation->rd_tableamhandler = HEAP_TABLE_AM_HANDLER_OID;
 	}
@@ -1946,6 +1943,7 @@ formrdesc(const char *relationName, Oid relationReltype,
 	/*
 	 * initialize the table am handler
 	 */
+	relation->rd_rel->relam = HEAP_TABLE_AM_OID;
 	relation->rd_tableamroutine = GetHeapamTableAmRoutine();
 
 	/*
@@ -3187,6 +3185,7 @@ RelationBuildLocalRelation(const char *relname,
 						   Oid relnamespace,
 						   TupleDesc tupDesc,
 						   Oid relid,
+						   Oid accessmtd,
 						   Oid relfilenode,
 						   Oid reltablespace,
 						   bool shared_relation,
@@ -3367,6 +3366,8 @@ RelationBuildLocalRelation(const char *relname,
 
 	RelationInitPhysicalAddr(rel);
 
+	rel->rd_rel->relam = accessmtd;
+
 	if (relkind == RELKIND_RELATION ||
 		relkind == RELKIND_MATVIEW ||
 		relkind == RELKIND_VIEW ||	/* Not exactly the storage, but underlying
@@ -3899,6 +3900,7 @@ RelationCacheInitializePhase3(void)
 		if (relation->rd_tableamroutine == NULL &&
 			(relation->rd_rel->relkind == RELKIND_RELATION ||
 			 relation->rd_rel->relkind == RELKIND_MATVIEW ||
+			 relation->rd_rel->relkind == RELKIND_VIEW ||
 			 relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
 			 relation->rd_rel->relkind == RELKIND_TOASTVALUE))
 		{
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index d29559395b..8250027637 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -19,6 +19,8 @@
 #include "executor/tuptable.h"
 #include "nodes/execnodes.h"
 
+#define DEFAULT_TABLE_ACCESS_METHOD	"heap_tableam"
+
 typedef union tuple_data
 {
 	TransactionId xid;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 59fc052494..00688d4718 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -45,6 +45,7 @@ extern Relation heap_create(const char *relname,
 			Oid reltablespace,
 			Oid relid,
 			Oid relfilenode,
+			Oid accessmtd,
 			TupleDesc tupDesc,
 			char relkind,
 			char relpersistence,
@@ -59,6 +60,7 @@ extern Oid heap_create_with_catalog(const char *relname,
 						 Oid reltypeid,
 						 Oid reloftypeid,
 						 Oid ownerid,
+						 Oid accessmtd,
 						 TupleDesc tupdesc,
 						 List *cooked_constraints,
 						 char relkind,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6390f7e8c1..ef9715321f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2022,6 +2022,7 @@ typedef struct CreateStmt
 	List	   *options;		/* options from WITH clause */
 	OnCommitAction oncommit;	/* what do we do at COMMIT? */
 	char	   *tablespacename; /* table space to use, or NULL */
+	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
 } CreateStmt;
 
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index df16b4ca98..858a7b30d2 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -101,6 +101,7 @@ extern Relation RelationBuildLocalRelation(const char *relname,
 						   Oid relnamespace,
 						   TupleDesc tupDesc,
 						   Oid relid,
+						   Oid accessmtd,
 						   Oid relfilenode,
 						   Oid reltablespace,
 						   bool shared_relation,
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index b1419d4bc2..ccd88c0260 100644
--- a/src/test/regress/expected/type_sanity.out
+++ b/src/test/regress/expected/type_sanity.out
@@ -502,14 +502,18 @@ WHERE relkind NOT IN ('r', 'i', 'S', 't', 'v', 'm', 'c', 'f', 'p') OR
 -----+---------
 (0 rows)
 
--- Indexes should have an access method, others not.
+-- Except default tables, others should have an access method.
 SELECT p1.oid, p1.relname
 FROM pg_class as p1
-WHERE (p1.relkind = 'i' AND p1.relam = 0) OR
-    (p1.relkind != 'i' AND p1.relam != 0);
- oid | relname 
------+---------
-(0 rows)
+WHERE p1.relkind IN ('r', 'i', 'S', 't', 'v', 'm', 'p', 'i', 'I') and
+    p1.relam = 0;
+ oid  |   relname    
+------+--------------
+ 1247 | pg_type
+ 1249 | pg_attribute
+ 1255 | pg_proc
+ 1259 | pg_class
+(4 rows)
 
 -- **************** pg_attribute ****************
 -- Look for illegal values in pg_attribute fields
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index f9aeea3214..3ed0b9efcb 100644
--- a/src/test/regress/sql/type_sanity.sql
+++ b/src/test/regress/sql/type_sanity.sql
@@ -367,12 +367,12 @@ WHERE relkind NOT IN ('r', 'i', 'S', 't', 'v', 'm', 'c', 'f', 'p') OR
     relpersistence NOT IN ('p', 'u', 't') OR
     relreplident NOT IN ('d', 'n', 'f', 'i');
 
--- Indexes should have an access method, others not.
+-- Except default tables, others should have an access method.
 
 SELECT p1.oid, p1.relname
 FROM pg_class as p1
-WHERE (p1.relkind = 'i' AND p1.relam = 0) OR
-    (p1.relkind != 'i' AND p1.relam != 0);
+WHERE p1.relkind IN ('r', 'i', 'S', 't', 'v', 'm', 'p', 'i', 'I') and
+    p1.relam = 0;
 
 -- **************** pg_attribute ****************
 
-- 
2.16.1.windows.4

#134Amit Kapila
amit.kapila16@gmail.com
In reply to: Haribabu Kommi (#133)
Re: [HACKERS] Pluggable storage

On Thu, Jun 14, 2018 at 1:50 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

On Fri, Apr 20, 2018 at 4:44 PM Haribabu Kommi <kommi.haribabu@gmail.com>
wrote:

VACUUM:
Not much changes are done in this apart moving the Vacuum visibility
functions as part of the
storage. But idea for the Vacuum was with each access method can define how
it should perform.

We are planning to have a somewhat different mechanism for vacuum (for
non-delete marked indexes), so if you can provide some details or
discuss what you have in mind before implementation of same, that
would be great.

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

#135AJG
ayden@gera.co.nz
In reply to: Amit Kapila (#134)
Re: Pluggable storage

@Amit

Re: Vacuum etc.

Chrome V8 just released this blog post around concurrent marking, which may
be of interest considering how cpu limited the browser is. Contains
benchmark numbers etc in post as well.

https://v8project.blogspot.com/2018/06/concurrent-marking.html

"This post describes the garbage collection technique called concurrent
marking. The optimization allows a JavaScript application to continue
execution while the garbage collector scans the heap to find and mark live
objects. Our benchmarks show that concurrent marking reduces the time spent
marking on the main thread by 60%–70%"

--
Sent from: http://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html

#136Andres Freund
andres@anarazel.de
In reply to: AJG (#135)
Re: Pluggable storage

On 2018-06-18 12:43:57 -0700, AJG wrote:

@Amit

Re: Vacuum etc.

Chrome V8 just released this blog post around concurrent marking, which may
be of interest considering how cpu limited the browser is. Contains
benchmark numbers etc in post as well.

https://v8project.blogspot.com/2018/06/concurrent-marking.html

"This post describes the garbage collection technique called concurrent
marking. The optimization allows a JavaScript application to continue
execution while the garbage collector scans the heap to find and mark live
objects. Our benchmarks show that concurrent marking reduces the time spent
marking on the main thread by 60%–70%"

I don't see how in-memory GC techniques have much bearing on the
discussion here?

Greetings,

Andres Freund

#137Amit Kapila
amit.kapila16@gmail.com
In reply to: AJG (#135)
Re: Pluggable storage

On Tue, Jun 19, 2018 at 1:13 AM, AJG <ayden@gera.co.nz> wrote:

@Amit

Re: Vacuum etc.

Chrome V8 just released this blog post around concurrent marking, which may
be of interest considering how cpu limited the browser is. Contains
benchmark numbers etc in post as well.

https://v8project.blogspot.com/2018/06/concurrent-marking.html

"This post describes the garbage collection technique called concurrent
marking. The optimization allows a JavaScript application to continue
execution while the garbage collector scans the heap to find and mark live
objects. Our benchmarks show that concurrent marking reduces the time spent
marking on the main thread by 60%–70%"

Thanks for sharing the link, but I don't think it is not directly
related to the work we are doing, but feel free to discuss it on zheap
thread [1]/messages/by-id/CAA4eK1+YtM5vxzSM2NZm+pC37MCwyvtkmJrO_yRBQeZDp9Wa2w@mail.gmail.com or even you can start a new thread, because it appears more
like some general technique to improve GC (garbage collection) rather
than something directly related zheap (or undo) technology.

[1]: /messages/by-id/CAA4eK1+YtM5vxzSM2NZm+pC37MCwyvtkmJrO_yRBQeZDp9Wa2w@mail.gmail.com

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

#138Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Amit Kapila (#134)
Re: [HACKERS] Pluggable storage

On Thu, Jun 14, 2018 at 12:25 PM Amit Kapila <amit.kapila16@gmail.com>
wrote:

On Thu, Jun 14, 2018 at 1:50 AM, Haribabu Kommi
<kommi.haribabu@gmail.com> wrote:

On Fri, Apr 20, 2018 at 4:44 PM Haribabu Kommi <kommi.haribabu@gmail.com

wrote:

VACUUM:
Not much changes are done in this apart moving the Vacuum visibility
functions as part of the
storage. But idea for the Vacuum was with each access method can define

how

it should perform.

We are planning to have a somewhat different mechanism for vacuum (for
non-delete marked indexes), so if you can provide some details or
discuss what you have in mind before implementation of same, that
would be great.

OK. Thanks for your input. We will discuss the changes before proceed to
code.

Apart from the this, the pluggable storage API contains some re-factored
changes
along with API, some of the re-factored changes are

1. Change the snapshot satisfies type from function to an enum
2. Try to return always the palloced tuple instead of a pointer to buffer
(This change may have performance impact,so can be done later).
3. Perform a tuple visibility check at heap itself for the page mode
scenario also
4. New function ExecSlotCompare to compare two slots or tuple by storing it
a temp slot.
5. heap_fetch and heap_lock_tuple returns the palloced tuple, not the
pointer to the buffer
6. The index insertion logic decision is moved into heap itself(insert,
update), not in executor.
7. Split HeapscanDesc into two and remove it's usage outside heap of the
second split
8. Move the tuple traversing and providing the updated record to heap.

Is it fine to create these changes as separate patches and can go if the
changes are fine
and doesn't have any impact?

Any comments or additions or deletions to the above list?

Regards,
Haribabu Kommi
Fujitsu Australia