]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Move heapam_handler.c index scan code to new file.
authorPeter Geoghegan <pg@bowt.ie>
Sat, 4 Apr 2026 15:30:41 +0000 (11:30 -0400)
committerPeter Geoghegan <pg@bowt.ie>
Sat, 4 Apr 2026 15:30:41 +0000 (11:30 -0400)
Move the heapam index fetch callbacks (index_fetch_begin,
index_fetch_reset, index_fetch_end, and index_fetch_tuple) into a new
dedicated file.  Also move heap_hot_search_buffer over.  This is a
purely mechanical move with no functional impact.

Upcoming work to add a slot-based table AM interface for index scans
will substantially expand this code.  Keeping it in heapam_handler.c
would clutter a file whose primary role is to wire up the TableAmRoutine
callbacks.  Bitmap heap scans and sequential scans would benefit from
similar separation in the future.

Author: Peter Geoghegan <pg@bowt.ie>
Reviewed-By: Andres Freund <andres@anarazel.de>
Discussion: https://postgr.es/m/bmbrkiyjxoal6o5xadzv5bveoynrt3x37wqch7w3jnwumkq2yo@b4zmtnrfs4mh

src/backend/access/heap/Makefile
src/backend/access/heap/heapam.c
src/backend/access/heap/heapam_handler.c
src/backend/access/heap/heapam_indexscan.c [new file with mode: 0644]
src/backend/access/heap/meson.build
src/include/access/heapam.h

index 394534172fa1a2313ac4b86f7600f2b277d93b05..1d27ccb916e092d7b8fae6a198bd0b3fc3ac121d 100644 (file)
@@ -15,6 +15,7 @@ include $(top_builddir)/src/Makefile.global
 OBJS = \
        heapam.o \
        heapam_handler.o \
+       heapam_indexscan.o \
        heapam_visibility.o \
        heapam_xlog.o \
        heaptoast.o \
