Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quellcode-Bibliothek heapam_handler.c  Sprache: unbekannt

 
/*-------------------------------------------------------------------------
 *
 * heapam_handler.c
 *   heap table access method code
 *
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *   src/backend/access/heap/heapam_handler.c
 *
 *
 * NOTES
 *   This files wires up the lower level heapam.c et al routines with the
 *   tableam abstraction.
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/genam.h"
#include "access/heapam.h"
#include "access/heaptoast.h"
#include "access/multixact.h"
#include "access/rewriteheap.h"
#include "access/syncscan.h"
#include "access/tableam.h"
#include "access/tsmapi.h"
#include "access/visibilitymap.h"
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/index.h"
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
#include "commands/progress.h"
#include "executor/executor.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/bufmgr.h"
#include "storage/bufpage.h"
#include "storage/lmgr.h"
#include "storage/predicate.h"
#include "storage/procarray.h"
#include "storage/smgr.h"
#include "utils/builtins.h"
#include "utils/rel.h"

static void reform_and_rewrite_tuple(HeapTuple tuple,
          Relation OldHeap, Relation NewHeap,
          Datum *values, bool *isnull, RewriteState rwstate);

static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
           HeapTuple tuple,
           OffsetNumber tupoffset);

static BlockNumber heapam_scan_get_blocks_done(HeapScanDesc hscan);

static bool BitmapHeapScanNextBlock(TableScanDesc scan,
         bool *recheck,
         uint64 *lossy_pages, uint64 *exact_pages);


/* ------------------------------------------------------------------------
 * Slot related callbacks for heap AM
 * ------------------------------------------------------------------------
 */


static const TupleTableSlotOps *
heapam_slot_callbacks(Relation relation)
{
 return &TTSOpsBufferHeapTuple;
}


/* ------------------------------------------------------------------------
 * Index Scan Callbacks for heap AM
 * ------------------------------------------------------------------------
 */


static IndexFetchTableData *
heapam_index_fetch_begin(Relation rel)
{
 IndexFetchHeapData *hscan = palloc0(sizeof(IndexFetchHeapData));

 hscan->xs_base.rel = rel;
 hscan->xs_cbuf = 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;
 }
}

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 *call_again, 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 (!*call_again)
 {
  /* 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);
 }

 /* 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,
           !*call_again);
 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.
 */

  *call_again = !IsMVCCSnapshot(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. */
  *call_again = false;
 }

 return got_heap_tuple;
}


/* ------------------------------------------------------------------------
 * Callbacks for non-modifying operations on individual tuples for heap AM
 * ------------------------------------------------------------------------
 */


static bool
heapam_fetch_row_version(Relation relation,
       ItemPointer tid,
       Snapshot snapshot,
       TupleTableSlot *slot)
{
 BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
 Buffer  buffer;

 Assert(TTS_IS_BUFFERTUPLE(slot));

 bslot->base.tupdata.t_self = *tid;
 if (heap_fetch(relation, snapshot, &bslot->base.tupdata, &buffer, false))
 {
  /* store in slot, transferring existing pin */
  ExecStorePinnedBufferHeapTuple(&bslot->base.tupdata, slot, buffer);
  slot->tts_tableOid = RelationGetRelid(relation);

  return true;
 }

 return false;
}

static bool
heapam_tuple_tid_valid(TableScanDesc scan, ItemPointer tid)
{
 HeapScanDesc hscan = (HeapScanDesc) scan;

 return ItemPointerIsValid(tid) &&
  ItemPointerGetBlockNumber(tid) < hscan->rs_nblocks;
}

static bool
heapam_tuple_satisfies_snapshot(Relation rel, TupleTableSlot *slot,
        Snapshot snapshot)
{
 BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
 bool  res;

 Assert(TTS_IS_BUFFERTUPLE(slot));
 Assert(BufferIsValid(bslot->buffer));

 /*
  * We need buffer pin and lock to call HeapTupleSatisfiesVisibility.
  * Caller should be holding pin, but not lock.
 */

 LockBuffer(bslot->buffer, BUFFER_LOCK_SHARE);
 res = HeapTupleSatisfiesVisibility(bslot->base.tuple, snapshot,
            bslot->buffer);
 LockBuffer(bslot->buffer, BUFFER_LOCK_UNLOCK);

 return res;
}


/* ----------------------------------------------------------------------------
 *  Functions for manipulations of physical tuples for heap AM.
 * ----------------------------------------------------------------------------
 */


static void
heapam_tuple_insert(Relation relation, TupleTableSlot *slot, CommandId cid,
     int options, BulkInsertState bistate)
{
 bool  shouldFree = true;
 HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);

 /* Update the tuple with table oid */
 slot->tts_tableOid = RelationGetRelid(relation);
 tuple->t_tableOid = slot->tts_tableOid;

 /* Perform the insertion, and copy the resulting ItemPointer */
 heap_insert(relation, tuple, cid, options, bistate);
 ItemPointerCopy(&tuple->t_self, &slot->tts_tid);

 if (shouldFree)
  pfree(tuple);
}

static void
heapam_tuple_insert_speculative(Relation relation, TupleTableSlot *slot,
        CommandId cid, int options,
        BulkInsertState bistate, uint32 specToken)
{
 bool  shouldFree = true;
 HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);

 /* Update the tuple with table oid */
 slot->tts_tableOid = RelationGetRelid(relation);
 tuple->t_tableOid = slot->tts_tableOid;

 HeapTupleHeaderSetSpeculativeToken(tuple->t_data, specToken);
 options |= HEAP_INSERT_SPECULATIVE;

 /* Perform the insertion, and copy the resulting ItemPointer */
 heap_insert(relation, tuple, cid, options, bistate);
 ItemPointerCopy(&tuple->t_self, &slot->tts_tid);

 if (shouldFree)
  pfree(tuple);
}

static void
heapam_tuple_complete_speculative(Relation relation, TupleTableSlot *slot,
          uint32 specToken, bool succeeded)
{
 bool  shouldFree = true;
 HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);

 /* adjust the tuple's state accordingly */
 if (succeeded)
  heap_finish_speculative(relation, &slot->tts_tid);
 else
  heap_abort_speculative(relation, &slot->tts_tid);

 if (shouldFree)
  pfree(tuple);
}

static TM_Result
heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid,
     Snapshot snapshot, Snapshot crosscheck, bool wait,
     TM_FailureData *tmfd, 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, tmfd, changingPart);
}


static TM_Result
heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
     CommandId cid, Snapshot snapshot, Snapshot crosscheck,
     bool wait, TM_FailureData *tmfd,
     LockTupleMode *lockmode, TU_UpdateIndexes *update_indexes)
{
 bool  shouldFree = true;
 HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
 TM_Result result;

 /* Update the tuple with table oid */
 slot->tts_tableOid = RelationGetRelid(relation);
 tuple->t_tableOid = slot->tts_tableOid;

 result = heap_update(relation, otid, tuple, cid, crosscheck, wait,
       tmfd, lockmode, update_indexes);
 ItemPointerCopy(&tuple->t_self, &slot->tts_tid);

 /*
  * Decide whether new index entries are needed for the tuple
  *
  * Note: heap_update returns the tid (location) of the new tuple in the
  * t_self field.
  *
  * If the update is not HOT, we must update all indexes. If the update is
  * HOT, it could be that we updated summarized columns, so we either
  * update only summarized indexes, or none at all.
 */

 if (result != TM_Ok)
 {
  Assert(*update_indexes == TU_None);
  *update_indexes = TU_None;
 }
 else if (!HeapTupleIsHeapOnly(tuple))
  Assert(*update_indexes == TU_All);
 else
  Assert((*update_indexes == TU_Summarizing) ||
      (*update_indexes == TU_None));

 if (shouldFree)
  pfree(tuple);

 return result;
}

static TM_Result
heapam_tuple_lock(Relation relation, ItemPointer tid, Snapshot snapshot,
      TupleTableSlot *slot, CommandId cid, LockTupleMode mode,
      LockWaitPolicy wait_policy, uint8 flags,
      TM_FailureData *tmfd)
{
 BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
 TM_Result result;
 Buffer  buffer;
 HeapTuple tuple = &bslot->base.tupdata;
 bool  follow_updates;

 follow_updates = (flags & TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS) != 0;
 tmfd->traversed = false;

 Assert(TTS_IS_BUFFERTUPLE(slot));

tuple_lock_retry:
 tuple->t_self = *tid;
 result = heap_lock_tuple(relation, tuple, cid, mode, wait_policy,
        follow_updates, &buffer, tmfd);

 if (result == TM_Updated &&
  (flags & TUPLE_LOCK_FLAG_FIND_LAST_VERSION))
 {
  /* Should not encounter speculative tuple on recheck */
  Assert(!HeapTupleHeaderIsSpeculative(tuple->t_data));

  ReleaseBuffer(buffer);

  if (!ItemPointerEquals(&tmfd->ctid, &tuple->t_self))
  {
   SnapshotData SnapshotDirty;
   TransactionId priorXmax;

   /* it was updated, so look at the updated version */
   *tid = tmfd->ctid;
   /* updated row should have xmin matching this xmax */
   priorXmax = tmfd->xmax;

   /* signal that a tuple later in the chain is getting locked */
   tmfd->traversed = true;

   /*
    * 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")));

    tuple->t_self = *tid;
    if (heap_fetch(relation, &SnapshotDirty, tuple, &buffer, true))
    {
     /*
      * 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 TM_Deleted;
     }

     /* otherwise xmin should not be dirty... */
     if (TransactionIdIsValid(SnapshotDirty.xmin))
      ereport(ERROR,
        (errcode(ERRCODE_DATA_CORRUPTED),
         errmsg_internal("t_xmin %u is uncommitted in tuple (%u,%u) to be updated in table \"%s\"",
             SnapshotDirty.xmin,
             ItemPointerGetBlockNumber(&tuple->t_self),
             ItemPointerGetOffsetNumber(&tuple->t_self),
             RelationGetRelationName(relation))));

     /*
      * 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, false))
         /* skip instead of waiting */
         return TM_WouldBlock;
        break;
       case LockWaitError:
        if (!ConditionalXactLockTableWait(SnapshotDirty.xmax, log_lock_failures))
         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 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)
     {
      tmfd->xmax = priorXmax;

      /*
       * Cmin is the problematic value, so store that. See
       * above.
 */

      tmfd->cmax = HeapTupleHeaderGetCmin(tuple->t_data);
      ReleaseBuffer(buffer);
      return TM_SelfModified;
     }

     /*
      * This is a live tuple, so try to lock it again.
 */

     ReleaseBuffer(buffer);
     goto tuple_lock_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)
    {
     Assert(!BufferIsValid(buffer));
     return TM_Deleted;
    }

    /*
     * As above, if xmin isn't what we're expecting, do nothing.
 */

    if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple->t_data),
           priorXmax))
    {
     ReleaseBuffer(buffer);
     return TM_Deleted;
    }

    /*
     * 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.  We'd better hold a buffer pin though.
 */

    if (ItemPointerEquals(&tuple->t_self, &tuple->t_data->t_ctid))
    {
     /* deleted, so forget about it */
     ReleaseBuffer(buffer);
     return TM_Deleted;
    }

    /* 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 TM_Deleted;
  }
 }

 slot->tts_tableOid = RelationGetRelid(relation);
 tuple->t_tableOid = slot->tts_tableOid;

 /* store in slot, transferring existing pin */
 ExecStorePinnedBufferHeapTuple(tuple, slot, buffer);

 return result;
}