index 6bff0032db2c24dc356b3bb91ca3ca9d6bbe7e36..e06ce2db2cf968c85ef24df470231acfe5cb3464 100644 (file)
@@ -1764,167 +1764,6 @@ heap_fetch(Relation relation,
        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
- * 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.
- */
-bool
-heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
-                                          Snapshot snapshot, HeapTuple heapTuple,
-                                          bool *all_dead, bool first_call)
-{
-       Page            page = BufferGetPage(buffer);
-       TransactionId prev_xmax = InvalidTransactionId;
-       BlockNumber blkno;
-       OffsetNumber offnum;
-       bool            at_chain_start;
-       bool            valid;
-       bool            skip;
-       GlobalVisState *vistest = NULL;
-
-       /* If this is not the first call, previous call returned a (live!) tuple */
-       if (all_dead)
-               *all_dead = first_call;
-
-       blkno = ItemPointerGetBlockNumber(tid);
-       offnum = ItemPointerGetOffsetNumber(tid);
-       at_chain_start = first_call;
-       skip = !first_call;
-
-       /* XXX: we should assert that a snapshot is pushed or registered */
-       Assert(TransactionIdIsValid(RecentXmin));
-       Assert(BufferGetBlockNumber(buffer) == blkno);
-
-       /* Scan through possible multiple members of HOT-chain */
-       for (;;)
-       {
-               ItemId          lp;
-
-               /* check for bogus TID */
-               if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
-                       break;
-
-               lp = PageGetItemId(page, 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;
-               }
-
-               /*
-                * Update heapTuple to point to the element of the HOT chain we're
-                * currently investigating. Having t_self set correctly is important
-                * because the SSI checks and the *Satisfies routine for historical
-                * MVCC snapshots need the correct tid to decide about the visibility.
-                */
-               heapTuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
-               heapTuple->t_len = ItemIdGetLength(lp);
-               heapTuple->t_tableOid = RelationGetRelid(relation);
-               ItemPointerSet(&heapTuple->t_self, blkno, 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)
-               {
-                       /* If it's visible per the snapshot, we must return it */
-                       valid = HeapTupleSatisfiesVisibility(heapTuple, snapshot, buffer);
-                       HeapCheckForSerializableConflictOut(valid, relation, heapTuple,
-                                                                                               buffer, snapshot);
-
-                       if (valid)
-                       {
-                               ItemPointerSetOffsetNumber(tid, offnum);
-                               PredicateLockTID(relation, &heapTuple->t_self, snapshot,
-                                                                HeapTupleHeaderGetXmin(heapTuple->t_data));
-                               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)
-               {
-                       if (!vistest)
-                               vistest = GlobalVisTestFor(relation);
-
-                       if (!HeapTupleIsSurelyDead(heapTuple, vistest))
-                               *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) ==
-                                  blkno);
-                       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_get_latest_tid -  get the latest tid of a specified tuple
  *
index dc7db58857c580867809789fe7d01a8fbf4e0e09..07f07188d46c9f98236b206eaaebcd9d9a73b439 100644 (file)
@@ -75,117 +75,6 @@ heapam_slot_callbacks(Relation relation)
 }
 
 
-/* ------------------------------------------------------------------------
- * Index Scan Callbacks for heap AM
- * ------------------------------------------------------------------------
- */
-
-static IndexFetchTableData *
-heapam_index_fetch_begin(Relation rel, uint32 flags)
-{
-       IndexFetchHeapData *hscan = palloc0_object(IndexFetchHeapData);
-
-       hscan->xs_base.rel = rel;
-       hscan->xs_base.flags = flags;
-       hscan->xs_cbuf = InvalidBuffer;
-       hscan->xs_vmbuffer = InvalidBuffer;
-
-       return &hscan->xs_base;
-}
-
-static void
-heapam_index_fetch_reset(IndexFetchTableData *scan)
-{
-       IndexFetchHeapData *hscan = (IndexFetchHeapData *) scan;
-
-       if (BufferIsValid(hscan->xs_cbuf))
-       {
-               ReleaseBuffer(hscan->xs_cbuf);
-               hscan->xs_cbuf = InvalidBuffer;
-       }
-
-       if (BufferIsValid(hscan->xs_vmbuffer))
-       {
-               ReleaseBuffer(hscan->xs_vmbuffer);
-               hscan->xs_vmbuffer = InvalidBuffer;
-       }
-}
-
-static void
-heapam_index_fetch_end(IndexFetchTableData *scan)
-{
-       IndexFetchHeapData *hscan = (IndexFetchHeapData *) scan;
-
-       heapam_index_fetch_reset(scan);
-
-       pfree(hscan);
-}
-
-static bool
-heapam_index_fetch_tuple(struct IndexFetchTableData *scan,
-                                                ItemPointer tid,
-                                                Snapshot snapshot,
-                                                TupleTableSlot *slot,
-                                                bool *heap_continue, bool *all_dead)
-{
-       IndexFetchHeapData *hscan = (IndexFetchHeapData *) scan;
-       BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
-       bool            got_heap_tuple;
-
-       Assert(TTS_IS_BUFFERTUPLE(slot));
-
-       /* We can skip the buffer-switching logic if we're in mid-HOT chain. */
-       if (!*heap_continue)
-       {
-               /* Switch to correct buffer if we don't have it already */
-               Buffer          prev_buf = hscan->xs_cbuf;
-
-               hscan->xs_cbuf = ReleaseAndReadBuffer(hscan->xs_cbuf,
-                                                                                         hscan->xs_base.rel,
-                                                                                         ItemPointerGetBlockNumber(tid));
-
-               /*
-                * Prune page, but only if we weren't already on this page
-                */
-               if (prev_buf != hscan->xs_cbuf)
-                       heap_page_prune_opt(hscan->xs_base.rel, hscan->xs_cbuf,
-                                                               &hscan->xs_vmbuffer,
-                                                               hscan->xs_base.flags & SO_HINT_REL_READ_ONLY);
-       }
-
-       /* Obtain share-lock on the buffer so we can examine visibility */
-       LockBuffer(hscan->xs_cbuf, BUFFER_LOCK_SHARE);
-       got_heap_tuple = heap_hot_search_buffer(tid,
-                                                                                       hscan->xs_base.rel,
-                                                                                       hscan->xs_cbuf,
-                                                                                       snapshot,
-                                                                                       &bslot->base.tupdata,
-                                                                                       all_dead,
-                                                                                       !*heap_continue);
-       bslot->base.tupdata.t_self = *tid;
-       LockBuffer(hscan->xs_cbuf, BUFFER_LOCK_UNLOCK);
-
-       if (got_heap_tuple)
-       {
-               /*
-                * Only in a non-MVCC snapshot can more than one member of the HOT
-                * chain be visible.
-                */
-               *heap_continue = !IsMVCCLikeSnapshot(snapshot);
-
-               slot->tts_tableOid = RelationGetRelid(scan->rel);
-               ExecStoreBufferHeapTuple(&bslot->base.tupdata, slot, hscan->xs_cbuf);
-       }
-       else
-       {
-               /* We've reached the end of the HOT chain. */
-               *heap_continue = false;
-       }
-
-       return got_heap_tuple;
-}
-
-
 /* ------------------------------------------------------------------------
  * Callbacks for non-modifying operations on individual tuples for heap AM
  * ------------------------------------------------------------------------
diff --git a/src/backend/access/heap/heapam_indexscan.c b/src/backend/access/heap/heapam_indexscan.c
new file mode 100644 (file)
index 0000000..c36b804
--- /dev/null
@@ -0,0 +1,292 @@
+/*-------------------------------------------------------------------------
+ *
+ * heapam_indexscan.c
+ *       heap table plain index scan and index-only scan code
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *       src/backend/access/heap/heapam_indexscan.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/relscan.h"
+#include "storage/predicate.h"
+
+
+/* ------------------------------------------------------------------------
+ * Index Scan Callbacks for heap AM
+ * ------------------------------------------------------------------------
+ */
+
+IndexFetchTableData *
+heapam_index_fetch_begin(Relation rel, uint32 flags)
+{
+       IndexFetchHeapData *hscan = palloc0_object(IndexFetchHeapData);
+
+       hscan->xs_base.rel = rel;
+       hscan->xs_base.flags = flags;
+       hscan->xs_cbuf = InvalidBuffer;
+       hscan->xs_vmbuffer = InvalidBuffer;
+
+       return &hscan->xs_base;
+}
+
+void
+heapam_index_fetch_reset(IndexFetchTableData *scan)
+{
+       IndexFetchHeapData *hscan = (IndexFetchHeapData *) scan;
+
+       if (BufferIsValid(hscan->xs_cbuf))
+       {
+               ReleaseBuffer(hscan->xs_cbuf);
+               hscan->xs_cbuf = InvalidBuffer;
+       }
+
+       if (BufferIsValid(hscan->xs_vmbuffer))
+       {
+               ReleaseBuffer(hscan->xs_vmbuffer);
+               hscan->xs_vmbuffer = InvalidBuffer;
+       }
+}
+
+void
+heapam_index_fetch_end(IndexFetchTableData *scan)
+{
+       IndexFetchHeapData *hscan = (IndexFetchHeapData *) scan;
+
+       heapam_index_fetch_reset(scan);
+
+       pfree(hscan);
+}
+
+/*
+ *     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.
+ */
+bool
+heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
+                                          Snapshot snapshot, HeapTuple heapTuple,
+                                          bool *all_dead, bool first_call)
+{
+       Page            page = BufferGetPage(buffer);
+       TransactionId prev_xmax = InvalidTransactionId;
+       BlockNumber blkno;
+       OffsetNumber offnum;
+       bool            at_chain_start;
+       bool            valid;
+       bool            skip;
+       GlobalVisState *vistest = NULL;
+
+       /* If this is not the first call, previous call returned a (live!) tuple */
+       if (all_dead)
+               *all_dead = first_call;
+
+       blkno = ItemPointerGetBlockNumber(tid);
+       offnum = ItemPointerGetOffsetNumber(tid);
+       at_chain_start = first_call;
+       skip = !first_call;
+
+       /* XXX: we should assert that a snapshot is pushed or registered */
+       Assert(TransactionIdIsValid(RecentXmin));
+       Assert(BufferGetBlockNumber(buffer) == blkno);
+
+       /* Scan through possible multiple members of HOT-chain */
+       for (;;)
+       {
+               ItemId          lp;
+
+               /* check for bogus TID */
+               if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
+                       break;
+
+               lp = PageGetItemId(page, 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;
+               }
+
+               /*
+                * Update heapTuple to point to the element of the HOT chain we're
+                * currently investigating. Having t_self set correctly is important
+                * because the SSI checks and the *Satisfies routine for historical
+                * MVCC snapshots need the correct tid to decide about the visibility.
+                */
+               heapTuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
+               heapTuple->t_len = ItemIdGetLength(lp);
+               heapTuple->t_tableOid = RelationGetRelid(relation);
+               ItemPointerSet(&heapTuple->t_self, blkno, 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)
+               {
+                       /* If it's visible per the snapshot, we must return it */
+                       valid = HeapTupleSatisfiesVisibility(heapTuple, snapshot, buffer);
+                       HeapCheckForSerializableConflictOut(valid, relation, heapTuple,
+                                                                                               buffer, snapshot);
+
+                       if (valid)
+                       {
+                               ItemPointerSetOffsetNumber(tid, offnum);
+                               PredicateLockTID(relation, &heapTuple->t_self, snapshot,
+                                                                HeapTupleHeaderGetXmin(heapTuple->t_data));
+                               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)
+               {
+                       if (!vistest)
+                               vistest = GlobalVisTestFor(relation);
+
+                       if (!HeapTupleIsSurelyDead(heapTuple, vistest))
+                               *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) ==
+                                  blkno);
+                       offnum = ItemPointerGetOffsetNumber(&heapTuple->t_data->t_ctid);
+                       at_chain_start = false;
+                       prev_xmax = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
+               }
+               else
+                       break;                          /* end of chain */
+
+       }
+
+       return false;
+}
+
+bool
+heapam_index_fetch_tuple(struct IndexFetchTableData *scan,
+                                                ItemPointer tid,
+                                                Snapshot snapshot,
+                                                TupleTableSlot *slot,
+                                                bool *heap_continue, bool *all_dead)
+{
+       IndexFetchHeapData *hscan = (IndexFetchHeapData *) scan;
+       BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
+       bool            got_heap_tuple;
+
+       Assert(TTS_IS_BUFFERTUPLE(slot));
+
+       /* We can skip the buffer-switching logic if we're in mid-HOT chain. */
+       if (!*heap_continue)
+       {
+               /* Switch to correct buffer if we don't have it already */
+               Buffer          prev_buf = hscan->xs_cbuf;
+
+               hscan->xs_cbuf = ReleaseAndReadBuffer(hscan->xs_cbuf,
+                                                                                         hscan->xs_base.rel,
+                                                                                         ItemPointerGetBlockNumber(tid));
+
+               /*
+                * Prune page, but only if we weren't already on this page
+                */
+               if (prev_buf != hscan->xs_cbuf)
+                       heap_page_prune_opt(hscan->xs_base.rel, hscan->xs_cbuf,
+                                                               &hscan->xs_vmbuffer,
+                                                               hscan->xs_base.flags & SO_HINT_REL_READ_ONLY);
+       }
+
+       /* Obtain share-lock on the buffer so we can examine visibility */
+       LockBuffer(hscan->xs_cbuf, BUFFER_LOCK_SHARE);
+       got_heap_tuple = heap_hot_search_buffer(tid,
+                                                                                       hscan->xs_base.rel,
+                                                                                       hscan->xs_cbuf,
+                                                                                       snapshot,
+                                                                                       &bslot->base.tupdata,
+                                                                                       all_dead,
+                                                                                       !*heap_continue);
+       bslot->base.tupdata.t_self = *tid;
+       LockBuffer(hscan->xs_cbuf, BUFFER_LOCK_UNLOCK);
+
+       if (got_heap_tuple)
+       {
+               /*
+                * Only in a non-MVCC snapshot can more than one member of the HOT
+                * chain be visible.
+                */
+               *heap_continue = !IsMVCCLikeSnapshot(snapshot);
+
+               slot->tts_tableOid = RelationGetRelid(scan->rel);
+               ExecStoreBufferHeapTuple(&bslot->base.tupdata, slot, hscan->xs_cbuf);
+       }
+       else
+       {
+               /* We've reached the end of the HOT chain. */
+               *heap_continue = false;
+       }
+
+       return got_heap_tuple;
+}
index 92ab8be3dfd352e42bdbf5d175b89950df859bcc..00ec07d7f30d1d1c65f6cfa31dd4d7b3f9473700 100644 (file)
@@ -3,6 +3,7 @@
 backend_sources += files(
   'heapam.c',
   'heapam_handler.c',
+  'heapam_indexscan.c',
   'heapam_visibility.c',
   'heapam_xlog.c',
   'heaptoast.c',
index 54067b828e44ed99daeee4cdac73f37bab05e05a..cc90c821be5e0a374c6eb68fd438821c3d723be5 100644 (file)
@@ -366,9 +366,6 @@ extern bool heap_getnextslot_tidrange(TableScanDesc sscan,
                                                                          TupleTableSlot *slot);
 extern bool heap_fetch(Relation relation, Snapshot snapshot,
                                           HeapTuple tuple, Buffer *userbuf, bool keep_buf);
-extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
-                                                                  Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
-                                                                  bool *all_dead, bool first_call);
 
 extern void heap_get_latest_tid(TableScanDesc sscan, ItemPointer tid);
 
@@ -431,6 +428,18 @@ extern void simple_heap_update(Relation relation, const ItemPointerData *otid,
 extern TransactionId heap_index_delete_tuples(Relation rel,
                                                                                          TM_IndexDeleteOp *delstate);
 
+/* in heap/heapam_indexscan.c */
+extern IndexFetchTableData *heapam_index_fetch_begin(Relation rel, uint32 flags);
+extern void heapam_index_fetch_reset(IndexFetchTableData *scan);
+extern void heapam_index_fetch_end(IndexFetchTableData *scan);
+extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
+                                                                  Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
+                                                                  bool *all_dead, bool first_call);
+extern bool heapam_index_fetch_tuple(struct IndexFetchTableData *scan,
+                                                                        ItemPointer tid, Snapshot snapshot,
+                                                                        TupleTableSlot *slot, bool *heap_continue,
+                                                                        bool *all_dead);
+
 /* in heap/pruneheap.c */
 extern void heap_page_prune_opt(Relation relation, Buffer buffer,
                                                                Buffer *vmbuffer, bool rel_read_only);