/* ------------------------------------------------------------------------
 * DDL related callbacks for heap AM.
 * ------------------------------------------------------------------------
 */


static void
heapam_relation_set_new_filelocator(Relation rel,
         const RelFileLocator *newrlocator,
         char persistence,
         TransactionId *freezeXid,
         MultiXactId *minmulti)
{
 SMgrRelation srel;

 /*
  * Initialize to the minimum XID that could put tuples in the table. We
  * know that no xacts older than RecentXmin are still running, so that
  * will do.
 */

 *freezeXid = RecentXmin;

 /*
  * Similarly, initialize the minimum Multixact to the first value that
  * could possibly be stored in tuples in the table.  Running transactions
  * could reuse values from their local cache, so we are careful to
  * consider all currently running multis.
  *
  * XXX this could be refined further, but is it worth the hassle?
 */

 *minmulti = GetOldestMultiXactId();

 srel = RelationCreateStorage(*newrlocator, persistence, true);

 /*
  * If required, set up an init fork for an unlogged table so that it can
  * be correctly reinitialized on restart.
 */

 if (persistence == RELPERSISTENCE_UNLOGGED)
 {
  Assert(rel->rd_rel->relkind == RELKIND_RELATION ||
      rel->rd_rel->relkind == RELKIND_TOASTVALUE);
  smgrcreate(srel, INIT_FORKNUM, false);
  log_smgrcreate(newrlocator, INIT_FORKNUM);
 }

 smgrclose(srel);
}

static void
heapam_relation_nontransactional_truncate(Relation rel)
{
 RelationTruncate(rel, 0);
}

static void
heapam_relation_copy_data(Relation rel, const RelFileLocator *newrlocator)
{
 SMgrRelation dstrel;

 /*
  * Since we copy the file directly without looking at the shared buffers,
  * we'd better first flush out any pages of the source relation that are
  * in shared buffers.  We assume no new changes will be made while we are
  * holding exclusive lock on the rel.
 */

 FlushRelationBuffers(rel);

 /*
  * Create and copy all forks of the relation, and schedule unlinking of
  * old physical files.
  *
  * NOTE: any conflict in relfilenumber value will be caught in
  * RelationCreateStorage().
 */

 dstrel = RelationCreateStorage(*newrlocator, rel->rd_rel->relpersistence, true);

 /* copy main fork */
 RelationCopyStorage(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
      rel->rd_rel->relpersistence);

 /* copy those extra forks that exist */
 for (ForkNumber forkNum = MAIN_FORKNUM + 1;
   forkNum <= MAX_FORKNUM; forkNum++)
 {
  if (smgrexists(RelationGetSmgr(rel), forkNum))
  {
   smgrcreate(dstrel, forkNum, false);

   /*
    * WAL log creation if the relation is persistent, or this is the
    * init fork of an unlogged relation.
 */

   if (RelationIsPermanent(rel) ||
    (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
     forkNum == INIT_FORKNUM))
    log_smgrcreate(newrlocator, forkNum);
   RelationCopyStorage(RelationGetSmgr(rel), dstrel, forkNum,
        rel->rd_rel->relpersistence);
  }
 }


 /* drop old relation, and close new one */
 RelationDropStorage(rel);
 smgrclose(dstrel);
}

static void
heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
         Relation OldIndex, bool use_sort,
         TransactionId OldestXmin,
         TransactionId *xid_cutoff,
         MultiXactId *multi_cutoff,
         double *num_tuples,
         double *tups_vacuumed,
         double *tups_recently_dead)
{
 RewriteState rwstate;
 IndexScanDesc indexScan;
 TableScanDesc tableScan;
 HeapScanDesc heapScan;
 bool  is_system_catalog;
 Tuplesortstate *tuplesort;
 TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
 TupleDesc newTupDesc = RelationGetDescr(NewHeap);
 TupleTableSlot *slot;
 int   natts;
 Datum    *values;
 bool    *isnull;
 BufferHeapTupleTableSlot *hslot;
 BlockNumber prev_cblock = InvalidBlockNumber;

 /* Remember if it's a system catalog */
 is_system_catalog = IsSystemRelation(OldHeap);

 /*
  * Valid smgr_targblock implies something already wrote to the relation.
  * This may be harmless, but this function hasn't planned for it.
 */

 Assert(RelationGetTargetBlock(NewHeap) == InvalidBlockNumber);

 /* Preallocate values/isnull arrays */
 natts = newTupDesc->natts;
 values = (Datum *) palloc(natts * sizeof(Datum));
 isnull = (bool *) palloc(natts * sizeof(bool));

 /* Initialize the rewrite operation */
 rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, *xid_cutoff,
         *multi_cutoff);


 /* Set up sorting if wanted */
 if (use_sort)
  tuplesort = tuplesort_begin_cluster(oldTupDesc, OldIndex,
           maintenance_work_mem,
           NULL, TUPLESORT_NONE);
 else
  tuplesort = NULL;

 /*
  * Prepare to scan the OldHeap.  To ensure we see recently-dead tuples
  * that still need to be copied, we scan with SnapshotAny and use
  * HeapTupleSatisfiesVacuum for the visibility test.
 */

 if (OldIndex != NULL && !use_sort)
 {
  const int ci_index[] = {
   PROGRESS_CLUSTER_PHASE,
   PROGRESS_CLUSTER_INDEX_RELID
  };
  int64  ci_val[2];

  /* Set phase and OIDOldIndex to columns */
  ci_val[0] = PROGRESS_CLUSTER_PHASE_INDEX_SCAN_HEAP;
  ci_val[1] = RelationGetRelid(OldIndex);
  pgstat_progress_update_multi_param(2, ci_index, ci_val);

  tableScan = NULL;
  heapScan = NULL;
  indexScan = index_beginscan(OldHeap, OldIndex, SnapshotAny, NULL, 0, 0);
  index_rescan(indexScan, NULL, 0, NULL, 0);
 }
 else
 {
  /* In scan-and-sort mode and also VACUUM FULL, set phase */
  pgstat_progress_update_param(PROGRESS_CLUSTER_PHASE,
          PROGRESS_CLUSTER_PHASE_SEQ_SCAN_HEAP);

  tableScan = table_beginscan(OldHeap, SnapshotAny, 0, (ScanKey) NULL);
  heapScan = (HeapScanDesc) tableScan;
  indexScan = NULL;

  /* Set total heap blocks */
  pgstat_progress_update_param(PROGRESS_CLUSTER_TOTAL_HEAP_BLKS,
          heapScan->rs_nblocks);
 }

 slot = table_slot_create(OldHeap, NULL);
 hslot = (BufferHeapTupleTableSlot *) slot;

 /*
  * Scan through the OldHeap, either in OldIndex order or sequentially;
  * copy each tuple into the NewHeap, or transiently to the tuplesort
  * module.  Note that we don't bother sorting dead tuples (they won't get
  * to the new table anyway).
 */

 for (;;)
 {
  HeapTuple tuple;
  Buffer  buf;
  bool  isdead;

  CHECK_FOR_INTERRUPTS();

  if (indexScan != NULL)
  {
   if (!index_getnext_slot(indexScan, ForwardScanDirection, slot))
    break;

   /* Since we used no scan keys, should never need to recheck */
   if (indexScan->xs_recheck)
    elog(ERROR, "CLUSTER does not support lossy index conditions");
  }
  else
  {
   if (!table_scan_getnextslot(tableScan, ForwardScanDirection, slot))
   {
    /*
     * If the last pages of the scan were empty, we would go to
     * the next phase while heap_blks_scanned != heap_blks_total.
     * Instead, to ensure that heap_blks_scanned is equivalent to
     * heap_blks_total after the table scan phase, this parameter
     * is manually updated to the correct value when the table
     * scan finishes.
 */

    pgstat_progress_update_param(PROGRESS_CLUSTER_HEAP_BLKS_SCANNED,
            heapScan->rs_nblocks);
    break;
   }

   /*
    * In scan-and-sort mode and also VACUUM FULL, set heap blocks
    * scanned
    *
    * Note that heapScan may start at an offset and wrap around, i.e.
    * rs_startblock may be >0, and rs_cblock may end with a number
    * below rs_startblock. To prevent showing this wraparound to the
    * user, we offset rs_cblock by rs_startblock (modulo rs_nblocks).
 */

   if (prev_cblock != heapScan->rs_cblock)
   {
    pgstat_progress_update_param(PROGRESS_CLUSTER_HEAP_BLKS_SCANNED,
            (heapScan->rs_cblock +
             heapScan->rs_nblocks -
             heapScan->rs_startblock
             ) % heapScan->rs_nblocks + 1);
    prev_cblock = heapScan->rs_cblock;
   }
  }

  tuple = ExecFetchSlotHeapTuple(slot, false, NULL);
  buf = hslot->buffer;

  LockBuffer(buf, BUFFER_LOCK_SHARE);

  switch (HeapTupleSatisfiesVacuum(tuple, OldestXmin, buf))
  {
   case HEAPTUPLE_DEAD:
    /* Definitely dead */
    isdead = true;
    break;
   case HEAPTUPLE_RECENTLY_DEAD:
    *tups_recently_dead += 1;
    /* fall through */
   case HEAPTUPLE_LIVE:
    /* Live or recently dead, must copy it */
    isdead = false;
    break;
   case HEAPTUPLE_INSERT_IN_PROGRESS:

    /*
     * Since we hold exclusive lock on the relation, normally the
     * only way to see this is if it was inserted earlier in our
     * own transaction.  However, it can happen in system
     * catalogs, since we tend to release write lock before commit
     * there.  Give a warning if neither case applies; but in any
     * case we had better copy it.
 */

    if (!is_system_catalog &&
     !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data)))
     elog(WARNING, "concurrent insert in progress within table \"%s\"",
       RelationGetRelationName(OldHeap));
    /* treat as live */
    isdead = false;
    break;
   case HEAPTUPLE_DELETE_IN_PROGRESS:

    /*
     * Similar situation to INSERT_IN_PROGRESS case.
 */

    if (!is_system_catalog &&
     !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple->t_data)))
     elog(WARNING, "concurrent delete in progress within table \"%s\"",
       RelationGetRelationName(OldHeap));
    /* treat as recently dead */
    *tups_recently_dead += 1;
    isdead = false;
    break;
   default:
    elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
    isdead = false; /* keep compiler quiet */
    break;
  }

  LockBuffer(buf, BUFFER_LOCK_UNLOCK);

  if (isdead)
  {
   *tups_vacuumed += 1;
   /* heap rewrite module still needs to see it... */
   if (rewrite_heap_dead_tuple(rwstate, tuple))
   {
    /* A previous recently-dead tuple is now known dead */
    *tups_vacuumed += 1;
    *tups_recently_dead -= 1;
   }
   continue;
  }

  *num_tuples += 1;
  if (tuplesort != NULL)
  {
   tuplesort_putheaptuple(tuplesort, tuple);

   /*
    * In scan-and-sort mode, report increase in number of tuples
    * scanned
 */

   pgstat_progress_update_param(PROGRESS_CLUSTER_HEAP_TUPLES_SCANNED,
           *num_tuples);
  }
  else
  {
   const int ct_index[] = {
    PROGRESS_CLUSTER_HEAP_TUPLES_SCANNED,
    PROGRESS_CLUSTER_HEAP_TUPLES_WRITTEN
   };
   int64  ct_val[2];

   reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
          values, isnull, rwstate);

   /*
    * In indexscan mode and also VACUUM FULL, report increase in
    * number of tuples scanned and written
 */

   ct_val[0] = *num_tuples;
   ct_val[1] = *num_tuples;
   pgstat_progress_update_multi_param(2, ct_index, ct_val);
  }
 }

 if (indexScan != NULL)
  index_endscan(indexScan);
 if (tableScan != NULL)
  table_endscan(tableScan);
 if (slot)
  ExecDropSingleTupleTableSlot(slot);

 /*
  * In scan-and-sort mode, complete the sort, then read out all live tuples
  * from the tuplestore and write them to the new relation.
 */

 if (tuplesort != NULL)
 {
  double  n_tuples = 0;

  /* Report that we are now sorting tuples */
  pgstat_progress_update_param(PROGRESS_CLUSTER_PHASE,
          PROGRESS_CLUSTER_PHASE_SORT_TUPLES);

  tuplesort_performsort(tuplesort);

  /* Report that we are now writing new heap */
  pgstat_progress_update_param(PROGRESS_CLUSTER_PHASE,
          PROGRESS_CLUSTER_PHASE_WRITE_NEW_HEAP);

  for (;;)
  {
   HeapTuple tuple;

   CHECK_FOR_INTERRUPTS();

   tuple = tuplesort_getheaptuple(tuplesort, true);
   if (tuple == NULL)
    break;

   n_tuples += 1;
   reform_and_rewrite_tuple(tuple,
          OldHeap, NewHeap,
          values, isnull,
          rwstate);
   /* Report n_tuples */
   pgstat_progress_update_param(PROGRESS_CLUSTER_HEAP_TUPLES_WRITTEN,
           n_tuples);
  }

  tuplesort_end(tuplesort);
 }

 /* Write out any remaining tuples, and fsync if needed */
 end_heap_rewrite(rwstate);

 /* Clean up */
 pfree(values);
 pfree(isnull);
}

/*
 * Prepare to analyze the next block in the read stream.  Returns false if
 * the stream is exhausted and true otherwise. The scan must have been started
 * with SO_TYPE_ANALYZE option.
 *
 * This routine holds a buffer pin and lock on the heap page.  They are held
 * until heapam_scan_analyze_next_tuple() returns false.  That is until all the
 * items of the heap page are analyzed.
 */

static bool
heapam_scan_analyze_next_block(TableScanDesc scan, ReadStream *stream)
{
 HeapScanDesc hscan = (HeapScanDesc) scan;

 /*
  * We must maintain a pin on the target page's buffer to ensure that
  * concurrent activity - e.g. HOT pruning - doesn't delete tuples out from
  * under us.  It comes from the stream already pinned.   We also choose to
  * hold sharelock on the buffer throughout --- we could release and
  * re-acquire sharelock for each tuple, but since we aren't doing much
  * work per tuple, the extra lock traffic is probably better avoided.
 */

 hscan->rs_cbuf = read_stream_next_buffer(stream, NULL);
 if (!BufferIsValid(hscan->rs_cbuf))
  return false;

 LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);

 hscan->rs_cblock = BufferGetBlockNumber(hscan->rs_cbuf);
 hscan->rs_cindex = FirstOffsetNumber;
 return true;
}

static bool
heapam_scan_analyze_next_tuple(TableScanDesc scan, TransactionId OldestXmin,
          double *liverows, double *deadrows,
          TupleTableSlot *slot)
{
 HeapScanDesc hscan = (HeapScanDesc) scan;
 Page  targpage;
 OffsetNumber maxoffset;
 BufferHeapTupleTableSlot *hslot;

 Assert(TTS_IS_BUFFERTUPLE(slot));

 hslot = (BufferHeapTupleTableSlot *) slot;
 targpage = BufferGetPage(hscan->rs_cbuf);
 maxoffset = PageGetMaxOffsetNumber(targpage);

 /* Inner loop over all tuples on the selected page */
 for (; hscan->rs_cindex <= maxoffset; hscan->rs_cindex++)
 {
  ItemId  itemid;
  HeapTuple targtuple = &hslot->base.tupdata;
  bool  sample_it = false;

  itemid = PageGetItemId(targpage, hscan->rs_cindex);

  /*
   * We ignore unused and redirect line pointers.  DEAD line pointers
   * should be counted as dead, because we need vacuum to run to get rid
   * of them.  Note that this rule agrees with the way that
   * heap_page_prune_and_freeze() counts things.
 */

  if (!ItemIdIsNormal(itemid))
  {
   if (ItemIdIsDead(itemid))
    *deadrows += 1;
   continue;
  }

  ItemPointerSet(&targtuple->t_self, hscan->rs_cblock, hscan->rs_cindex);

  targtuple->t_tableOid = RelationGetRelid(scan->rs_rd);
  targtuple->t_data = (HeapTupleHeader) PageGetItem(targpage, itemid);
  targtuple->t_len = ItemIdGetLength(itemid);

  switch (HeapTupleSatisfiesVacuum(targtuple, OldestXmin,
           hscan->rs_cbuf))
  {
   case HEAPTUPLE_LIVE:
    sample_it = true;
    *liverows += 1;
    break;

   case HEAPTUPLE_DEAD:
   case HEAPTUPLE_RECENTLY_DEAD:
    /* Count dead and recently-dead rows */
    *deadrows += 1;
    break;

   case HEAPTUPLE_INSERT_IN_PROGRESS:

    /*
     * Insert-in-progress rows are not counted.  We assume that
     * when the inserting transaction commits or aborts, it will
     * send a stats message to increment the proper count.  This
     * works right only if that transaction ends after we finish
     * analyzing the table; if things happen in the other order,
     * its stats update will be overwritten by ours.  However, the
     * error will be large only if the other transaction runs long
     * enough to insert many tuples, so assuming it will finish
     * after us is the safer option.
     *
     * A special case is that the inserting transaction might be
     * our own.  In this case we should count and sample the row,
     * to accommodate users who load a table and analyze it in one
     * transaction.  (pgstat_report_analyze has to adjust the
     * numbers we report to the cumulative stats system to make
     * this come out right.)
 */

    if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(targtuple->t_data)))
    {
     sample_it = true;
     *liverows += 1;
    }
    break;

   case HEAPTUPLE_DELETE_IN_PROGRESS:

    /*
     * We count and sample delete-in-progress rows the same as
     * live ones, so that the stats counters come out right if the
     * deleting transaction commits after us, per the same
     * reasoning given above.
     *
     * If the delete was done by our own transaction, however, we
     * must count the row as dead to make pgstat_report_analyze's
     * stats adjustments come out right.  (Note: this works out
     * properly when the row was both inserted and deleted in our
     * xact.)
     *
     * The net effect of these choices is that we act as though an
     * IN_PROGRESS transaction hasn't happened yet, except if it
     * is our own transaction, which we assume has happened.
     *
     * This approach ensures that we behave sanely if we see both
     * the pre-image and post-image rows for a row being updated
     * by a concurrent transaction: we will sample the pre-image
     * but not the post-image.  We also get sane results if the
     * concurrent transaction never commits.
 */

    if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(targtuple->t_data)))
     *deadrows += 1;
    else
    {
     sample_it = true;
     *liverows += 1;
    }
    break;

   default:
    elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
    break;
  }

  if (sample_it)
  {
   ExecStoreBufferHeapTuple(targtuple, slot, hscan->rs_cbuf);
   hscan->rs_cindex++;

   /* note that we leave the buffer locked here! */
   return true;
  }
 }

 /* Now release the lock and pin on the page */
 UnlockReleaseBuffer(hscan->rs_cbuf);
 hscan->rs_cbuf = InvalidBuffer;

 /* also prevent old slot contents from having pin on page */
 ExecClearTuple(slot);

 return false;
}

static double
heapam_index_build_range_scan(Relation heapRelation,
         Relation indexRelation,
         IndexInfo *indexInfo,
         bool allow_sync,
         bool anyvisible,
         bool progress,
         BlockNumber start_blockno,
         BlockNumber numblocks,
         IndexBuildCallback callback,
         void *callback_state,
         TableScanDesc scan)
{
 HeapScanDesc hscan;
 bool  is_system_catalog;
 bool  checking_uniqueness;
 HeapTuple heapTuple;
 Datum  values[INDEX_MAX_KEYS];
 bool  isnull[INDEX_MAX_KEYS];
 double  reltuples;
 ExprState  *predicate;
 TupleTableSlot *slot;
 EState    *estate;
 ExprContext *econtext;
 Snapshot snapshot;
 bool  need_unregister_snapshot = false;
 TransactionId OldestXmin;
 BlockNumber previous_blkno = InvalidBlockNumber;
 BlockNumber root_blkno = InvalidBlockNumber;
 OffsetNumber root_offsets[MaxHeapTuplesPerPage];

 /*
  * sanity checks
 */

 Assert(OidIsValid(indexRelation->rd_rel->relam));

 /* Remember if it's a system catalog */
 is_system_catalog = IsSystemRelation(heapRelation);

 /* See whether we're verifying uniqueness/exclusion properties */
 checking_uniqueness = (indexInfo->ii_Unique ||
         indexInfo->ii_ExclusionOps != NULL);

 /*
  * "Any visible" mode is not compatible with uniqueness checks; make sure
  * only one of those is requested.
 */

 Assert(!(anyvisible && checking_uniqueness));

 /*
  * Need an EState for evaluation of index expressions and partial-index
  * predicates.  Also a slot to hold the current tuple.
 */

 estate = CreateExecutorState();
 econtext = GetPerTupleExprContext(estate);
 slot = table_slot_create(heapRelation, NULL);

 /* Arrange for econtext's scan tuple to be the tuple under test */
 econtext->ecxt_scantuple = slot;

 /* Set up execution state for predicate, if any. */
 predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);

 /*
  * Prepare for scan of the base relation.  In a normal index build, we use
  * SnapshotAny because we must retrieve all tuples and do our own time
  * qual checks (because we have to index RECENTLY_DEAD tuples). In a
  * concurrent build, or during bootstrap, we take a regular MVCC snapshot
  * and index whatever's live according to that.
 */

 OldestXmin = InvalidTransactionId;

 /* okay to ignore lazy VACUUMs here */
 if (!IsBootstrapProcessingMode() && !indexInfo->ii_Concurrent)
  OldestXmin = GetOldestNonRemovableTransactionId(heapRelation);

 if (!scan)
 {
  /*
   * Serial index build.
   *
   * Must begin our own heap scan in this case.  We may also need to
   * register a snapshot whose lifetime is under our direct control.
 */

  if (!TransactionIdIsValid(OldestXmin))
  {
   snapshot = RegisterSnapshot(GetTransactionSnapshot());
   need_unregister_snapshot = true;
  }
  else
   snapshot = SnapshotAny;

  scan = table_beginscan_strat(heapRelation, /* relation */
          snapshot, /* snapshot */
          0, /* number of keys */
          NULL, /* scan key */
          true, /* buffer access strategy OK */
          allow_sync); /* syncscan OK? */
 }
 else
 {
  /*
   * Parallel index build.
   *
   * Parallel case never registers/unregisters own snapshot.  Snapshot
   * is taken from parallel heap scan, and is SnapshotAny or an MVCC
   * snapshot, based on same criteria as serial case.
 */

  Assert(!IsBootstrapProcessingMode());
  Assert(allow_sync);
  snapshot = scan->rs_snapshot;
 }

 hscan = (HeapScanDesc) scan;

 /*
  * Must have called GetOldestNonRemovableTransactionId() if using
  * SnapshotAny.  Shouldn't have for an MVCC snapshot. (It's especially
  * worth checking this for parallel builds, since ambuild routines that
  * support parallel builds must work these details out for themselves.)
 */

 Assert(snapshot == SnapshotAny || IsMVCCSnapshot(snapshot));
 Assert(snapshot == SnapshotAny ? TransactionIdIsValid(OldestXmin) :
     !TransactionIdIsValid(OldestXmin));
 Assert(snapshot == SnapshotAny || !anyvisible);

 /* Publish number of blocks to scan */
 if (progress)
 {
  BlockNumber nblocks;

  if (hscan->rs_base.rs_parallel != NULL)
  {
   ParallelBlockTableScanDesc pbscan;

   pbscan = (ParallelBlockTableScanDesc) hscan->rs_base.rs_parallel;
   nblocks = pbscan->phs_nblocks;
  }
  else
   nblocks = hscan->rs_nblocks;

  pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_TOTAL,
          nblocks);
 }

 /* set our scan endpoints */
 if (!allow_sync)
  heap_setscanlimits(scan, start_blockno, numblocks);
 else
 {
  /* syncscan can only be requested on whole relation */
  Assert(start_blockno == 0);
  Assert(numblocks == InvalidBlockNumber);
 }

 reltuples = 0;

 /*
  * Scan all tuples in the base relation.
 */

 while ((heapTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
 {
  bool  tupleIsAlive;

  CHECK_FOR_INTERRUPTS();

  /* Report scan progress, if asked to. */
  if (progress)
  {
   BlockNumber blocks_done = heapam_scan_get_blocks_done(hscan);

   if (blocks_done != previous_blkno)
   {
    pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_DONE,
            blocks_done);
    previous_blkno = blocks_done;
   }
  }

  /*
   * When dealing with a HOT-chain of updated tuples, we want to index
   * the values of the live tuple (if any), but index it under the TID
   * of the chain's root tuple.  This approach is necessary to preserve
   * the HOT-chain structure in the heap. So we need to be able to find
   * the root item offset for every tuple that's in a HOT-chain.  When
   * first reaching a new page of the relation, call
   * heap_get_root_tuples() to build a map of root item offsets on the
   * page.
   *
   * It might look unsafe to use this information across buffer
   * lock/unlock.  However, we hold ShareLock on the table so no
   * ordinary insert/update/delete should occur; and we hold pin on the
   * buffer continuously while visiting the page, so no pruning
   * operation can occur either.
   *
   * In cases with only ShareUpdateExclusiveLock on the table, it's
   * possible for some HOT tuples to appear that we didn't know about
   * when we first read the page.  To handle that case, we re-obtain the
   * list of root offsets when a HOT tuple points to a root item that we
   * don't know about.
   *
   * Also, although our opinions about tuple liveness could change while
   * we scan the page (due to concurrent transaction commits/aborts),
   * the chain root locations won't, so this info doesn't need to be
   * rebuilt after waiting for another transaction.
   *
   * Note the implied assumption that there is no more than one live
   * tuple per HOT-chain --- else we could create more than one index
   * entry pointing to the same root tuple.
 */

  if (hscan->rs_cblock != root_blkno)
  {
   Page  page = BufferGetPage(hscan->rs_cbuf);

   LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);
   heap_get_root_tuples(page, root_offsets);
   LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);

   root_blkno = hscan->rs_cblock;
  }

  if (snapshot == SnapshotAny)
  {
   /* do our own time qual check */
   bool  indexIt;
   TransactionId xwait;

 recheck:

   /*
    * We could possibly get away with not locking the buffer here,
    * since caller should hold ShareLock on the relation, but let's
    * be conservative about it.  (This remark is still correct even
    * with HOT-pruning: our pin on the buffer prevents pruning.)
 */

   LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);

   /*
    * The criteria for counting a tuple as live in this block need to
    * match what analyze.c's heapam_scan_analyze_next_tuple() does,
    * otherwise CREATE INDEX and ANALYZE may produce wildly different
    * reltuples values, e.g. when there are many recently-dead
    * tuples.
 */

   switch (HeapTupleSatisfiesVacuum(heapTuple, OldestXmin,
            hscan->rs_cbuf))
   {
    case HEAPTUPLE_DEAD:
     /* Definitely dead, we can ignore it */
     indexIt = false;
     tupleIsAlive = false;
     break;
    case HEAPTUPLE_LIVE:
     /* Normal case, index and unique-check it */
     indexIt = true;
     tupleIsAlive = true;
     /* Count it as live, too */
     reltuples += 1;
     break;
    case HEAPTUPLE_RECENTLY_DEAD:

     /*
      * If tuple is recently deleted then we must index it
      * anyway to preserve MVCC semantics.  (Pre-existing
      * transactions could try to use the index after we finish
      * building it, and may need to see such tuples.)
      *
      * However, if it was HOT-updated then we must only index
      * the live tuple at the end of the HOT-chain.  Since this
      * breaks semantics for pre-existing snapshots, mark the
      * index as unusable for them.
      *
      * We don't count recently-dead tuples in reltuples, even
      * if we index them; see heapam_scan_analyze_next_tuple().
 */

     if (HeapTupleIsHotUpdated(heapTuple))
     {
      indexIt = false;
      /* mark the index as unsafe for old snapshots */
      indexInfo->ii_BrokenHotChain = true;
     }
     else
      indexIt = true;
     /* In any case, exclude the tuple from unique-checking */
     tupleIsAlive = false;
     break;
    case HEAPTUPLE_INSERT_IN_PROGRESS:

     /*
      * In "anyvisible" mode, this tuple is visible and we
      * don't need any further checks.
 */

     if (anyvisible)
     {
      indexIt = true;
      tupleIsAlive = true;
      reltuples += 1;
      break;
     }

     /*
      * Since caller should hold ShareLock or better, normally
      * the only way to see this is if it was inserted earlier
      * in our own transaction.  However, it can happen in
      * system catalogs, since we tend to release write lock
      * before commit there.  Give a warning if neither case
      * applies.
 */

     xwait = HeapTupleHeaderGetXmin(heapTuple->t_data);
     if (!TransactionIdIsCurrentTransactionId(xwait))
     {
      if (!is_system_catalog)
       elog(WARNING, "concurrent insert in progress within table \"%s\"",
         RelationGetRelationName(heapRelation));

      /*
       * If we are performing uniqueness checks, indexing
       * such a tuple could lead to a bogus uniqueness
       * failure.  In that case we wait for the inserting
       * transaction to finish and check again.
 */

      if (checking_uniqueness)
      {
       /*
        * Must drop the lock on the buffer before we wait
 */

       LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);
       XactLockTableWait(xwait, heapRelation,
             &heapTuple->t_self,
             XLTW_InsertIndexUnique);
       CHECK_FOR_INTERRUPTS();
       goto recheck;
      }
     }
     else
     {
      /*
       * For consistency with
       * heapam_scan_analyze_next_tuple(), count
       * HEAPTUPLE_INSERT_IN_PROGRESS tuples as live only
       * when inserted by our own transaction.
 */

      reltuples += 1;
     }

     /*
      * We must index such tuples, since if the index build
      * commits then they're good.
 */

     indexIt = true;
     tupleIsAlive = true;
     break;
    case HEAPTUPLE_DELETE_IN_PROGRESS:

     /*
      * As with INSERT_IN_PROGRESS case, this is unexpected
      * unless it's our own deletion or a system catalog; but
      * in anyvisible mode, this tuple is visible.
 */

     if (anyvisible)
     {
      indexIt = true;
      tupleIsAlive = false;
      reltuples += 1;
      break;
     }

     xwait = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
     if (!TransactionIdIsCurrentTransactionId(xwait))
     {
      if (!is_system_catalog)
       elog(WARNING, "concurrent delete in progress within table \"%s\"",
         RelationGetRelationName(heapRelation));

      /*
       * If we are performing uniqueness checks, assuming
       * the tuple is dead could lead to missing a
       * uniqueness violation.  In that case we wait for the
       * deleting transaction to finish and check again.
       *
       * Also, if it's a HOT-updated tuple, we should not
       * index it but rather the live tuple at the end of
       * the HOT-chain.  However, the deleting transaction
       * could abort, possibly leaving this tuple as live
       * after all, in which case it has to be indexed. The
       * only way to know what to do is to wait for the
       * deleting transaction to finish and check again.
 */

      if (checking_uniqueness ||
       HeapTupleIsHotUpdated(heapTuple))
      {
       /*
        * Must drop the lock on the buffer before we wait
 */

       LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);
       XactLockTableWait(xwait, heapRelation,
             &heapTuple->t_self,
             XLTW_InsertIndexUnique);
       CHECK_FOR_INTERRUPTS();
       goto recheck;
      }

      /*
       * Otherwise index it but don't check for uniqueness,
       * the same as a RECENTLY_DEAD tuple.
 */

      indexIt = true;

      /*
       * Count HEAPTUPLE_DELETE_IN_PROGRESS tuples as live,
       * if they were not deleted by the current
       * transaction.  That's what
       * heapam_scan_analyze_next_tuple() does, and we want
       * the behavior to be consistent.
 */

      reltuples += 1;
     }
     else if (HeapTupleIsHotUpdated(heapTuple))
     {
      /*
       * It's a HOT-updated tuple deleted by our own xact.
       * We can assume the deletion will commit (else the
       * index contents don't matter), so treat the same as
       * RECENTLY_DEAD HOT-updated tuples.
 */

      indexIt = false;
      /* mark the index as unsafe for old snapshots */
      indexInfo->ii_BrokenHotChain = true;
     }
     else
     {
      /*
       * It's a regular tuple deleted by our own xact. Index
       * it, but don't check for uniqueness nor count in
       * reltuples, the same as a RECENTLY_DEAD tuple.
 */

      indexIt = true;
     }
     /* In any case, exclude the tuple from unique-checking */
     tupleIsAlive = false;
     break;
    default:
     elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
     indexIt = tupleIsAlive = false; /* keep compiler quiet */
     break;
   }

   LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);

   if (!indexIt)
    continue;
  }
  else
  {
   /* heap_getnext did the time qual check */
   tupleIsAlive = true;
   reltuples += 1;
  }

  MemoryContextReset(econtext->ecxt_per_tuple_memory);

  /* Set up for predicate or expression evaluation */
  ExecStoreBufferHeapTuple(heapTuple, slot, hscan->rs_cbuf);

  /*
   * In a partial index, discard tuples that don't satisfy the
   * predicate.
 */

  if (predicate != NULL)
  {
   if (!ExecQual(predicate, econtext))
    continue;
  }

  /*
   * For the current heap tuple, extract all the attributes we use in
   * this index, and note which are null.  This also performs evaluation
   * of any expressions needed.
 */

  FormIndexDatum(indexInfo,
        slot,
        estate,
        values,
        isnull);

  /*
   * You'd think we should go ahead and build the index tuple here, but
   * some index AMs want to do further processing on the data first.  So
   * pass the values[] and isnull[] arrays, instead.
 */


  if (HeapTupleIsHeapOnly(heapTuple))
  {
   /*
    * For a heap-only tuple, pretend its TID is that of the root. See
    * src/backend/access/heap/README.HOT for discussion.
 */

   ItemPointerData tid;
   OffsetNumber offnum;

   offnum = ItemPointerGetOffsetNumber(&heapTuple->t_self);

   /*
    * If a HOT tuple points to a root that we don't know about,
    * obtain root items afresh.  If that still fails, report it as
    * corruption.
 */

   if (root_offsets[offnum - 1] == InvalidOffsetNumber)
   {
    Page  page = BufferGetPage(hscan->rs_cbuf);

    LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);
    heap_get_root_tuples(page, root_offsets);
    LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);
   }

   if (!OffsetNumberIsValid(root_offsets[offnum - 1]))
    ereport(ERROR,
      (errcode(ERRCODE_DATA_CORRUPTED),
       errmsg_internal("failed to find parent tuple for heap-only tuple at (%u,%u) in table \"%s\"",
           ItemPointerGetBlockNumber(&heapTuple->t_self),
           offnum,
           RelationGetRelationName(heapRelation))));

   ItemPointerSet(&tid, ItemPointerGetBlockNumber(&heapTuple->t_self),
         root_offsets[offnum - 1]);

   /* Call the AM's callback routine to process the tuple */
   callback(indexRelation, &tid, values, isnull, tupleIsAlive,
      callback_state);
  }
  else
  {
   /* Call the AM's callback routine to process the tuple */
   callback(indexRelation, &heapTuple->t_self, values, isnull,
      tupleIsAlive, callback_state);
  }
 }

 /* Report scan progress one last time. */
 if (progress)
 {
  BlockNumber blks_done;

  if (hscan->rs_base.rs_parallel != NULL)
  {
   ParallelBlockTableScanDesc pbscan;

   pbscan = (ParallelBlockTableScanDesc) hscan->rs_base.rs_parallel;
   blks_done = pbscan->phs_nblocks;
  }
  else
   blks_done = hscan->rs_nblocks;

  pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_DONE,
          blks_done);
 }

 table_endscan(scan);

 /* we can now forget our snapshot, if set and registered by us */
 if (need_unregister_snapshot)
  UnregisterSnapshot(snapshot);

 ExecDropSingleTupleTableSlot(slot);

 FreeExecutorState(estate);

 /* These may have been pointing to the now-gone estate */
 indexInfo->ii_ExpressionsState = NIL;
 indexInfo->ii_PredicateState = NULL;

 return reltuples;
}

static void
heapam_index_validate_scan(Relation heapRelation,
         Relation indexRelation,
         IndexInfo *indexInfo,
         Snapshot snapshot,
         ValidateIndexState *state)
{
 TableScanDesc scan;
 HeapScanDesc hscan;
 HeapTuple heapTuple;
 Datum  values[INDEX_MAX_KEYS];
 bool  isnull[INDEX_MAX_KEYS];
 ExprState  *predicate;
 TupleTableSlot *slot;
 EState    *estate;
 ExprContext *econtext;
 BlockNumber root_blkno = InvalidBlockNumber;
 OffsetNumber root_offsets[MaxHeapTuplesPerPage];
 bool  in_index[MaxHeapTuplesPerPage];
 BlockNumber previous_blkno = InvalidBlockNumber;

 /* state variables for the merge */
 ItemPointer indexcursor = NULL;
 ItemPointerData decoded;
 bool  tuplesort_empty = false;

 /*
  * sanity checks
 */

 Assert(OidIsValid(indexRelation->rd_rel->relam));

 /*
  * Need an EState for evaluation of index expressions and partial-index
  * predicates.  Also a slot to hold the current tuple.
 */

 estate = CreateExecutorState();
 econtext = GetPerTupleExprContext(estate);
 slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRelation),
         &TTSOpsHeapTuple);

 /* Arrange for econtext's scan tuple to be the tuple under test */
 econtext->ecxt_scantuple = slot;

 /* Set up execution state for predicate, if any. */
 predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);

 /*
  * Prepare for scan of the base relation.  We need just those tuples
  * satisfying the passed-in reference snapshot.  We must disable syncscan
  * here, because it's critical that we read from block zero forward to
  * match the sorted TIDs.
 */

 scan = table_beginscan_strat(heapRelation, /* relation */
         snapshot, /* snapshot */
         0, /* number of keys */
         NULL, /* scan key */
         true, /* buffer access strategy OK */
         false); /* syncscan not OK */
 hscan = (HeapScanDesc) scan;

 pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_TOTAL,
         hscan->rs_nblocks);

 /*
  * Scan all tuples matching the snapshot.
 */

 while ((heapTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
 {
  ItemPointer heapcursor = &heapTuple->t_self;
  ItemPointerData rootTuple;
  OffsetNumber root_offnum;

  CHECK_FOR_INTERRUPTS();

  state->htups += 1;

  if ((previous_blkno == InvalidBlockNumber) ||
   (hscan->rs_cblock != previous_blkno))
  {
   pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_DONE,
           hscan->rs_cblock);
   previous_blkno = hscan->rs_cblock;
  }

  /*
   * As commented in table_index_build_scan, we should index heap-only
   * tuples under the TIDs of their root tuples; so when we advance onto
   * a new heap page, build a map of root item offsets on the page.
   *
   * This complicates merging against the tuplesort output: we will
   * visit the live tuples in order by their offsets, but the root
   * offsets that we need to compare against the index contents might be
   * ordered differently.  So we might have to "look back" within the
   * tuplesort output, but only within the current page.  We handle that
   * by keeping a bool array in_index[] showing all the
   * already-passed-over tuplesort output TIDs of the current page. We
   * clear that array here, when advancing onto a new heap page.
 */

  if (hscan->rs_cblock != root_blkno)
  {
   Page  page = BufferGetPage(hscan->rs_cbuf);

   LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);
   heap_get_root_tuples(page, root_offsets);
   LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);

   memset(in_index, 0, sizeof(in_index));

   root_blkno = hscan->rs_cblock;
  }

  /* Convert actual tuple TID to root TID */
  rootTuple = *heapcursor;
  root_offnum = ItemPointerGetOffsetNumber(heapcursor);

  if (HeapTupleIsHeapOnly(heapTuple))
  {
   root_offnum = root_offsets[root_offnum - 1];
   if (!OffsetNumberIsValid(root_offnum))
    ereport(ERROR,
      (errcode(ERRCODE_DATA_CORRUPTED),
       errmsg_internal("failed to find parent tuple for heap-only tuple at (%u,%u) in table \"%s\"",
           ItemPointerGetBlockNumber(heapcursor),
           ItemPointerGetOffsetNumber(heapcursor),
           RelationGetRelationName(heapRelation))));
   ItemPointerSetOffsetNumber(&rootTuple, root_offnum);
  }

  /*
   * "merge" by skipping through the index tuples until we find or pass
   * the current root tuple.
 */

  while (!tuplesort_empty &&
      (!indexcursor ||
    ItemPointerCompare(indexcursor, &rootTuple) < 0))
  {
   Datum  ts_val;
   bool  ts_isnull;

   if (indexcursor)
   {
    /*
     * Remember index items seen earlier on the current heap page
 */

    if (ItemPointerGetBlockNumber(indexcursor) == root_blkno)
     in_index[ItemPointerGetOffsetNumber(indexcursor) - 1] = true;
   }

   tuplesort_empty = !tuplesort_getdatum(state->tuplesort, true,
              false, &ts_val, &ts_isnull,
              NULL);
   Assert(tuplesort_empty || !ts_isnull);
   if (!tuplesort_empty)
   {
    itemptr_decode(&decoded, DatumGetInt64(ts_val));
    indexcursor = &decoded;
   }
   else
   {
    /* Be tidy */
    indexcursor = NULL;
   }
  }

  /*
   * If the tuplesort has overshot *and* we didn't see a match earlier,
   * then this tuple is missing from the index, so insert it.
 */

  if ((tuplesort_empty ||
    ItemPointerCompare(indexcursor, &rootTuple) > 0) &&
   !in_index[root_offnum - 1])
  {
   MemoryContextReset(econtext->ecxt_per_tuple_memory);

   /* Set up for predicate or expression evaluation */
   ExecStoreHeapTuple(heapTuple, slot, false);

   /*
    * In a partial index, discard tuples that don't satisfy the
    * predicate.
 */

   if (predicate != NULL)
   {
    if (!ExecQual(predicate, econtext))
     continue;
   }

   /*
    * For the current heap tuple, extract all the attributes we use
    * in this index, and note which are null.  This also performs
    * evaluation of any expressions needed.
 */

   FormIndexDatum(indexInfo,
         slot,
         estate,
         values,
         isnull);

   /*
    * You'd think we should go ahead and build the index tuple here,
    * but some index AMs want to do further processing on the data
    * first. So pass the values[] and isnull[] arrays, instead.
 */


   /*
    * If the tuple is already committed dead, you might think we
    * could suppress uniqueness checking, but this is no longer true
    * in the presence of HOT, because the insert is actually a proxy
    * for a uniqueness check on the whole HOT-chain.  That is, the
    * tuple we have here could be dead because it was already
    * HOT-updated, and if so the updating transaction will not have
    * thought it should insert index entries.  The index AM will
    * check the whole HOT-chain and correctly detect a conflict if
    * there is one.
 */


   index_insert(indexRelation,
       values,
       isnull,
       &rootTuple,
       heapRelation,
       indexInfo->ii_Unique ?
       UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
       false,
       indexInfo);

   state->tups_inserted += 1;
  }
 }

 table_endscan(scan);

 ExecDropSingleTupleTableSlot(slot);

 FreeExecutorState(estate);

 /* These may have been pointing to the now-gone estate */
 indexInfo->ii_ExpressionsState = NIL;
 indexInfo->ii_PredicateState = NULL;
}

/*
 * Return the number of blocks that have been read by this scan since
 * starting.  This is meant for progress reporting rather than be fully
 * accurate: in a parallel scan, workers can be concurrently reading blocks
 * further ahead than what we report.
 */

static BlockNumber
heapam_scan_get_blocks_done(HeapScanDesc hscan)
{
 ParallelBlockTableScanDesc bpscan = NULL;
 BlockNumber startblock;
 BlockNumber blocks_done;

 if (hscan->rs_base.rs_parallel != NULL)
 {
  bpscan = (ParallelBlockTableScanDesc) hscan->rs_base.rs_parallel;
  startblock = bpscan->phs_startblock;
 }
 else
  startblock = hscan->rs_startblock;

 /*
  * Might have wrapped around the end of the relation, if startblock was
  * not zero.
 */

 if (hscan->rs_cblock > startblock)
  blocks_done = hscan->rs_cblock - startblock;
 else
 {
  BlockNumber nblocks;

  nblocks = bpscan != NULL ? bpscan->phs_nblocks : hscan->rs_nblocks;
  blocks_done = nblocks - startblock +
   hscan->rs_cblock;
 }

 return blocks_done;
}


/* ------------------------------------------------------------------------
 * Miscellaneous callbacks for the heap AM
 * ------------------------------------------------------------------------
 */


/*
 * Check to see whether the table needs a TOAST table.  It does only if
 * (1) there are any toastable attributes, and (2) the maximum length
 * of a tuple could exceed TOAST_TUPLE_THRESHOLD.  (We don't want to
 * create a toast table for something like "f1 varchar(20)".)
 */

static bool
heapam_relation_needs_toast_table(Relation rel)
{
 int32  data_length = 0;
 bool  maxlength_unknown = false;
 bool  has_toastable_attrs = false;
 TupleDesc tupdesc = rel->rd_att;
 int32  tuple_length;
 int   i;

 for (i = 0; i < tupdesc->natts; i++)
 {
  Form_pg_attribute att = TupleDescAttr(tupdesc, i);

  if (att->attisdropped)
   continue;
  if (att->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
   continue;
  data_length = att_align_nominal(data_length, att->attalign);
  if (att->attlen > 0)
  {
   /* Fixed-length types are never toastable */
   data_length += att->attlen;
  }
  else
  {
   int32  maxlen = type_maximum_size(att->atttypid,
               att->atttypmod);

   if (maxlen < 0)
    maxlength_unknown = true;
   else
    data_length += maxlen;
   if (att->attstorage != TYPSTORAGE_PLAIN)
    has_toastable_attrs = true;
  }
 }
 if (!has_toastable_attrs)
  return false;   /* nothing to toast? */
 if (maxlength_unknown)
  return true;   /* any unlimited-length attrs? */
 tuple_length = MAXALIGN(SizeofHeapTupleHeader +
       BITMAPLEN(tupdesc->natts)) +
  MAXALIGN(data_length);
 return (tuple_length > TOAST_TUPLE_THRESHOLD);
}

/*
 * TOAST tables for heap relations are just heap relations.
 */

static Oid
heapam_relation_toast_am(Relation rel)
{
 return rel->rd_rel->relam;
}


/* ------------------------------------------------------------------------
 * Planner related callbacks for the heap AM
 * ------------------------------------------------------------------------
 */


#define HEAP_OVERHEAD_BYTES_PER_TUPLE \
 (MAXALIGN(SizeofHeapTupleHeader) + sizeof(ItemIdData))
#define HEAP_USABLE_BYTES_PER_PAGE \
 (BLCKSZ - SizeOfPageHeaderData)

static void
heapam_estimate_rel_size(Relation rel, int32 *attr_widths,
       BlockNumber *pages, double *tuples,
       double *allvisfrac)
{
 table_block_relation_estimate_size(rel, attr_widths, pages,
            tuples, allvisfrac,
            HEAP_OVERHEAD_BYTES_PER_TUPLE,
            HEAP_USABLE_BYTES_PER_PAGE);
}


/* ------------------------------------------------------------------------
 * Executor related callbacks for the heap AM
 * ------------------------------------------------------------------------
 */


static bool
heapam_scan_bitmap_next_tuple(TableScanDesc scan,
         TupleTableSlot *slot,
         bool *recheck,
         uint64 *lossy_pages,
         uint64 *exact_pages)
{
 BitmapHeapScanDesc bscan = (BitmapHeapScanDesc) scan;
 HeapScanDesc hscan = (HeapScanDesc) bscan;
 OffsetNumber targoffset;
 Page  page;
 ItemId  lp;

 /*
  * Out of range?  If so, nothing more to look at on this page
 */

 while (hscan->rs_cindex >= hscan->rs_ntuples)
 {
  /*
   * Returns false if the bitmap is exhausted and there are no further
   * blocks we need to scan.
 */

  if (!BitmapHeapScanNextBlock(scan, recheck, lossy_pages, exact_pages))
   return false;
 }

 targoffset = hscan->rs_vistuples[hscan->rs_cindex];
 page = BufferGetPage(hscan->rs_cbuf);
 lp = PageGetItemId(page, targoffset);
 Assert(ItemIdIsNormal(lp));

 hscan->rs_ctup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
 hscan->rs_ctup.t_len = ItemIdGetLength(lp);
 hscan->rs_ctup.t_tableOid = scan->rs_rd->rd_id;
 ItemPointerSet(&hscan->rs_ctup.t_self, hscan->rs_cblock, targoffset);

 pgstat_count_heap_fetch(scan->rs_rd);

 /*
  * Set up the result slot to point to this tuple.  Note that the slot
  * acquires a pin on the buffer.
 */

 ExecStoreBufferHeapTuple(&hscan->rs_ctup,
        slot,
        hscan->rs_cbuf);

 hscan->rs_cindex++;

 return true;
}

static bool
heapam_scan_sample_next_block(TableScanDesc scan, SampleScanState *scanstate)
{
 HeapScanDesc hscan = (HeapScanDesc) scan;
 TsmRoutine *tsm = scanstate->tsmroutine;
 BlockNumber blockno;

 /* return false immediately if relation is empty */
 if (hscan->rs_nblocks == 0)
  return false;

 /* release previous scan buffer, if any */
 if (BufferIsValid(hscan->rs_cbuf))
 {
  ReleaseBuffer(hscan->rs_cbuf);
  hscan->rs_cbuf = InvalidBuffer;
 }

 if (tsm->NextSampleBlock)
  blockno = tsm->NextSampleBlock(scanstate, hscan->rs_nblocks);
 else
 {
  /* scanning table sequentially */

  if (hscan->rs_cblock == InvalidBlockNumber)
  {
   Assert(!hscan->rs_inited);
   blockno = hscan->rs_startblock;
  }
  else
  {
   Assert(hscan->rs_inited);

   blockno = hscan->rs_cblock + 1;

   if (blockno >= hscan->rs_nblocks)
   {
    /* wrap to beginning of rel, might not have started at 0 */
    blockno = 0;
   }

   /*
    * Report our new scan position for synchronization purposes.
    *
    * 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_flags & SO_ALLOW_SYNC)
    ss_report_location(scan->rs_rd, blockno);

   if (blockno == hscan->rs_startblock)
   {
    blockno = InvalidBlockNumber;
   }
  }
 }

 hscan->rs_cblock = blockno;

 if (!BlockNumberIsValid(blockno))
 {
  hscan->rs_inited = false;
  return false;
 }

 Assert(hscan->rs_cblock < hscan->rs_nblocks);

 /*
  * Be sure to check for interrupts at least once per page.  Checks at
  * higher code levels won't be able to stop a sample scan that encounters
  * many pages' worth of consecutive dead tuples.
 */

 CHECK_FOR_INTERRUPTS();

 /* Read page using selected strategy */
 hscan->rs_cbuf = ReadBufferExtended(hscan->rs_base.rs_rd, MAIN_FORKNUM,
          blockno, RBM_NORMAL, hscan->rs_strategy);

 /* in pagemode, prune the page and determine visible tuple offsets */
 if (hscan->rs_base.rs_flags & SO_ALLOW_PAGEMODE)
  heap_prepare_pagescan(scan);

 hscan->rs_inited = true;
 return true;
}

static bool
heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate,
         TupleTableSlot *slot)
{
 HeapScanDesc hscan = (HeapScanDesc) scan;
 TsmRoutine *tsm = scanstate->tsmroutine;
 BlockNumber blockno = hscan->rs_cblock;
 bool  pagemode = (scan->rs_flags & SO_ALLOW_PAGEMODE) != 0;

 Page  page;
 bool  all_visible;
 OffsetNumber maxoffset;

 /*
  * When not using pagemode, we must lock the buffer during tuple
  * visibility checks.
 */

 if (!pagemode)
  LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);

 page = (Page) BufferGetPage(hscan->rs_cbuf);
 all_visible = PageIsAllVisible(page) &&
  !scan->rs_snapshot->takenDuringRecovery;
 maxoffset = PageGetMaxOffsetNumber(page);

 for (;;)
 {
  OffsetNumber tupoffset;

  CHECK_FOR_INTERRUPTS();

  /* Ask the tablesample method which tuples to check on this page. */
  tupoffset = tsm->NextSampleTuple(scanstate,
           blockno,
           maxoffset);

  if (OffsetNumberIsValid(tupoffset))
  {
   ItemId  itemid;
   bool  visible;
   HeapTuple tuple = &(hscan->rs_ctup);

   /* Skip invalid tuple pointers. */
   itemid = PageGetItemId(page, tupoffset);
   if (!ItemIdIsNormal(itemid))
    continue;

   tuple->t_data = (HeapTupleHeader) PageGetItem(page, itemid);
   tuple->t_len = ItemIdGetLength(itemid);
   ItemPointerSet(&(tuple->t_self), blockno, tupoffset);


   if (all_visible)
    visible = true;
   else
    visible = SampleHeapTupleVisible(scan, hscan->rs_cbuf,
             tuple, tupoffset);

   /* in pagemode, heap_prepare_pagescan did this for us */
   if (!pagemode)
    HeapCheckForSerializableConflictOut(visible, scan->rs_rd, tuple,
             hscan->rs_cbuf, scan->rs_snapshot);

   /* Try next tuple from same page. */
   if (!visible)
    continue;

   /* Found visible tuple, return it. */
   if (!pagemode)
    LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);

   ExecStoreBufferHeapTuple(tuple, slot, hscan->rs_cbuf);

   /* Count successfully-fetched tuples as heap fetches */
   pgstat_count_heap_getnext(scan->rs_rd);

   return true;
  }
  else
  {
   /*
    * If we get here, it means we've exhausted the items on this page
    * and it's time to move to the next.
 */

   if (!pagemode)
    LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);

   ExecClearTuple(slot);
   return false;
  }
 }

 Assert(0);
}


/* ----------------------------------------------------------------------------
 *  Helper functions for the above.
 * ----------------------------------------------------------------------------
 */


/*
 * Reconstruct and rewrite the given tuple
 *
 * We cannot simply copy the tuple as-is, for several reasons:
 *
 * 1. We'd like to squeeze out the values of any dropped columns, both
 * to save space and to ensure we have no corner-case failures. (It's
 * possible for example that the new table hasn't got a TOAST table
 * and so is unable to store any large values of dropped cols.)
 *
 * 2. The tuple might not even be legal for the new table; this is
 * currently only known to happen as an after-effect of ALTER TABLE
 * SET WITHOUT OIDS.
 *
 * So, we must reconstruct the tuple from component Datums.
 */

static void
reform_and_rewrite_tuple(HeapTuple tuple,
       Relation OldHeap, Relation NewHeap,
       Datum *values, bool *isnull, RewriteState rwstate)
{
 TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
 TupleDesc newTupDesc = RelationGetDescr(NewHeap);
 HeapTuple copiedTuple;
 int   i;

 heap_deform_tuple(tuple, oldTupDesc, values, isnull);

 /* Be sure to null out any dropped columns */
 for (i = 0; i < newTupDesc->natts; i++)
 {
  if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
   isnull[i] = true;
 }

 copiedTuple = heap_form_tuple(newTupDesc, values, isnull);

 /* The heap rewrite module does the rest */
 rewrite_heap_tuple(rwstate, tuple, copiedTuple);

 heap_freetuple(copiedTuple);
}

/*
 * Check visibility of the tuple.
 */

static bool
SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
        HeapTuple tuple,
        OffsetNumber tupoffset)
{
 HeapScanDesc hscan = (HeapScanDesc) scan;

 if (scan->rs_flags & SO_ALLOW_PAGEMODE)
 {
  uint32  start = 0,
     end = hscan->rs_ntuples;

  /*
   * In pageatatime mode, heap_prepare_pagescan() already did visibility
   * checks, so just look at the info it left in rs_vistuples[].
   *
   * We use a binary search over the known-sorted array.  Note: we could
   * save some effort if we insisted that NextSampleTuple select tuples
   * in increasing order, but it's not clear that there would be enough
   * gain to justify the restriction.
 */

  while (start < end)
  {
   uint32  mid = start + (end - start) / 2;
   OffsetNumber curoffset = hscan->rs_vistuples[mid];

   if (tupoffset == curoffset)
    return true;
   else if (tupoffset < curoffset)
    end = mid;
   else
    start = mid + 1;
  }

  return false;
 }
 else
 {
  /* Otherwise, we have to check the tuple individually. */
  return HeapTupleSatisfiesVisibility(tuple, scan->rs_snapshot,
           buffer);
 }
}

/*
 * Helper function get the next block of a bitmap heap scan. Returns true when
 * it got the next block and saved it in the scan descriptor and false when
 * the bitmap and or relation are exhausted.
 */

static bool
BitmapHeapScanNextBlock(TableScanDesc scan,
      bool *recheck,
      uint64 *lossy_pages, uint64 *exact_pages)
{
 BitmapHeapScanDesc bscan = (BitmapHeapScanDesc) scan;
 HeapScanDesc hscan = (HeapScanDesc) bscan;
 BlockNumber block;
 void    *per_buffer_data;
 Buffer  buffer;
 Snapshot snapshot;
 int   ntup;
 TBMIterateResult *tbmres;
 OffsetNumber offsets[TBM_MAX_TUPLES_PER_PAGE];
 int   noffsets = -1;

 Assert(scan->rs_flags & SO_TYPE_BITMAPSCAN);
 Assert(hscan->rs_read_stream);

 hscan->rs_cindex = 0;
 hscan->rs_ntuples = 0;

 /* Release buffer containing previous block. */
 if (BufferIsValid(hscan->rs_cbuf))
 {
  ReleaseBuffer(hscan->rs_cbuf);
  hscan->rs_cbuf = InvalidBuffer;
 }

 hscan->rs_cbuf = read_stream_next_buffer(hscan->rs_read_stream,
            &per_buffer_data);

 if (BufferIsInvalid(hscan->rs_cbuf))
 {
  /* the bitmap is exhausted */
  return false;
 }

 Assert(per_buffer_data);

 tbmres = per_buffer_data;

 Assert(BlockNumberIsValid(tbmres->blockno));
 Assert(BufferGetBlockNumber(hscan->rs_cbuf) == tbmres->blockno);

 /* Exact pages need their tuple offsets extracted. */
 if (!tbmres->lossy)
  noffsets = tbm_extract_page_tuple(tbmres, offsets,
            TBM_MAX_TUPLES_PER_PAGE);

 *recheck = tbmres->recheck;

 block = hscan->rs_cblock = tbmres->blockno;
 buffer = hscan->rs_cbuf;
 snapshot = scan->rs_snapshot;

 ntup = 0;

 /*
  * Prune and repair fragmentation for the whole page, if possible.
 */

 heap_page_prune_opt(scan->rs_rd, buffer);

 /*
  * We must hold share lock on the buffer content while examining tuple
  * visibility.  Afterwards, however, the tuples we have found to be
  * visible are guaranteed good as long as we hold the buffer pin.
 */

 LockBuffer(buffer, BUFFER_LOCK_SHARE);

 /*
  * We need two separate strategies for lossy and non-lossy cases.
 */

 if (!tbmres->lossy)
 {
  /*
   * Bitmap is non-lossy, so we just look through the offsets listed in
   * tbmres; but we have to follow any HOT chain starting at each such
   * offset.
 */

  int   curslot;

  /* We must have extracted the tuple offsets by now */
  Assert(noffsets > -1);

  for (curslot = 0; curslot < noffsets; curslot++)
  {
   OffsetNumber offnum = offsets[curslot];
   ItemPointerData tid;
   HeapTupleData heapTuple;

   ItemPointerSet(&tid, block, offnum);
   if (heap_hot_search_buffer(&tid, scan->rs_rd, buffer, snapshot,
            &heapTuple, NULL, true))
    hscan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
  }
 }
 else
 {
  /*
   * Bitmap is lossy, so we must examine each line pointer on the page.
   * But we can ignore HOT chains, since we'll check each tuple anyway.
 */

  Page  page = BufferGetPage(buffer);
  OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
  OffsetNumber offnum;

  for (offnum = FirstOffsetNumber; offnum <= maxoff; offnum = OffsetNumberNext(offnum))
  {
   ItemId  lp;
   HeapTupleData loctup;
   bool  valid;

   lp = PageGetItemId(page, offnum);
   if (!ItemIdIsNormal(lp))
    continue;
   loctup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
   loctup.t_len = ItemIdGetLength(lp);
   loctup.t_tableOid = scan->rs_rd->rd_id;
   ItemPointerSet(&loctup.t_self, block, offnum);
   valid = HeapTupleSatisfiesVisibility(&loctup, snapshot, buffer);
   if (valid)
   {
    hscan->rs_vistuples[ntup++] = offnum;
    PredicateLockTID(scan->rs_rd, &loctup.t_self, snapshot,
         HeapTupleHeaderGetXmin(loctup.t_data));
   }
   HeapCheckForSerializableConflictOut(valid, scan->rs_rd, &loctup,
            buffer, snapshot);
  }
 }

 LockBuffer(buffer, BUFFER_LOCK_UNLOCK);

 Assert(ntup <= MaxHeapTuplesPerPage);
 hscan->rs_ntuples = ntup;

 if (tbmres->lossy)
  (*lossy_pages)++;
 else
  (*exact_pages)++;

 /*
  * Return true to indicate that a valid block was found and the bitmap is
  * not exhausted. If there are no visible tuples on this page,
  * hscan->rs_ntuples will be 0 and heapam_scan_bitmap_next_tuple() will
  * return false returning control to this function to advance to the next
  * block in the bitmap.
 */

 return true;
}

/* ------------------------------------------------------------------------
 * Definition of the heap table access method.
 * ------------------------------------------------------------------------
 */


static const TableAmRoutine heapam_methods = {
 .type = T_TableAmRoutine,

 .slot_callbacks = heapam_slot_callbacks,

 .scan_begin = heap_beginscan,
 .scan_end = heap_endscan,
 .scan_rescan = heap_rescan,
 .scan_getnextslot = heap_getnextslot,

 .scan_set_tidrange = heap_set_tidrange,
 .scan_getnextslot_tidrange = heap_getnextslot_tidrange,

 .parallelscan_estimate = table_block_parallelscan_estimate,
 .parallelscan_initialize = table_block_parallelscan_initialize,
 .parallelscan_reinitialize = table_block_parallelscan_reinitialize,

 .index_fetch_begin = heapam_index_fetch_begin,
 .index_fetch_reset = heapam_index_fetch_reset,
 .index_fetch_end = heapam_index_fetch_end,
 .index_fetch_tuple = heapam_index_fetch_tuple,

 .tuple_insert = heapam_tuple_insert,
 .tuple_insert_speculative = heapam_tuple_insert_speculative,
 .tuple_complete_speculative = heapam_tuple_complete_speculative,
 .multi_insert = heap_multi_insert,
 .tuple_delete = heapam_tuple_delete,
 .tuple_update = heapam_tuple_update,
 .tuple_lock = heapam_tuple_lock,

 .tuple_fetch_row_version = heapam_fetch_row_version,
 .tuple_get_latest_tid = heap_get_latest_tid,
 .tuple_tid_valid = heapam_tuple_tid_valid,
 .tuple_satisfies_snapshot = heapam_tuple_satisfies_snapshot,
 .index_delete_tuples = heap_index_delete_tuples,

 .relation_set_new_filelocator = heapam_relation_set_new_filelocator,
 .relation_nontransactional_truncate = heapam_relation_nontransactional_truncate,
 .relation_copy_data = heapam_relation_copy_data,
 .relation_copy_for_cluster = heapam_relation_copy_for_cluster,
 .relation_vacuum = heap_vacuum_rel,
 .scan_analyze_next_block = heapam_scan_analyze_next_block,
 .scan_analyze_next_tuple = heapam_scan_analyze_next_tuple,
 .index_build_range_scan = heapam_index_build_range_scan,
 .index_validate_scan = heapam_index_validate_scan,

 .relation_size = table_block_relation_size,
 .relation_needs_toast_table = heapam_relation_needs_toast_table,
 .relation_toast_am = heapam_relation_toast_am,
 .relation_fetch_toast_slice = heap_fetch_toast_slice,

 .relation_estimate_size = heapam_estimate_rel_size,

 .scan_bitmap_next_tuple = heapam_scan_bitmap_next_tuple,
 .scan_sample_next_block = heapam_scan_sample_next_block,
 .scan_sample_next_tuple = heapam_scan_sample_next_tuple
};


const TableAmRoutine *
GetHeapamTableAmRoutine(void)
{
 return &heapam_methods;
}

Datum
heap_tableam_handler(PG_FUNCTION_ARGS)
{
 PG_RETURN_POINTER(&heapam_methods);
}

Messung V0.5 in Prozent
C=64 H=95 G=80

[zur Elbe Produktseite wechseln0.199QuellennavigatorsAnalyse erneut starten2026-09-28]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1126864
#Domains=1897691