1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-23 13:29:09 +03:00

Add slot-based table AM index scan interface.

Add table_index_getnext_slot, a new table AM interface that serves as
the single entry point for both plain and index-only index scans that
use amgettuple.  The table AM's index_scan_begin callback resolves which
implementation the scan will use and stores it in an IndexScanDesc
function pointer (xs_getnext_slot), which table_index_getnext_slot
dispatches through.

The interface intentionally avoids using fixed TableAmRoutine members,
allowing the table AM to dynamically register an implementation based on
conditions known at the start of each scan.  That way a table AM can
provide specialized variants that help the compiler produce more
efficient code.  heapam provides two implementations: one for plain
index scans, and one for index-only scans.  An upcoming commit that adds
the amgetbatch interface will add two more.  (Table AMs can opt to
provide a single generic implementation, or to provide many specialized
implementations, as requirements dictate.)

heapam's xs_getnext_slot implementations make aggressive use of forced
inlining to ensure that plain and index-only code paths are fully
specialized at compile time, without duplicating the code they have in
common.  This specialization is necessary to keep icache and iTLB
pressure to a minimum.

The new interface moves visibility map checks required by index-only
scans out of the executor (and selfuncs.c) and into heapam, enabling
batching of visibility map lookups (though for now we continue to just
perform retail lookups).  Using the new higher level slot-based
interface greatly simplifies nodeIndexonlyscan.c, which no longer has to
deal with the visibility map directly.  More importantly, this is a
significant architectural improvement: table AMs can now implement
index-only scans that are not tied to heapam's visibility map.

Two users of the now removed table_index_fetch_tuple interface,
_bt_check_unique and unique_key_recheck, fundamentally need to pass a
TID to the table AM to perform constraint enforcement.  Neither actually
performs an index scan (even though their TIDs are taken from an index),
so neither has any use for most of the index scan machinery.  Switch
these callers over to fetch_tid, a new special-purpose table AM
interface for constraint enforcement code.  All true index scan callers
now use the new slot-based interface.  (Note that fetch_tid doesn't
perform on-access pruning, which matches how things worked prior to
Postgres 12 commits c2fe139c2 and 71bdc99d0.)

The VISITED_PAGES_LIMIT mechanism used by get_actual_variable_range to
cap scan overhead during planning is reworked to go through a new scan
descriptor field (xs_visited_pages_limit), rather than having selfuncs.c
count heap page fetches and terminate the scan itself.  This is
necessary because callers that use the new slot-based interface no
longer have direct access to which heap blocks were fetched.  Similarly,
nodeIndexonlyscan.c can no longer use InstrCountTuples2 to count heap
fetches during an EXPLAIN ANALYZE.  EXPLAIN ANALYZE now obtains this
information from a new IndexScanInstrumentation field, which table AMs
are required to maintain during index-only scans.

Though independently useful, this commit is preparatory work for an
upcoming commit that will add an amgetbatch index AM interface, where
the table AM takes full responsibility for managing the progress of
index scans.  The table AM determines when to request the next batch
from the index AM, at a time of its choosing.  A follow-on commit will
rely on this to prefetch table blocks during index scans.  Having a
single entry point for all index scan callers unambiguously puts the
table AM in control of the scan.

Author: Peter Geoghegan <pg@bowt.ie>
Reviewed-by: Andres Freund <andres@anarazel.de>
Reviewed-by: Tomas Vondra <tomas@vondra.me>
Reviewed-by: Rui Zhao <zhaorui126@gmail.com>
Discussion: https://postgr.es/m/CAH2-WzmYqhacBH161peAWb5eF=Ja7CFAQ+0jSEMq=qnfLVTOOg@mail.gmail.com
This commit is contained in:
Peter Geoghegan
2026-09-15 11:19:13 -04:00
parent 1a3e782e76
commit ddce1da5b1
31 changed files with 869 additions and 796 deletions

View File

@@ -395,7 +395,7 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
RelationGetRelationName(rel)); RelationGetRelationName(rel));
/* /*
* This assertion matches the one in index_getnext_tid(). See page * This assertion matches the one in heapam_indexscan.c. See page
* recycling/"visible to everyone" notes in nbtree README. * recycling/"visible to everyone" notes in nbtree README.
*/ */
Assert(TransactionIdIsValid(RecentXmin)); Assert(TransactionIdIsValid(RecentXmin));

View File

@@ -665,7 +665,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
tableScan = NULL; tableScan = NULL;
heapScan = NULL; heapScan = NULL;
indexScan = index_beginscan(OldHeap, OldIndex, indexScan = index_beginscan(OldHeap, OldIndex, false,
snapshot ? snapshot : SnapshotAny, snapshot ? snapshot : SnapshotAny,
NULL, 0, 0, NULL, 0, 0,
SO_NONE); SO_NONE);
@@ -708,7 +708,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
if (indexScan != NULL) if (indexScan != NULL)
{ {
if (!index_getnext_slot(indexScan, ForwardScanDirection, slot)) if (!table_index_getnext_slot(indexScan, ForwardScanDirection,
slot))
break; break;
/* Since we used no scan keys, should never need to recheck */ /* Since we used no scan keys, should never need to recheck */
@@ -2665,10 +2666,9 @@ static const TableAmRoutine heapam_methods = {
.parallelscan_initialize = table_block_parallelscan_initialize, .parallelscan_initialize = table_block_parallelscan_initialize,
.parallelscan_reinitialize = table_block_parallelscan_reinitialize, .parallelscan_reinitialize = table_block_parallelscan_reinitialize,
.index_fetch_begin = heapam_index_fetch_begin, .index_scan_begin = heapam_index_scan_begin,
.index_fetch_reset = heapam_index_fetch_reset, .index_scan_reset = heapam_index_scan_reset,
.index_fetch_end = heapam_index_fetch_end, .index_scan_end = heapam_index_scan_end,
.index_fetch_tuple = heapam_index_fetch_tuple,
.tuple_insert = heapam_tuple_insert, .tuple_insert = heapam_tuple_insert,
.tuple_insert_speculative = heapam_tuple_insert_speculative, .tuple_insert_speculative = heapam_tuple_insert_speculative,
@@ -2678,6 +2678,7 @@ static const TableAmRoutine heapam_methods = {
.tuple_update = heapam_tuple_update, .tuple_update = heapam_tuple_update,
.tuple_lock = heapam_tuple_lock, .tuple_lock = heapam_tuple_lock,
.fetch_tid = heapam_fetch_tid,
.tuple_fetch_row_version = heapam_fetch_row_version, .tuple_fetch_row_version = heapam_fetch_row_version,
.tuple_get_latest_tid = heap_get_latest_tid, .tuple_get_latest_tid = heap_get_latest_tid,
.tuple_tid_valid = heapam_tuple_tid_valid, .tuple_tid_valid = heapam_tuple_tid_valid,

View File

@@ -14,36 +14,93 @@
*/ */
#include "postgres.h" #include "postgres.h"
#include "access/amapi.h"
#include "access/heapam.h" #include "access/heapam.h"
#include "access/relscan.h" #include "access/relscan.h"
#include "access/tableam_indexscan.h"
#include "access/visibilitymap.h"
#include "pgstat.h"
#include "storage/predicate.h" #include "storage/predicate.h"
static bool heapam_index_plain_tuple_getnext_slot(IndexScanDesc scan,
ScanDirection direction,
TupleTableSlot *slot);
static bool heapam_index_only_tuple_getnext_slot(IndexScanDesc scan,
ScanDirection direction,
TupleTableSlot *slot);
static pg_always_inline bool heapam_index_getnext_slot(IndexScanDesc scan,
ScanDirection direction,
TupleTableSlot *slot,
bool index_only);
static pg_always_inline bool heapam_index_heap_fetch(IndexScanDesc scan,
IndexScanHeapData *hscan,
TupleTableSlot *slot,
bool index_only);
static pg_noinline bool heapam_index_only_heap_fetch(IndexScanDesc scan);
static pg_noinline void heapam_index_kill_item(IndexScanDesc scan);
static inline bool heapam_index_visited_pages_exceeded(IndexScanDesc scan);
/*
* TID-based lookup used by constraint enforcement code (e.g., unique index
* enforcement).
*
* This isn't actually used by index scans, but this is as good a place for it
* as anywhere else.
*/
bool
heapam_fetch_tid(Relation rel, ItemPointer tid, Snapshot snapshot,
bool *all_dead)
{
HeapTupleData heapTuple;
Buffer buf;
bool found;
buf = ReadBuffer(rel, ItemPointerGetBlockNumber(tid));
LockBuffer(buf, BUFFER_LOCK_SHARE);
found = heap_hot_search_buffer(tid, rel, buf, snapshot, &heapTuple,
all_dead, true);
UnlockReleaseBuffer(buf);
return found;
}
/* ------------------------------------------------------------------------ /* ------------------------------------------------------------------------
* Index Scan Callbacks for heap AM * Index Scan Callbacks for heap AM
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------
*/ */
IndexFetchTableData * void
heapam_index_fetch_begin(Relation rel, uint32 flags) heapam_index_scan_begin(IndexScanDesc scan, uint32 flags)
{ {
IndexFetchHeapData *hscan = palloc0_object(IndexFetchHeapData); IndexScanHeapData *hscan = palloc0_object(IndexScanHeapData);
hscan->xs_base.rel = rel;
hscan->xs_base.flags = flags;
hscan->xs_cbuf = InvalidBuffer; hscan->xs_cbuf = InvalidBuffer;
hscan->xs_blk = InvalidBlockNumber; hscan->xs_blk = InvalidBlockNumber;
hscan->xs_vmbuffer = InvalidBuffer; hscan->xs_vmbuffer = InvalidBuffer;
return &hscan->xs_base; /* Remember if scan is read-only */
hscan->xs_readonly = (flags & SO_HINT_REL_READ_ONLY) != 0;
/* Resolve which xs_getnext_slot implementation to use for this scan */
if (scan->xs_want_itup)
scan->xs_getnext_slot = heapam_index_only_tuple_getnext_slot;
else
scan->xs_getnext_slot = heapam_index_plain_tuple_getnext_slot;
/* Expose heapam's private scan state through the scan's opaque pointer */
scan->xs_table_opaque = hscan;
} }
void void
heapam_index_fetch_reset(IndexFetchTableData *scan) heapam_index_scan_reset(IndexScanDesc scan)
{ {
IndexScanHeapData *hscan = (IndexScanHeapData *) scan->xs_table_opaque;
/* Heap fetches from the last rescan don't count towards this limit */
hscan->xs_blkswitch_count = 0;
/* /*
* Resets are a no-op.
*
* Deliberately avoid dropping pins now held in xs_cbuf and xs_vmbuffer. * Deliberately avoid dropping pins now held in xs_cbuf and xs_vmbuffer.
* This saves cycles during certain tight nested loop joins (it can avoid * This saves cycles during certain tight nested loop joins (it can avoid
* repeated pinning and unpinning of the same buffer across rescans). * repeated pinning and unpinning of the same buffer across rescans).
@@ -51,9 +108,9 @@ heapam_index_fetch_reset(IndexFetchTableData *scan)
} }
void void
heapam_index_fetch_end(IndexFetchTableData *scan) heapam_index_scan_end(IndexScanDesc scan)
{ {
IndexFetchHeapData *hscan = (IndexFetchHeapData *) scan; IndexScanHeapData *hscan = (IndexScanHeapData *) scan->xs_table_opaque;
/* drop pin if there's a pinned heap page */ /* drop pin if there's a pinned heap page */
if (BufferIsValid(hscan->xs_cbuf)) if (BufferIsValid(hscan->xs_cbuf))
@@ -228,38 +285,195 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
return false; return false;
} }
bool /* xs_getnext_slot callback: amgettuple, plain index scan */
heapam_index_fetch_tuple(struct IndexFetchTableData *scan, static bool
ItemPointer tid, heapam_index_plain_tuple_getnext_slot(IndexScanDesc scan,
Snapshot snapshot, ScanDirection direction,
TupleTableSlot *slot, TupleTableSlot *slot)
bool *heap_continue, bool *all_dead)
{ {
IndexFetchHeapData *hscan = (IndexFetchHeapData *) scan; Assert(!scan->xs_want_itup);
BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; Assert(scan->indexRelation->rd_indam->amgettuple != NULL);
bool got_heap_tuple;
Assert(TTS_IS_BUFFERTUPLE(slot)); return heapam_index_getnext_slot(scan, direction, slot, false);
}
/* xs_getnext_slot callback: amgettuple, index-only scan */
static bool
heapam_index_only_tuple_getnext_slot(IndexScanDesc scan,
ScanDirection direction,
TupleTableSlot *slot)
{
Assert(scan->xs_want_itup);
Assert(scan->indexRelation->rd_indam->amgettuple != NULL);
return heapam_index_getnext_slot(scan, direction, slot, true);
}
/*
* Common implementation for both heapam_index_*_getnext_slot variants.
*
* The result is true if a tuple satisfying the scan keys and the snapshot was
* found, false otherwise. This is per the table_index_getnext_slot
* interface.
*
* The index_only parameter is a compile-time constant at each call site,
* allowing the compiler to specialize the code for each variant.
*/
static pg_always_inline bool
heapam_index_getnext_slot(IndexScanDesc scan, ScanDirection direction,
TupleTableSlot *slot, bool index_only)
{
Assert(TransactionIdIsValid(RecentXmin));
Assert(index_only || scan->xs_visited_pages_limit == 0);
for (;;)
{
IndexScanHeapData *hscan;
bool all_visible;
/*
* Get the next TID from the index, unless we're still working through
* a HOT chain (index-only scans never do that, and plain index scans
* only do it with a non-MVCC snapshot)
*/
Assert(!index_only || !scan->xs_heap_continue);
if (index_only || likely(!scan->xs_heap_continue))
{
if (!tableam_index_getnext_tid(scan, direction))
return false;
}
/* The scan's next TID was set in scan->xs_heaptid for us */
Assert(ItemPointerIsValid(&scan->xs_heaptid));
hscan = (IndexScanHeapData *) scan->xs_table_opaque;
if (!index_only)
{
/* Plain index scan */
if (!heapam_index_heap_fetch(scan, hscan, slot, false))
continue; /* no visible tuple, try next index entry */
}
else
{
/*
* Note: VM_ALL_VISIBLE does not lock the visibility map buffer,
* so the result could be slightly stale. See the comments above
* visibilitymap_get_status for why this is okay.
*/
all_visible = VM_ALL_VISIBLE(scan->heapRelation,
ItemPointerGetBlockNumber(&scan->xs_heaptid),
&hscan->xs_vmbuffer);
/* Page isn't all-visible, so verify visibility with a heap fetch */
if (unlikely(!all_visible))
{
if (!heapam_index_only_heap_fetch(scan))
{
/* No visible tuple */
if (heapam_index_visited_pages_exceeded(scan))
return false; /* give up */
continue; /* try next index entry */
}
}
else
{
/*
* Index-only scan with all-visible item.
*
* We won't access the heap, so we'll need to take a predicate
* lock explicitly, as if we had. For now we do that at page
* level.
*/
PredicateLockPage(scan->heapRelation,
ItemPointerGetBlockNumber(&scan->xs_heaptid),
scan->xs_snapshot);
}
/*
* Fill slot with data returned by the index AM (during plain
* scans heapam_index_heap_fetch does this for us instead)
*/
tableam_index_fill_ios_slot(scan, slot);
}
return true;
}
pg_unreachable();
return false;
}
/*
* Get the scan's next heap tuple.
*
* Returns true if a visible heap tuple associated with the index TID most
* recently fetched by our caller in scan->xs_heaptid was found, false if no
* more matching tuples exist. (There can be more than one matching tuple
* because of HOT chains, although when using an MVCC snapshot it should be
* impossible for more than one such tuple to exist.)
*
* Plain index scans have us store the tuple in their slot, and its buffer
* stays pinned until a later call here (or heapam_index_scan_end) releases
* it. Index-only scans just need us to verify tuple visibility, so they pass
* a NULL slot.
*
* When the TID's whole HOT chain turns out to be dead, we arrange for the
* index AM to kill its entry for the TID before returning false.
*/
static pg_always_inline bool
heapam_index_heap_fetch(IndexScanDesc scan, IndexScanHeapData *hscan,
TupleTableSlot *slot, bool index_only)
{
Relation rel = scan->heapRelation;
ItemPointer tid = &scan->xs_heaptid;
Snapshot snapshot = scan->xs_snapshot;
HeapTupleData tupdata;
HeapTuple heapTuple;
bool got_heap_tuple;
bool all_dead;
if (!index_only)
{
/* Plain index scans have us store fetched tuple in their slot */
BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
Assert(TTS_IS_BUFFERTUPLE(slot));
heapTuple = &bslot->base.tupdata;
}
else
{
/* Index-only scans only need to verify tuple visibility */
pg_assume(slot == NULL);
heapTuple = &tupdata;
if (scan->instrument)
scan->instrument->ntabletuplefetches++;
}
/* We can skip the buffer-switching logic if we're on the same page. */ /* We can skip the buffer-switching logic if we're on the same page. */
if (hscan->xs_blk != ItemPointerGetBlockNumber(tid)) if (hscan->xs_blk != ItemPointerGetBlockNumber(tid))
{ {
Assert(!*heap_continue); Assert(!scan->xs_heap_continue);
/* Remember this buffer's block number for next time */ /* Remember this buffer's block number for next time */
hscan->xs_blk = ItemPointerGetBlockNumber(tid); hscan->xs_blk = ItemPointerGetBlockNumber(tid);
/* We're switching to a new heap block, so count it */
hscan->xs_blkswitch_count++;
if (BufferIsValid(hscan->xs_cbuf)) if (BufferIsValid(hscan->xs_cbuf))
ReleaseBuffer(hscan->xs_cbuf); ReleaseBuffer(hscan->xs_cbuf);
hscan->xs_cbuf = ReadBuffer(hscan->xs_base.rel, hscan->xs_blk); hscan->xs_cbuf = ReadBuffer(rel, hscan->xs_blk);
/* /*
* Prune page when it is pinned for the first time * Prune page when it is pinned for the first time
*/ */
heap_page_prune_opt(hscan->xs_base.rel, hscan->xs_cbuf, heap_page_prune_opt(rel, hscan->xs_cbuf, &hscan->xs_vmbuffer,
&hscan->xs_vmbuffer, hscan->xs_readonly);
hscan->xs_base.flags & SO_HINT_REL_READ_ONLY);
} }
Assert(BufferGetBlockNumber(hscan->xs_cbuf) == hscan->xs_blk); Assert(BufferGetBlockNumber(hscan->xs_cbuf) == hscan->xs_blk);
@@ -268,31 +482,106 @@ heapam_index_fetch_tuple(struct IndexFetchTableData *scan,
/* Obtain share-lock on the buffer so we can examine visibility */ /* Obtain share-lock on the buffer so we can examine visibility */
LockBuffer(hscan->xs_cbuf, BUFFER_LOCK_SHARE); LockBuffer(hscan->xs_cbuf, BUFFER_LOCK_SHARE);
got_heap_tuple = heap_hot_search_buffer(tid, got_heap_tuple = heap_hot_search_buffer(tid,
hscan->xs_base.rel, rel,
hscan->xs_cbuf, hscan->xs_cbuf,
snapshot, snapshot,
&bslot->base.tupdata, heapTuple,
all_dead, &all_dead,
!*heap_continue); !scan->xs_heap_continue);
bslot->base.tupdata.t_self = *tid; heapTuple->t_self = *tid;
LockBuffer(hscan->xs_cbuf, BUFFER_LOCK_UNLOCK); LockBuffer(hscan->xs_cbuf, BUFFER_LOCK_UNLOCK);
if (got_heap_tuple) if (got_heap_tuple)
{ {
/* if (!index_only)
* Only in a non-MVCC snapshot can more than one member of the HOT {
* chain be visible. /*
*/ * Only with a non-MVCC snapshot can more than one HOT chain
*heap_continue = !IsMVCCLikeSnapshot(snapshot); * member be visible, so only then must we keep walking the chain
*/
scan->xs_heap_continue = !IsMVCCLikeSnapshot(snapshot);
slot->tts_tableOid = RelationGetRelid(scan->rel); ExecStoreBufferHeapTuple(heapTuple, slot, hscan->xs_cbuf);
ExecStoreBufferHeapTuple(&bslot->base.tupdata, slot, hscan->xs_cbuf);
Assert(slot->tts_tableOid == RelationGetRelid(rel));
}
else
{
/*
* Index-only scans stop at the first visible HOT chain member.
* With a non-MVCC snapshot a later member could also be visible,
* but we never look. That's fine for the only non-MVCC
* index-only scan caller (selfuncs.c), which only needs to know
* that some version is visible.
*/
scan->xs_heap_continue = false;
}
pgstat_count_heap_fetch(scan->indexRelation);
} }
else else
{ {
/* We've reached the end of the HOT chain. */ /* We've reached the end of the HOT chain. */
*heap_continue = false; scan->xs_heap_continue = false;
if (unlikely(all_dead))
heapam_index_kill_item(scan);
} }
return got_heap_tuple; return got_heap_tuple;
} }
/*
* Out-of-line heapam_index_heap_fetch wrapper for index-only scans.
*
* Index-only scans usually avoid heap fetches using the visibility map, so
* keeping their fetch out of line keeps the frame of their getnext_slot
* callback small.
*/
static pg_noinline bool
heapam_index_only_heap_fetch(IndexScanDesc scan)
{
IndexScanHeapData *hscan = (IndexScanHeapData *) scan->xs_table_opaque;
return heapam_index_heap_fetch(scan, hscan, NULL, true);
}
/*
* Called when we scanned a whole HOT chain and found only dead tuples:
* arrange for the index AM to kill its entry for that TID. We do not do this
* when in recovery because it may violate MVCC to do so. See comments in
* RelationGetIndexScan().
*/
static pg_noinline void
heapam_index_kill_item(IndexScanDesc scan)
{
if (scan->xactStartedInRecovery)
return;
/*
* Tell amgettuple-based index AM to kill its entry for that TID. The
* next tableam_index_getnext_tid call will pass that along to the index
* AM, before unsetting the flag again.
*/
scan->kill_prior_tuple = true;
}
/*
* Did an index-only scan switch heap pages more times than the caller's
* visited-pages limit allows?
*
* Caller passes scan rather than hscan to avoiding keeping hscan live across
* heap fetches.
*/
static inline bool
heapam_index_visited_pages_exceeded(IndexScanDesc scan)
{
IndexScanHeapData *hscan;
if (likely(scan->xs_visited_pages_limit == 0))
return false;
hscan = (IndexScanHeapData *) scan->xs_table_opaque;
return hscan->xs_blkswitch_count > scan->xs_visited_pages_limit;
}

View File

@@ -84,7 +84,7 @@ RelationGetIndexScan(Relation indexRelation, int nkeys, int norderbys)
scan = palloc_object(IndexScanDescData); scan = palloc_object(IndexScanDescData);
scan->heapRelation = NULL; /* may be set later */ scan->heapRelation = NULL; /* may be set later */
scan->xs_heapfetch = NULL; scan->xs_table_opaque = NULL;
scan->indexRelation = indexRelation; scan->indexRelation = indexRelation;
scan->xs_snapshot = InvalidSnapshot; /* caller must initialize this */ scan->xs_snapshot = InvalidSnapshot; /* caller must initialize this */
scan->numberOfKeys = nkeys; scan->numberOfKeys = nkeys;
@@ -126,6 +126,14 @@ RelationGetIndexScan(Relation indexRelation, int nkeys, int norderbys)
scan->xs_hitup = NULL; scan->xs_hitup = NULL;
scan->xs_hitupdesc = NULL; scan->xs_hitupdesc = NULL;
scan->xs_getnext_slot = NULL;
scan->xs_name_cstring_attnums = NULL;
scan->xs_name_cstring_buf = NULL;
scan->xs_name_cstring_count = 0;
scan->xs_visited_pages_limit = 0;
return scan; return scan;
} }
@@ -148,6 +156,10 @@ IndexScanEnd(IndexScanDesc scan)
pfree(scan->keyData); pfree(scan->keyData);
if (scan->orderByData != NULL) if (scan->orderByData != NULL)
pfree(scan->orderByData); pfree(scan->orderByData);
if (scan->xs_name_cstring_attnums != NULL)
pfree(scan->xs_name_cstring_attnums);
if (scan->xs_name_cstring_buf != NULL)
pfree(scan->xs_name_cstring_buf);
pfree(scan); pfree(scan);
} }
@@ -454,7 +466,7 @@ systable_beginscan(Relation heapRelation,
elog(ERROR, "column is not in index"); elog(ERROR, "column is not in index");
} }
sysscan->iscan = index_beginscan(heapRelation, irel, sysscan->iscan = index_beginscan(heapRelation, irel, false,
snapshot, NULL, nkeys, 0, snapshot, NULL, nkeys, 0,
SO_NONE); SO_NONE);
index_rescan(sysscan->iscan, idxkey, nkeys, NULL, 0); index_rescan(sysscan->iscan, idxkey, nkeys, NULL, 0);
@@ -518,7 +530,8 @@ systable_getnext(SysScanDesc sysscan)
if (sysscan->irel) if (sysscan->irel)
{ {
if (index_getnext_slot(sysscan->iscan, ForwardScanDirection, sysscan->slot)) if (table_index_getnext_slot(sysscan->iscan, ForwardScanDirection,
sysscan->slot))
{ {
bool shouldFree; bool shouldFree;
@@ -643,9 +656,9 @@ systable_endscan(SysScanDesc sysscan)
* we could do a heapscan and sort, but the uses are in places that * we could do a heapscan and sort, but the uses are in places that
* probably don't need to still work with corrupted catalog indexes.) * probably don't need to still work with corrupted catalog indexes.)
* For the moment, therefore, these functions are merely the thinest of * For the moment, therefore, these functions are merely the thinest of
* wrappers around index_beginscan/index_getnext_slot. The main reason for * wrappers around index_beginscan/table_index_getnext_slot. The main reason
* their existence is to centralize possible future support of lossy operators * for their existence is to centralize possible future support of lossy
* in catalog scans. * operators in catalog scans.
*/ */
SysScanDesc SysScanDesc
systable_beginscan_ordered(Relation heapRelation, systable_beginscan_ordered(Relation heapRelation,
@@ -716,7 +729,7 @@ systable_beginscan_ordered(Relation heapRelation,
if (TransactionIdIsValid(CheckXidAlive)) if (TransactionIdIsValid(CheckXidAlive))
bsysscan = true; bsysscan = true;
sysscan->iscan = index_beginscan(heapRelation, indexRelation, sysscan->iscan = index_beginscan(heapRelation, indexRelation, false,
snapshot, NULL, nkeys, 0, snapshot, NULL, nkeys, 0,
SO_NONE); SO_NONE);
index_rescan(sysscan->iscan, idxkey, nkeys, NULL, 0); index_rescan(sysscan->iscan, idxkey, nkeys, NULL, 0);
@@ -736,7 +749,7 @@ systable_getnext_ordered(SysScanDesc sysscan, ScanDirection direction)
HeapTuple htup = NULL; HeapTuple htup = NULL;
Assert(sysscan->irel); Assert(sysscan->irel);
if (index_getnext_slot(sysscan->iscan, direction, sysscan->slot)) if (table_index_getnext_slot(sysscan->iscan, direction, sysscan->slot))
htup = ExecFetchSlotHeapTuple(sysscan->slot, false, NULL); htup = ExecFetchSlotHeapTuple(sysscan->slot, false, NULL);
/* See notes in systable_getnext */ /* See notes in systable_getnext */

View File

@@ -24,9 +24,6 @@
* index_parallelscan_initialize - initialize parallel scan * index_parallelscan_initialize - initialize parallel scan
* index_parallelrescan - (re)start a parallel scan of an index * index_parallelrescan - (re)start a parallel scan of an index
* index_beginscan_parallel - join parallel index scan * index_beginscan_parallel - join parallel index scan
* index_getnext_tid - get the next TID from a scan
* index_fetch_heap - get the scan's next heap tuple
* index_getnext_slot - get the next tuple from a scan
* index_getbitmap - get all tuples from a scan * index_getbitmap - get all tuples from a scan
* index_bulk_delete - bulk deletion of index tuples * index_bulk_delete - bulk deletion of index tuples
* index_vacuum_cleanup - post-deletion cleanup of an index * index_vacuum_cleanup - post-deletion cleanup of an index
@@ -105,10 +102,17 @@ do { \
CppAsString(pname), RelationGetRelationName(scan->indexRelation)); \ CppAsString(pname), RelationGetRelationName(scan->indexRelation)); \
} while(0) } while(0)
static IndexScanDesc index_beginscan_internal(Relation indexRelation,
int nkeys, int norderbys, Snapshot snapshot,
ParallelIndexScanDesc pscan, bool temp_snap);
static inline void validate_relation_as_index(Relation r); static inline void validate_relation_as_index(Relation r);
static pg_always_inline IndexScanDesc index_beginscan_internal(Relation indexRelation,
Relation heapRelation,
int nkeys,
int norderbys,
Snapshot snapshot,
ParallelIndexScanDesc pscan,
IndexScanInstrumentation *instrument,
bool index_only_scan,
bool temp_snap,
uint32 flags);
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
@@ -256,14 +260,14 @@ index_insert_cleanup(Relation indexRelation,
IndexScanDesc IndexScanDesc
index_beginscan(Relation heapRelation, index_beginscan(Relation heapRelation,
Relation indexRelation, Relation indexRelation,
bool index_only_scan,
Snapshot snapshot, Snapshot snapshot,
IndexScanInstrumentation *instrument, IndexScanInstrumentation *instrument,
int nkeys, int norderbys, int nkeys, int norderbys,
uint32 flags) uint32 flags)
{ {
IndexScanDesc scan;
Assert(snapshot != InvalidSnapshot); Assert(snapshot != InvalidSnapshot);
pg_assume(heapRelation != NULL);
/* Check that a historic snapshot is not used for non-catalog tables */ /* Check that a historic snapshot is not used for non-catalog tables */
if (IsHistoricMVCCSnapshot(snapshot) && if (IsHistoricMVCCSnapshot(snapshot) &&
@@ -275,20 +279,10 @@ index_beginscan(Relation heapRelation,
RelationGetRelationName(heapRelation)))); RelationGetRelationName(heapRelation))));
} }
scan = index_beginscan_internal(indexRelation, nkeys, norderbys, snapshot, NULL, false); return index_beginscan_internal(indexRelation, heapRelation,
nkeys, norderbys,
/* snapshot, NULL, instrument,
* Save additional parameters into the scandesc. Everything else was set index_only_scan, false, flags);
* up by RelationGetIndexScan.
*/
scan->heapRelation = heapRelation;
scan->xs_snapshot = snapshot;
scan->instrument = instrument;
/* prepare to fetch index matches from table */
scan->xs_heapfetch = table_index_fetch_begin(heapRelation, flags);
return scan;
} }
/* /*
@@ -303,29 +297,24 @@ index_beginscan_bitmap(Relation indexRelation,
IndexScanInstrumentation *instrument, IndexScanInstrumentation *instrument,
int nkeys) int nkeys)
{ {
IndexScanDesc scan;
Assert(snapshot != InvalidSnapshot); Assert(snapshot != InvalidSnapshot);
Assert(IsMVCCLikeSnapshot(snapshot));
scan = index_beginscan_internal(indexRelation, nkeys, 0, snapshot, NULL, false); return index_beginscan_internal(indexRelation, NULL, nkeys, 0, snapshot,
NULL, instrument, false, false, SO_NONE);
/*
* Save additional parameters into the scandesc. Everything else was set
* up by RelationGetIndexScan.
*/
scan->xs_snapshot = snapshot;
scan->instrument = instrument;
return scan;
} }
/* /*
* index_beginscan_internal --- common code for index_beginscan variants * index_beginscan_internal --- common code for index_beginscan variants
*
* When heapRelation is not NULL, also initializes table AM index scan state.
*/ */
static IndexScanDesc static pg_always_inline IndexScanDesc
index_beginscan_internal(Relation indexRelation, index_beginscan_internal(Relation indexRelation, Relation heapRelation,
int nkeys, int norderbys, Snapshot snapshot, int nkeys, int norderbys, Snapshot snapshot,
ParallelIndexScanDesc pscan, bool temp_snap) ParallelIndexScanDesc pscan,
IndexScanInstrumentation *instrument,
bool index_only_scan, bool temp_snap, uint32 flags)
{ {
IndexScanDesc scan; IndexScanDesc scan;
@@ -349,6 +338,63 @@ index_beginscan_internal(Relation indexRelation,
scan->parallel_scan = pscan; scan->parallel_scan = pscan;
scan->xs_temp_snap = temp_snap; scan->xs_temp_snap = temp_snap;
scan->xs_snapshot = snapshot;
scan->instrument = instrument;
/*
* Initialize heap-side scan state when a heap relation is provided.
* Bitmap index scans don't provide one.
*/
if (heapRelation != NULL)
{
scan->heapRelation = heapRelation;
scan->xs_want_itup = index_only_scan;
scan->xs_heap_continue = false;
/*
* The "name" type's btree opclass stores index keys as cstrings
* rather than names to save space, so keys returned by an index-only
* scan must be re-padded to NAMEDATALEN allocations. Set up the
* state tableam_index_fill_ios_slot uses to do that. We detect such
* columns generically (stored type CSTRINGOID, opclass input type
* NAMEOID) in case other opclasses adopt the same optimization.
*/
if (index_only_scan)
{
int indnkeyatts = indexRelation->rd_index->indnkeyatts;
int namecount = 0;
for (int attnum = 0; attnum < indnkeyatts; attnum++)
{
if (TupleDescAttr(indexRelation->rd_att, attnum)->atttypid == CSTRINGOID &&
indexRelation->rd_opcintype[attnum] == NAMEOID)
namecount++;
}
if (unlikely(namecount > 0))
{
int idx = 0;
scan->xs_name_cstring_attnums = palloc_array(AttrNumber, namecount);
for (int attnum = 0; attnum < indnkeyatts; attnum++)
{
if (TupleDescAttr(indexRelation->rd_att, attnum)->atttypid == CSTRINGOID &&
indexRelation->rd_opcintype[attnum] == NAMEOID)
scan->xs_name_cstring_attnums[idx++] = (AttrNumber) attnum;
}
scan->xs_name_cstring_buf = palloc(namecount * NAMEDATALEN);
scan->xs_name_cstring_count = namecount;
}
}
/* set up table AM state for the index scan (sets xs_table_opaque) */
table_index_scan_begin(scan, flags);
/* table AM must set these for us */
Assert(scan->xs_getnext_slot != NULL && scan->xs_table_opaque != NULL);
}
return scan; return scan;
} }
@@ -376,8 +422,8 @@ index_rescan(IndexScanDesc scan,
Assert(norderbys == scan->numberOfOrderBys); Assert(norderbys == scan->numberOfOrderBys);
/* reset table AM state for rescan */ /* reset table AM state for rescan */
if (scan->xs_heapfetch) if (scan->xs_table_opaque)
table_index_fetch_reset(scan->xs_heapfetch); table_index_scan_reset(scan);
scan->kill_prior_tuple = false; /* for safety */ scan->kill_prior_tuple = false; /* for safety */
scan->xs_heap_continue = false; scan->xs_heap_continue = false;
@@ -397,11 +443,8 @@ index_endscan(IndexScanDesc scan)
CHECK_SCAN_PROCEDURE(amendscan); CHECK_SCAN_PROCEDURE(amendscan);
/* Release resources (like buffer pins) from table accesses */ /* Release resources (like buffer pins) from table accesses */
if (scan->xs_heapfetch) if (scan->xs_table_opaque)
{ table_index_scan_end(scan);
table_index_fetch_end(scan->xs_heapfetch);
scan->xs_heapfetch = NULL;
}
/* End the AM's scan */ /* End the AM's scan */
scan->indexRelation->rd_indam->amendscan(scan); scan->indexRelation->rd_indam->amendscan(scan);
@@ -453,8 +496,8 @@ index_restrpos(IndexScanDesc scan)
CHECK_SCAN_PROCEDURE(amrestrpos); CHECK_SCAN_PROCEDURE(amrestrpos);
/* reset table AM state for restoring the marked position */ /* reset table AM state for restoring the marked position */
if (scan->xs_heapfetch) if (scan->xs_table_opaque)
table_index_fetch_reset(scan->xs_heapfetch); table_index_scan_reset(scan);
scan->kill_prior_tuple = false; /* for safety */ scan->kill_prior_tuple = false; /* for safety */
scan->xs_heap_continue = false; scan->xs_heap_continue = false;
@@ -540,8 +583,8 @@ index_parallelrescan(IndexScanDesc scan)
SCAN_CHECKS; SCAN_CHECKS;
/* reset table AM state for rescan */ /* reset table AM state for rescan */
if (scan->xs_heapfetch) if (scan->xs_table_opaque)
table_index_fetch_reset(scan->xs_heapfetch); table_index_scan_reset(scan);
/* amparallelrescan is optional; assume no-op if not provided by AM */ /* amparallelrescan is optional; assume no-op if not provided by AM */
if (scan->indexRelation->rd_indam->amparallelrescan != NULL) if (scan->indexRelation->rd_indam->amparallelrescan != NULL)
@@ -558,172 +601,24 @@ index_parallelrescan(IndexScanDesc scan)
*/ */
IndexScanDesc IndexScanDesc
index_beginscan_parallel(Relation heaprel, Relation indexrel, index_beginscan_parallel(Relation heaprel, Relation indexrel,
bool index_only_scan,
IndexScanInstrumentation *instrument, IndexScanInstrumentation *instrument,
int nkeys, int norderbys, int nkeys, int norderbys,
ParallelIndexScanDesc pscan, ParallelIndexScanDesc pscan,
uint32 flags) uint32 flags)
{ {
Snapshot snapshot; Snapshot snapshot;
IndexScanDesc scan;
Assert(RelFileLocatorEquals(heaprel->rd_locator, pscan->ps_locator)); Assert(RelFileLocatorEquals(heaprel->rd_locator, pscan->ps_locator));
Assert(RelFileLocatorEquals(indexrel->rd_locator, pscan->ps_indexlocator)); Assert(RelFileLocatorEquals(indexrel->rd_locator, pscan->ps_indexlocator));
pg_assume(heaprel != NULL);
snapshot = RestoreSnapshot(pscan->ps_snapshot_data); snapshot = RestoreSnapshot(pscan->ps_snapshot_data);
RegisterSnapshot(snapshot); RegisterSnapshot(snapshot);
scan = index_beginscan_internal(indexrel, nkeys, norderbys, snapshot,
pscan, true);
/* return index_beginscan_internal(indexrel, heaprel, nkeys, norderbys,
* Save additional parameters into the scandesc. Everything else was set snapshot, pscan, instrument,
* up by index_beginscan_internal. index_only_scan, true, flags);
*/
scan->heapRelation = heaprel;
scan->xs_snapshot = snapshot;
scan->instrument = instrument;
/* prepare to fetch index matches from table */
scan->xs_heapfetch = table_index_fetch_begin(heaprel, flags);
return scan;
}
/* ----------------
* index_getnext_tid - get the next TID from a scan
*
* The result is the next TID satisfying the scan keys,
* or NULL if no more matching tuples exist.
* ----------------
*/
ItemPointer
index_getnext_tid(IndexScanDesc scan, ScanDirection direction)
{
bool found;
SCAN_CHECKS;
CHECK_SCAN_PROCEDURE(amgettuple);
/* XXX: we should assert that a snapshot is pushed or registered */
Assert(TransactionIdIsValid(RecentXmin));
/*
* The AM's amgettuple proc finds the next index entry matching the scan
* keys, and puts the TID into scan->xs_heaptid. It should also set
* scan->xs_recheck and possibly scan->xs_itup/scan->xs_hitup, though we
* pay no attention to those fields here.
*/
found = scan->indexRelation->rd_indam->amgettuple(scan, direction);
/* Reset kill flag immediately for safety */
scan->kill_prior_tuple = false;
scan->xs_heap_continue = false;
/* If we're out of index entries, we're done */
if (!found)
{
/* reset table AM state */
if (scan->xs_heapfetch)
table_index_fetch_reset(scan->xs_heapfetch);
return NULL;
}
Assert(ItemPointerIsValid(&scan->xs_heaptid));
pgstat_count_index_tuples(scan->indexRelation, 1);
/* Return the TID of the tuple we found. */
return &scan->xs_heaptid;
}
/* ----------------
* index_fetch_heap - get the scan's next heap tuple
*
* The result is a visible heap tuple associated with the index TID most
* recently fetched by index_getnext_tid, or NULL if no more matching tuples
* exist. (There can be more than one matching tuple because of HOT chains,
* although when using an MVCC snapshot it should be impossible for more than
* one such tuple to exist.)
*
* On success, the buffer containing the heap tup is pinned (the pin will be
* dropped in a future index_getnext_tid, index_fetch_heap or index_endscan
* call).
*
* Note: caller must check scan->xs_recheck, and perform rechecking of the
* scan keys if required. We do not do that here because we don't have
* enough information to do it efficiently in the general case.
* ----------------
*/
bool
index_fetch_heap(IndexScanDesc scan, TupleTableSlot *slot)
{
bool all_dead = false;
bool found;
found = table_index_fetch_tuple(scan->xs_heapfetch, &scan->xs_heaptid,
scan->xs_snapshot, slot,
&scan->xs_heap_continue, &all_dead);
if (found)
pgstat_count_heap_fetch(scan->indexRelation);
/*
* If we scanned a whole HOT chain and found only dead tuples, tell index
* AM to kill its entry for that TID (this will take effect in the next
* amgettuple call, in index_getnext_tid). We do not do this when in
* recovery because it may violate MVCC to do so. See comments in
* RelationGetIndexScan().
*/
if (!scan->xactStartedInRecovery)
scan->kill_prior_tuple = all_dead;
return found;
}
/* ----------------
* index_getnext_slot - get the next tuple from a scan
*
* The result is true if a tuple satisfying the scan keys and the snapshot was
* found, false otherwise. The tuple is stored in the specified slot.
*
* On success, resources (like buffer pins) are likely to be held, and will be
* dropped by a future index_getnext_tid, index_fetch_heap or index_endscan
* call).
*
* Note: caller must check scan->xs_recheck, and perform rechecking of the
* scan keys if required. We do not do that here because we don't have
* enough information to do it efficiently in the general case.
* ----------------
*/
bool
index_getnext_slot(IndexScanDesc scan, ScanDirection direction, TupleTableSlot *slot)
{
for (;;)
{
if (!scan->xs_heap_continue)
{
ItemPointer tid;
/* Time to fetch the next TID from the index */
tid = index_getnext_tid(scan, direction);
/* If we're out of index entries, we're done */
if (tid == NULL)
break;
Assert(ItemPointerEquals(tid, &scan->xs_heaptid));
}
/*
* Fetch the next (or only) visible heap tuple for this index entry.
* If we don't find anything, loop around and grab the next TID from
* the index.
*/
Assert(ItemPointerIsValid(&scan->xs_heaptid));
if (index_fetch_heap(scan, slot))
return true;
}
return false;
} }
/* ---------------- /* ----------------

View File

@@ -560,9 +560,8 @@ _bt_check_unique(Relation rel, BTInsertState insertstate, Relation heapRel,
* with optimizations like heap's HOT, we have just a single * with optimizations like heap's HOT, we have just a single
* index entry for the entire chain. * index entry for the entire chain.
*/ */
else if (table_index_fetch_tuple_check(heapRel, &htid, else if (table_fetch_tid(heapRel, &htid, &SnapshotDirty,
&SnapshotDirty, &all_dead))
&all_dead))
{ {
TransactionId xwait; TransactionId xwait;
@@ -618,8 +617,7 @@ _bt_check_unique(Relation rel, BTInsertState insertstate, Relation heapRel,
* entry. * entry.
*/ */
htid = itup->t_tid; htid = itup->t_tid;
if (table_index_fetch_tuple_check(heapRel, &htid, if (table_fetch_tid(heapRel, &htid, SnapshotSelf, NULL))
SnapshotSelf, NULL))
{ {
/* Normal case --- it's still live */ /* Normal case --- it's still live */
} }

View File

@@ -15,6 +15,7 @@ include $(top_builddir)/src/Makefile.global
OBJS = \ OBJS = \
table.o \ table.o \
tableam.o \ tableam.o \
tableam_indexscan.o \
tableamapi.o \ tableamapi.o \
toast_helper.o toast_helper.o

View File

@@ -3,6 +3,7 @@
backend_sources += files( backend_sources += files(
'table.c', 'table.c',
'tableam.c', 'tableam.c',
'tableam_indexscan.c',
'tableamapi.c', 'tableamapi.c',
'toast_helper.c', 'toast_helper.c',
) )

View File

@@ -222,44 +222,6 @@ table_beginscan_parallel_tidrange(Relation relation,
} }
/* ----------------------------------------------------------------------------
* Index scan related functions.
* ----------------------------------------------------------------------------
*/
/*
* To perform that check simply start an index scan, create the necessary
* slot, do the heap lookup, and shut everything down again. This could be
* optimized, but is unlikely to matter from a performance POV. If there
* frequently are live index pointers also matching a unique index key, the
* CPU overhead of this routine is unlikely to matter.
*
* Note that *tid may be modified when we return true if the AM supports
* storing multiple row versions reachable via a single index entry (like
* heap's HOT).
*/
bool
table_index_fetch_tuple_check(Relation rel,
ItemPointer tid,
Snapshot snapshot,
bool *all_dead)
{
IndexFetchTableData *scan;
TupleTableSlot *slot;
bool call_again = false;
bool found;
slot = table_slot_create(rel, NULL);
scan = table_index_fetch_begin(rel, SO_NONE);
found = table_index_fetch_tuple(scan, tid, snapshot, slot, &call_again,
all_dead);
table_index_fetch_end(scan);
ExecDropSingleTupleTableSlot(slot);
return found;
}
/* ------------------------------------------------------------------------ /* ------------------------------------------------------------------------
* Functions for non-modifying operations on individual tuples * Functions for non-modifying operations on individual tuples
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------

View File

@@ -0,0 +1,49 @@
/*-------------------------------------------------------------------------
*
* tableam_indexscan.c
* Helpers for table AM index scan callbacks.
*
*
* Table AMs can use these functions to implement xs_getnext_slot callbacks.
* See access/tableam.h.
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/access/table/tableam_indexscan.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/tableam_indexscan.h"
#include "utils/builtins.h"
/*
* Copy all name columns stored as cstrings back into NAMEDATALEN bytes of
* xs_name_cstring_buf.
*
* Called by tableam_index_fill_ios_slot (kept out of line, since it's needed
* only by index-only scans of indexes on "name" columns).
*/
pg_attribute_cold void
tableam_index_fill_ios_names(IndexScanDesc scan, TupleTableSlot *slot)
{
for (int idx = 0; idx < scan->xs_name_cstring_count; idx++)
{
int attnum = scan->xs_name_cstring_attnums[idx];
Name name;
/* skip null Datums */
if (slot->tts_isnull[attnum])
continue;
/* use namestrcpy to zero-pad all trailing bytes */
name = (Name) (scan->xs_name_cstring_buf + idx * NAMEDATALEN);
namestrcpy(name, DatumGetCString(slot->tts_values[attnum]));
slot->tts_values[attnum] = NameGetDatum(name);
}
}

View File

@@ -50,11 +50,11 @@ GetTableAmRoutine(Oid amhandler)
Assert(routine->parallelscan_initialize != NULL); Assert(routine->parallelscan_initialize != NULL);
Assert(routine->parallelscan_reinitialize != NULL); Assert(routine->parallelscan_reinitialize != NULL);
Assert(routine->index_fetch_begin != NULL); Assert(routine->index_scan_begin != NULL);
Assert(routine->index_fetch_reset != NULL); Assert(routine->index_scan_reset != NULL);
Assert(routine->index_fetch_end != NULL); Assert(routine->index_scan_end != NULL);
Assert(routine->index_fetch_tuple != NULL);
Assert(routine->fetch_tid != NULL);
Assert(routine->tuple_fetch_row_version != NULL); Assert(routine->tuple_fetch_row_version != NULL);
Assert(routine->tuple_tid_valid != NULL); Assert(routine->tuple_tid_valid != NULL);
Assert(routine->tuple_get_latest_tid != NULL); Assert(routine->tuple_get_latest_tid != NULL);

View File

@@ -72,19 +72,18 @@ unique_key_recheck(PG_FUNCTION_ARGS)
* Get the new data that was inserted/updated. * Get the new data that was inserted/updated.
*/ */
if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
checktid = trigdata->tg_trigslot->tts_tid; slot = trigdata->tg_trigslot;
else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
checktid = trigdata->tg_newslot->tts_tid; slot = trigdata->tg_newslot;
else else
{ {
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
errmsg("function \"%s\" must be fired for INSERT or UPDATE", errmsg("function \"%s\" must be fired for INSERT or UPDATE",
funcname))); funcname)));
ItemPointerSetInvalid(&checktid); /* keep compiler quiet */ slot = NULL; /* keep compiler quiet */
} }
checktid = slot->tts_tid;
slot = table_slot_create(trigdata->tg_relation, NULL);
/* /*
* If the row pointed at by checktid is now dead (ie, inserted and then * If the row pointed at by checktid is now dead (ie, inserted and then
@@ -105,23 +104,12 @@ unique_key_recheck(PG_FUNCTION_ARGS)
* removed. * removed.
*/ */
tmptid = checktid; tmptid = checktid;
if (!table_fetch_tid(trigdata->tg_relation, &tmptid, SnapshotSelf, NULL))
{ {
IndexFetchTableData *scan = table_index_fetch_begin(trigdata->tg_relation, /*
SO_NONE); * All rows referenced by the index entry are dead, so skip the check
bool call_again = false; */
return PointerGetDatum(NULL);
if (!table_index_fetch_tuple(scan, &tmptid, SnapshotSelf, slot,
&call_again, NULL))
{
/*
* All rows referenced by the index entry are dead, so skip the
* check.
*/
ExecDropSingleTupleTableSlot(slot);
table_index_fetch_end(scan);
return PointerGetDatum(NULL);
}
table_index_fetch_end(scan);
} }
/* /*
@@ -168,9 +156,8 @@ unique_key_recheck(PG_FUNCTION_ARGS)
/* /*
* Note: this is not a real insert; it is a check that the index entry * Note: this is not a real insert; it is a check that the index entry
* that has already been inserted is unique. Passing the tuple's tid * that has already been inserted is unique. Passing the tuple's tid
* (i.e. unmodified by table_index_fetch_tuple()) is correct even if * (i.e. unmodified by table_fetch_tid()) is correct even if the row
* the row is now dead, because that is the TID the index will know * is now dead, because that is the TID the index will know about.
* about.
*/ */
index_insert(indexRel, values, isnull, &checktid, index_insert(indexRel, values, isnull, &checktid,
trigdata->tg_relation, UNIQUE_CHECK_EXISTING, trigdata->tg_relation, UNIQUE_CHECK_EXISTING,
@@ -199,8 +186,6 @@ unique_key_recheck(PG_FUNCTION_ARGS)
if (estate != NULL) if (estate != NULL)
FreeExecutorState(estate); FreeExecutorState(estate);
ExecDropSingleTupleTableSlot(slot);
index_close(indexRel, RowExclusiveLock); index_close(indexRel, RowExclusiveLock);
return PointerGetDatum(NULL); return PointerGetDatum(NULL);

View File

@@ -137,7 +137,7 @@ static void show_recursive_union_info(RecursiveUnionState *rstate,
static void show_memoize_info(MemoizeState *mstate, List *ancestors, static void show_memoize_info(MemoizeState *mstate, List *ancestors,
ExplainState *es); ExplainState *es);
static void show_hashagg_info(AggState *aggstate, ExplainState *es); static void show_hashagg_info(AggState *aggstate, ExplainState *es);
static void show_indexsearches_info(PlanState *planstate, ExplainState *es); static void show_indexscan_info(PlanState *planstate, ExplainState *es);
static void show_tidbitmap_info(BitmapHeapScanState *planstate, static void show_tidbitmap_info(BitmapHeapScanState *planstate,
ExplainState *es); ExplainState *es);
static void show_scan_io_usage(ScanState *planstate, static void show_scan_io_usage(ScanState *planstate,
@@ -1978,7 +1978,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
if (plan->qual) if (plan->qual)
show_instrumentation_count("Rows Removed by Filter", 1, show_instrumentation_count("Rows Removed by Filter", 1,
planstate, es); planstate, es);
show_indexsearches_info(planstate, es); show_indexscan_info(planstate, es);
break; break;
case T_IndexOnlyScan: case T_IndexOnlyScan:
show_scan_qual(((IndexOnlyScan *) plan)->indexqual, show_scan_qual(((IndexOnlyScan *) plan)->indexqual,
@@ -1992,15 +1992,12 @@ ExplainNode(PlanState *planstate, List *ancestors,
if (plan->qual) if (plan->qual)
show_instrumentation_count("Rows Removed by Filter", 1, show_instrumentation_count("Rows Removed by Filter", 1,
planstate, es); planstate, es);
if (es->analyze) show_indexscan_info(planstate, es);
ExplainPropertyFloat("Heap Fetches", NULL,
planstate->instrument->ntuples2, 0, es);
show_indexsearches_info(planstate, es);
break; break;
case T_BitmapIndexScan: case T_BitmapIndexScan:
show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig, show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig,
"Index Cond", planstate, ancestors, es); "Index Cond", planstate, ancestors, es);
show_indexsearches_info(planstate, es); show_indexscan_info(planstate, es);
break; break;
case T_BitmapHeapScan: case T_BitmapHeapScan:
show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig, show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig,
@@ -3872,15 +3869,16 @@ show_hashagg_info(AggState *aggstate, ExplainState *es)
} }
/* /*
* Show the total number of index searches for a * Show index scan related executor instrumentation for a
* IndexScan/IndexOnlyScan/BitmapIndexScan node * IndexScan/IndexOnlyScan/BitmapIndexScan node
*/ */
static void static void
show_indexsearches_info(PlanState *planstate, ExplainState *es) show_indexscan_info(PlanState *planstate, ExplainState *es)
{ {
Plan *plan = planstate->plan; Plan *plan = planstate->plan;
SharedIndexScanInstrumentation *SharedInfo = NULL; SharedIndexScanInstrumentation *SharedInfo = NULL;
uint64 nsearches = 0; uint64 nsearches = 0,
ntabletuplefetches = 0;
if (!es->analyze) if (!es->analyze)
return; return;
@@ -3901,6 +3899,7 @@ show_indexsearches_info(PlanState *planstate, ExplainState *es)
IndexOnlyScanState *indexstate = ((IndexOnlyScanState *) planstate); IndexOnlyScanState *indexstate = ((IndexOnlyScanState *) planstate);
nsearches = indexstate->ioss_Instrument->nsearches; nsearches = indexstate->ioss_Instrument->nsearches;
ntabletuplefetches = indexstate->ioss_Instrument->ntabletuplefetches;
SharedInfo = indexstate->ioss_SharedInfo; SharedInfo = indexstate->ioss_SharedInfo;
break; break;
} }
@@ -3924,9 +3923,13 @@ show_indexsearches_info(PlanState *planstate, ExplainState *es)
IndexScanInstrumentation *winstrument = &SharedInfo->winstrument[i]; IndexScanInstrumentation *winstrument = &SharedInfo->winstrument[i];
nsearches += winstrument->nsearches; nsearches += winstrument->nsearches;
ntabletuplefetches += winstrument->ntabletuplefetches;
} }
} }
if (nodeTag(plan) == T_IndexOnlyScan)
ExplainPropertyUInteger("Heap Fetches", NULL, ntabletuplefetches, es);
ExplainPropertyUInteger("Index Searches", NULL, nsearches, es); ExplainPropertyUInteger("Index Searches", NULL, nsearches, es);
} }

View File

@@ -3031,10 +3031,10 @@ find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
} }
/* XXX no instrumentation for now */ /* XXX no instrumentation for now */
scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(), scan = index_beginscan(rel, chgcxt->cc_ident_index, false, GetActiveSnapshot(),
NULL, chgcxt->cc_ident_key_nentries, 0, 0); NULL, chgcxt->cc_ident_key_nentries, 0, 0);
index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0); index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
while (index_getnext_slot(scan, ForwardScanDirection, retrieved)) while (table_index_getnext_slot(scan, ForwardScanDirection, retrieved))
{ {
/* Be wary of temporal constraints */ /* Be wary of temporal constraints */
if (scan->xs_recheck && !identity_key_equal(chgcxt, locator, retrieved)) if (scan->xs_recheck && !identity_key_equal(chgcxt, locator, retrieved))

View File

@@ -823,12 +823,13 @@ check_exclusion_or_unique_constraint(Relation heap, Relation index,
retry: retry:
conflict = false; conflict = false;
found_self = false; found_self = false;
index_scan = index_beginscan(heap, index, index_scan = index_beginscan(heap, index, false,
&DirtySnapshot, NULL, indnkeyatts, 0, &DirtySnapshot, NULL, indnkeyatts, 0,
SO_NONE); SO_NONE);
index_rescan(index_scan, scankeys, indnkeyatts, NULL, 0); index_rescan(index_scan, scankeys, indnkeyatts, NULL, 0);
while (index_getnext_slot(index_scan, ForwardScanDirection, existing_slot)) while (table_index_getnext_slot(index_scan, ForwardScanDirection,
existing_slot))
{ {
TransactionId xwait; TransactionId xwait;
XLTW_Oper reason_wait; XLTW_Oper reason_wait;

View File

@@ -205,7 +205,7 @@ RelationFindReplTupleByIndex(Relation rel, Oid idxoid,
skey_attoff = build_replindex_scan_key(skey, rel, idxrel, searchslot); skey_attoff = build_replindex_scan_key(skey, rel, idxrel, searchslot);
/* Start an index scan. */ /* Start an index scan. */
scan = index_beginscan(rel, idxrel, scan = index_beginscan(rel, idxrel, false,
&snap, NULL, skey_attoff, 0, SO_NONE); &snap, NULL, skey_attoff, 0, SO_NONE);
retry: retry:
@@ -214,7 +214,7 @@ retry:
index_rescan(scan, skey, skey_attoff, NULL, 0); index_rescan(scan, skey, skey_attoff, NULL, 0);
/* Try to find the tuple */ /* Try to find the tuple */
while (index_getnext_slot(scan, ForwardScanDirection, outslot)) while (table_index_getnext_slot(scan, ForwardScanDirection, outslot))
{ {
/* /*
* Avoid expensive equality check if the index is primary key or * Avoid expensive equality check if the index is primary key or
@@ -669,13 +669,13 @@ RelationFindDeletedTupleInfoByIndex(Relation rel, Oid idxoid,
* not yet committed or those just committed prior to the scan are * not yet committed or those just committed prior to the scan are
* excluded in update_most_recent_deletion_info(). * excluded in update_most_recent_deletion_info().
*/ */
scan = index_beginscan(rel, idxrel, scan = index_beginscan(rel, idxrel, false,
SnapshotAny, NULL, skey_attoff, 0, SO_NONE); SnapshotAny, NULL, skey_attoff, 0, SO_NONE);
index_rescan(scan, skey, skey_attoff, NULL, 0); index_rescan(scan, skey, skey_attoff, NULL, 0);
/* Try to find the tuple */ /* Try to find the tuple */
while (index_getnext_slot(scan, ForwardScanDirection, scanslot)) while (table_index_getnext_slot(scan, ForwardScanDirection, scanslot))
{ {
/* /*
* Avoid expensive equality check if the index is primary key or * Avoid expensive equality check if the index is primary key or

View File

@@ -204,6 +204,7 @@ ExecEndBitmapIndexScan(BitmapIndexScanState *node)
* which will have a new BitmapIndexScanState and zeroed stats. * which will have a new BitmapIndexScanState and zeroed stats.
*/ */
winstrument->nsearches += node->biss_Instrument->nsearches; winstrument->nsearches += node->biss_Instrument->nsearches;
Assert(node->biss_Instrument->ntabletuplefetches == 0);
} }
/* /*

View File

@@ -31,26 +31,17 @@
#include "postgres.h" #include "postgres.h"
#include "access/genam.h" #include "access/genam.h"
#include "access/htup_details.h"
#include "access/relscan.h" #include "access/relscan.h"
#include "access/tableam.h" #include "access/tableam.h"
#include "access/tupdesc.h"
#include "access/visibilitymap.h"
#include "catalog/pg_type.h"
#include "executor/executor.h" #include "executor/executor.h"
#include "executor/instrument.h" #include "executor/instrument.h"
#include "executor/nodeIndexonlyscan.h" #include "executor/nodeIndexonlyscan.h"
#include "executor/nodeIndexscan.h" #include "executor/nodeIndexscan.h"
#include "miscadmin.h" #include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/predicate.h"
#include "utils/builtins.h"
#include "utils/rel.h" #include "utils/rel.h"
static TupleTableSlot *IndexOnlyNext(IndexOnlyScanState *node); static TupleTableSlot *IndexOnlyNext(IndexOnlyScanState *node);
static void StoreIndexTuple(IndexOnlyScanState *node, TupleTableSlot *slot,
IndexScanDesc scandesc);
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
@@ -67,7 +58,6 @@ IndexOnlyNext(IndexOnlyScanState *node)
ScanDirection direction; ScanDirection direction;
IndexScanDesc scandesc; IndexScanDesc scandesc;
TupleTableSlot *slot; TupleTableSlot *slot;
ItemPointer tid;
/* /*
* extract necessary information from index scan node * extract necessary information from index scan node
@@ -93,6 +83,7 @@ IndexOnlyNext(IndexOnlyScanState *node)
*/ */
scandesc = index_beginscan(node->ss.ss_currentRelation, scandesc = index_beginscan(node->ss.ss_currentRelation,
node->ioss_RelationDesc, node->ioss_RelationDesc,
true,
estate->es_snapshot, estate->es_snapshot,
node->ioss_Instrument, node->ioss_Instrument,
node->ioss_NumScanKeys, node->ioss_NumScanKeys,
@@ -101,11 +92,7 @@ IndexOnlyNext(IndexOnlyScanState *node)
SO_HINT_REL_READ_ONLY : SO_NONE); SO_HINT_REL_READ_ONLY : SO_NONE);
node->ioss_ScanDesc = scandesc; node->ioss_ScanDesc = scandesc;
Assert(node->ioss_ScanDesc->xs_want_itup);
/* Set it up for index-only scan */
node->ioss_ScanDesc->xs_want_itup = true;
node->ioss_VMBuffer = InvalidBuffer;
/* /*
* If no run-time keys to calculate or they are ready, go ahead and * If no run-time keys to calculate or they are ready, go ahead and
@@ -122,60 +109,10 @@ IndexOnlyNext(IndexOnlyScanState *node)
/* /*
* OK, now that we have what we need, fetch the next tuple. * OK, now that we have what we need, fetch the next tuple.
*/ */
while ((tid = index_getnext_tid(scandesc, direction)) != NULL) while (table_index_getnext_slot(scandesc, direction, slot))
{ {
bool tuple_from_heap = false;
CHECK_FOR_INTERRUPTS(); CHECK_FOR_INTERRUPTS();
/*
* We can skip the heap fetch if the TID references a heap page on
* which all tuples are known visible to everybody. In any case,
* we'll use the index tuple not the heap tuple as the data source.
*
* Note on Memory Ordering Effects: visibilitymap_get_status does not
* lock the visibility map buffer, and therefore the result we read
* here could be slightly stale. However, it can't be stale enough to
* matter; see comments above visibilitymap_get_status for the full
* argument. It's worth going through this complexity to avoid
* needing to lock the VM buffer, which could cause significant
* contention.
*/
if (!VM_ALL_VISIBLE(scandesc->heapRelation,
ItemPointerGetBlockNumber(tid),
&node->ioss_VMBuffer))
{
/*
* Rats, we have to visit the heap to check visibility.
*/
InstrCountTuples2(node, 1);
if (!index_fetch_heap(scandesc, node->ioss_TableSlot))
continue; /* no visible tuple, try next index entry */
ExecClearTuple(node->ioss_TableSlot);
/*
* Only MVCC snapshots are supported here, so there should be no
* need to keep following the HOT chain once a visible entry has
* been found. If we did want to allow that, we'd need to keep
* more state to remember not to call index_getnext_tid next time.
*/
if (scandesc->xs_heap_continue)
elog(ERROR, "non-MVCC snapshots are not supported in index-only scans");
/*
* Note: at this point we are holding a pin on the heap page, as
* recorded in scandesc->xs_cbuf. We could release that pin now,
* but it's not clear whether it's a win to do so. The next index
* entry might require a visit to the same heap page.
*/
tuple_from_heap = true;
}
/* Fill the scan tuple slot with data from the index */
StoreIndexTuple(node, slot, scandesc);
/* /*
* If the index was lossy, we have to recheck the index quals. * If the index was lossy, we have to recheck the index quals.
*/ */
@@ -202,16 +139,6 @@ IndexOnlyNext(IndexOnlyScanState *node)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("lossy distance functions are not supported in index-only scans"))); errmsg("lossy distance functions are not supported in index-only scans")));
/*
* If we didn't access the heap, then we'll need to take a predicate
* lock explicitly, as if we had. For now we do that at page level.
*/
if (!tuple_from_heap)
PredicateLockPage(scandesc->heapRelation,
ItemPointerGetBlockNumber(tid),
estate->es_snapshot);
return slot; return slot;
} }
@@ -222,85 +149,6 @@ IndexOnlyNext(IndexOnlyScanState *node)
return ExecClearTuple(slot); return ExecClearTuple(slot);
} }
/*
* StoreIndexTuple
* Fill the slot with the data the index AM returned.
*
* The data might be provided in either HeapTuple (xs_hitup) or IndexTuple
* (xs_itup) format. Conceivably an index AM might fill both fields, in which
* case we prefer the heap format, since it's probably a bit cheaper to fill a
* slot from.
*
* At some point this might be generally-useful functionality, but
* right now we don't need it elsewhere.
*/
static void
StoreIndexTuple(IndexOnlyScanState *node, TupleTableSlot *slot,
IndexScanDesc scandesc)
{
ExecClearTuple(slot);
/*
* We must deform the tuple using the tupdesc the index AM formed it with
* (xs_hitupdesc or xs_itupdesc), not the slot's tupdesc. The datums
* returned by the index AM must be binary compatible, but the descriptors
* may align each column differently in certain rare cases. (Actually,
* btree's "name" opclass stores cstring tuples that _aren't_ even binary
* compatible, in the strictest sense. We directly handle that here.)
*/
if (scandesc->xs_hitup)
{
Assert(slot->tts_tupleDescriptor->natts == scandesc->xs_hitupdesc->natts);
heap_deform_tuple(scandesc->xs_hitup, scandesc->xs_hitupdesc,
slot->tts_values, slot->tts_isnull);
}
else if (scandesc->xs_itup)
{
Assert(slot->tts_tupleDescriptor->natts == scandesc->xs_itupdesc->natts);
index_deform_tuple(scandesc->xs_itup, scandesc->xs_itupdesc,
slot->tts_values, slot->tts_isnull);
/*
* Copy all name columns stored as cstrings back into a NAMEDATALEN
* byte sized allocation. We mark this branch as unlikely as
* generally "name" is used only for the system catalogs and this
* would have to be a user query running on those or some other user
* table with an index on a name column.
*/
if (unlikely(node->ioss_NameCStringAttNums != NULL))
{
int attcount = node->ioss_NameCStringCount;
for (int idx = 0; idx < attcount; idx++)
{
int attnum = node->ioss_NameCStringAttNums[idx];
Name name;
/* skip null Datums */
if (slot->tts_isnull[attnum])
continue;
/*
* allocate the NAMEDATALEN and copy the datum into that
* memory
*/
name = (Name) MemoryContextAlloc(node->ss.ps.ps_ExprContext->ecxt_per_tuple_memory,
NAMEDATALEN);
/* use namestrcpy to zero-pad all trailing bytes */
namestrcpy(name, DatumGetCString(slot->tts_values[attnum]));
slot->tts_values[attnum] = NameGetDatum(name);
}
}
}
else
elog(ERROR, "no data returned for index-only scan");
ExecStoreVirtualTuple(slot);
}
/* /*
* IndexOnlyRecheck -- access method routine to recheck a tuple in EvalPlanQual * IndexOnlyRecheck -- access method routine to recheck a tuple in EvalPlanQual
* *
@@ -394,13 +242,6 @@ ExecEndIndexOnlyScan(IndexOnlyScanState *node)
indexRelationDesc = node->ioss_RelationDesc; indexRelationDesc = node->ioss_RelationDesc;
indexScanDesc = node->ioss_ScanDesc; indexScanDesc = node->ioss_ScanDesc;
/* Release VM buffer pin, if any. */
if (node->ioss_VMBuffer != InvalidBuffer)
{
ReleaseBuffer(node->ioss_VMBuffer);
node->ioss_VMBuffer = InvalidBuffer;
}
/* /*
* When ending a parallel worker, copy the statistics gathered by the * When ending a parallel worker, copy the statistics gathered by the
* worker back into shared memory so that it can be picked up by the main * worker back into shared memory so that it can be picked up by the main
@@ -420,6 +261,7 @@ ExecEndIndexOnlyScan(IndexOnlyScanState *node)
* which will have a new IndexOnlyScanState and zeroed stats. * which will have a new IndexOnlyScanState and zeroed stats.
*/ */
winstrument->nsearches += node->ioss_Instrument->nsearches; winstrument->nsearches += node->ioss_Instrument->nsearches;
winstrument->ntabletuplefetches += node->ioss_Instrument->ntabletuplefetches;
} }
/* /*
@@ -519,8 +361,6 @@ ExecInitIndexOnlyScan(IndexOnlyScan *node, EState *estate, int eflags)
Relation indexRelation; Relation indexRelation;
LOCKMODE lockmode; LOCKMODE lockmode;
TupleDesc tupDesc; TupleDesc tupDesc;
int indnkeyatts;
int namecount;
/* /*
* create state structure * create state structure
@@ -549,23 +389,15 @@ ExecInitIndexOnlyScan(IndexOnlyScan *node, EState *estate, int eflags)
* Build the scan tuple type using the indextlist generated by the * Build the scan tuple type using the indextlist generated by the
* planner. We use this, rather than the index's physical tuple * planner. We use this, rather than the index's physical tuple
* descriptor, because the latter contains storage column types not the * descriptor, because the latter contains storage column types not the
* types of the original datums. (It's the AM's responsibility to return * types of the original datums. (It's the index AM's responsibility to
* suitable data anyway.) * return suitable data anyway. The table AM fills our slot with that
* returned data.)
*/ */
tupDesc = ExecTypeFromTL(node->indextlist); tupDesc = ExecTypeFromTL(node->indextlist);
ExecInitScanTupleSlot(estate, &indexstate->ss, tupDesc, ExecInitScanTupleSlot(estate, &indexstate->ss, tupDesc,
&TTSOpsVirtual, &TTSOpsVirtual,
0); 0);
/*
* We need another slot, in a format that's suitable for the table AM, for
* when we need to fetch a tuple from the table for rechecking visibility.
*/
indexstate->ioss_TableSlot =
ExecAllocTableSlot(&estate->es_tupleTable,
RelationGetDescr(currentRelation),
table_slot_callbacks(currentRelation), 0);
/* /*
* Initialize result type and projection info. The node's targetlist will * Initialize result type and projection info. The node's targetlist will
* contain Vars with varno = INDEX_VAR, referencing the scan tuple. * contain Vars with varno = INDEX_VAR, referencing the scan tuple.
@@ -655,48 +487,6 @@ ExecInitIndexOnlyScan(IndexOnlyScan *node, EState *estate, int eflags)
indexstate->ioss_RuntimeContext = NULL; indexstate->ioss_RuntimeContext = NULL;
} }
indexstate->ioss_NameCStringAttNums = NULL;
indnkeyatts = indexRelation->rd_index->indnkeyatts;
namecount = 0;
/*
* The "name" type for btree uses text_ops which results in storing
* cstrings in the indexed keys rather than names. Here we detect that in
* a generic way in case other index AMs want to do the same optimization.
* Check for opclasses with an opcintype of NAMEOID and an index tuple
* descriptor with CSTRINGOID. If any of these are found, create an array
* marking the index attribute number of each of them. StoreIndexTuple()
* handles copying the name Datums into a NAMEDATALEN-byte allocation.
*/
/* First, count the number of such index keys */
for (int attnum = 0; attnum < indnkeyatts; attnum++)
{
if (TupleDescAttr(indexRelation->rd_att, attnum)->atttypid == CSTRINGOID &&
indexRelation->rd_opcintype[attnum] == NAMEOID)
namecount++;
}
if (namecount > 0)
{
int idx = 0;
/*
* Now create an array to mark the attribute numbers of the keys that
* need to be converted from cstring to name.
*/
indexstate->ioss_NameCStringAttNums = palloc_array(AttrNumber, namecount);
for (int attnum = 0; attnum < indnkeyatts; attnum++)
{
if (TupleDescAttr(indexRelation->rd_att, attnum)->atttypid == CSTRINGOID &&
indexRelation->rd_opcintype[attnum] == NAMEOID)
indexstate->ioss_NameCStringAttNums[idx++] = (AttrNumber) attnum;
}
}
indexstate->ioss_NameCStringCount = namecount;
/* /*
* all done. * all done.
*/ */
@@ -752,14 +542,14 @@ ExecIndexOnlyScanInitializeDSM(IndexOnlyScanState *node,
node->ioss_ScanDesc = node->ioss_ScanDesc =
index_beginscan_parallel(node->ss.ss_currentRelation, index_beginscan_parallel(node->ss.ss_currentRelation,
node->ioss_RelationDesc, node->ioss_RelationDesc,
true,
node->ioss_Instrument, node->ioss_Instrument,
node->ioss_NumScanKeys, node->ioss_NumScanKeys,
node->ioss_NumOrderByKeys, node->ioss_NumOrderByKeys,
piscan, piscan,
ScanRelIsReadOnly(&node->ss) ? ScanRelIsReadOnly(&node->ss) ?
SO_HINT_REL_READ_ONLY : SO_NONE); SO_HINT_REL_READ_ONLY : SO_NONE);
node->ioss_ScanDesc->xs_want_itup = true; Assert(node->ioss_ScanDesc->xs_want_itup);
node->ioss_VMBuffer = InvalidBuffer;
/* /*
* If no run-time keys to calculate or they are ready, go ahead and pass * If no run-time keys to calculate or they are ready, go ahead and pass
@@ -802,13 +592,14 @@ ExecIndexOnlyScanInitializeWorker(IndexOnlyScanState *node,
node->ioss_ScanDesc = node->ioss_ScanDesc =
index_beginscan_parallel(node->ss.ss_currentRelation, index_beginscan_parallel(node->ss.ss_currentRelation,
node->ioss_RelationDesc, node->ioss_RelationDesc,
true,
node->ioss_Instrument, node->ioss_Instrument,
node->ioss_NumScanKeys, node->ioss_NumScanKeys,
node->ioss_NumOrderByKeys, node->ioss_NumOrderByKeys,
piscan, piscan,
ScanRelIsReadOnly(&node->ss) ? ScanRelIsReadOnly(&node->ss) ?
SO_HINT_REL_READ_ONLY : SO_NONE); SO_HINT_REL_READ_ONLY : SO_NONE);
node->ioss_ScanDesc->xs_want_itup = true; Assert(node->ioss_ScanDesc->xs_want_itup);
/* /*
* If no run-time keys to calculate or they are ready, go ahead and pass * If no run-time keys to calculate or they are ready, go ahead and pass

View File

@@ -110,6 +110,7 @@ IndexNext(IndexScanState *node)
*/ */
scandesc = index_beginscan(node->ss.ss_currentRelation, scandesc = index_beginscan(node->ss.ss_currentRelation,
node->iss_RelationDesc, node->iss_RelationDesc,
false,
estate->es_snapshot, estate->es_snapshot,
node->iss_Instrument, node->iss_Instrument,
node->iss_NumScanKeys, node->iss_NumScanKeys,
@@ -132,7 +133,7 @@ IndexNext(IndexScanState *node)
/* /*
* ok, now that we have what we need, fetch the next tuple. * ok, now that we have what we need, fetch the next tuple.
*/ */
while (index_getnext_slot(scandesc, direction, slot)) while (table_index_getnext_slot(scandesc, direction, slot))
{ {
CHECK_FOR_INTERRUPTS(); CHECK_FOR_INTERRUPTS();
@@ -208,6 +209,7 @@ IndexNextWithReorder(IndexScanState *node)
*/ */
scandesc = index_beginscan(node->ss.ss_currentRelation, scandesc = index_beginscan(node->ss.ss_currentRelation,
node->iss_RelationDesc, node->iss_RelationDesc,
false,
estate->es_snapshot, estate->es_snapshot,
node->iss_Instrument, node->iss_Instrument,
node->iss_NumScanKeys, node->iss_NumScanKeys,
@@ -266,7 +268,7 @@ IndexNextWithReorder(IndexScanState *node)
* Fetch next tuple from the index. * Fetch next tuple from the index.
*/ */
next_indextuple: next_indextuple:
if (!index_getnext_slot(scandesc, ForwardScanDirection, slot)) if (!table_index_getnext_slot(scandesc, ForwardScanDirection, slot))
{ {
/* /*
* No more tuples from the index. But we still need to drain any * No more tuples from the index. But we still need to drain any
@@ -818,6 +820,7 @@ ExecEndIndexScan(IndexScanState *node)
* which will have a new IndexOnlyScanState and zeroed stats. * which will have a new IndexOnlyScanState and zeroed stats.
*/ */
winstrument->nsearches += node->iss_Instrument->nsearches; winstrument->nsearches += node->iss_Instrument->nsearches;
Assert(node->iss_Instrument->ntabletuplefetches == 0);
} }
/* /*
@@ -1696,6 +1699,7 @@ ExecIndexScanInitializeDSM(IndexScanState *node,
node->iss_ScanDesc = node->iss_ScanDesc =
index_beginscan_parallel(node->ss.ss_currentRelation, index_beginscan_parallel(node->ss.ss_currentRelation,
node->iss_RelationDesc, node->iss_RelationDesc,
false,
node->iss_Instrument, node->iss_Instrument,
node->iss_NumScanKeys, node->iss_NumScanKeys,
node->iss_NumOrderByKeys, node->iss_NumOrderByKeys,
@@ -1744,6 +1748,7 @@ ExecIndexScanInitializeWorker(IndexScanState *node,
node->iss_ScanDesc = node->iss_ScanDesc =
index_beginscan_parallel(node->ss.ss_currentRelation, index_beginscan_parallel(node->ss.ss_currentRelation,
node->iss_RelationDesc, node->iss_RelationDesc,
false,
node->iss_Instrument, node->iss_Instrument,
node->iss_NumScanKeys, node->iss_NumScanKeys,
node->iss_NumOrderByKeys, node->iss_NumOrderByKeys,

View File

@@ -2947,7 +2947,7 @@ ri_FastPathCheck(RI_ConstraintInfo *riinfo,
* btree this has no functional consequence, but it keeps the ordering * btree this has no functional consequence, but it keeps the ordering
* correct for out-of-tree access methods. * correct for out-of-tree access methods.
*/ */
scandesc = index_beginscan(pk_rel, idx_rel, scandesc = index_beginscan(pk_rel, idx_rel, false,
snapshot, NULL, snapshot, NULL,
riinfo->nkeys, 0, riinfo->nkeys, 0,
SO_NONE); SO_NONE);
@@ -3114,7 +3114,7 @@ ri_FastPathBatchFlush(RI_FastPathEntry *fpentry, Relation fk_rel,
* btree this has no functional consequence, but it keeps the ordering * btree this has no functional consequence, but it keeps the ordering
* correct for out-of-tree access methods. * correct for out-of-tree access methods.
*/ */
scandesc = index_beginscan(pk_rel, idx_rel, snapshot, NULL, scandesc = index_beginscan(pk_rel, idx_rel, false, snapshot, NULL,
riinfo->nkeys, 0, SO_NONE); riinfo->nkeys, 0, SO_NONE);
if (riinfo->fpmeta == NULL) if (riinfo->fpmeta == NULL)
@@ -3322,7 +3322,7 @@ ri_FastPathFlushArray(RI_FastPathEntry *fpentry, TupleTableSlot *fk_slot,
* Walk all matches. The index AM returns them in index order. For each * Walk all matches. The index AM returns them in index order. For each
* match, find which batch item(s) it satisfies. * match, find which batch item(s) it satisfies.
*/ */
while (index_getnext_slot(scandesc, ForwardScanDirection, pk_slot)) while (table_index_getnext_slot(scandesc, ForwardScanDirection, pk_slot))
{ {
Datum found_val; Datum found_val;
bool found_null; bool found_null;
@@ -3397,7 +3397,7 @@ ri_FastPathProbeOne(Relation pk_rel, Relation idx_rel,
index_rescan(scandesc, skey, nkeys, NULL, 0); index_rescan(scandesc, skey, nkeys, NULL, 0);
if (index_getnext_slot(scandesc, ForwardScanDirection, slot)) if (table_index_getnext_slot(scandesc, ForwardScanDirection, slot))
{ {
bool concurrently_updated; bool concurrently_updated;

View File

@@ -102,7 +102,6 @@
#include "access/gin.h" #include "access/gin.h"
#include "access/table.h" #include "access/table.h"
#include "access/tableam.h" #include "access/tableam.h"
#include "access/visibilitymap.h"
#include "catalog/pg_collation.h" #include "catalog/pg_collation.h"
#include "catalog/pg_operator.h" #include "catalog/pg_operator.h"
#include "catalog/pg_statistic.h" #include "catalog/pg_statistic.h"
@@ -122,7 +121,6 @@
#include "parser/parsetree.h" #include "parser/parsetree.h"
#include "rewrite/rewriteManip.h" #include "rewrite/rewriteManip.h"
#include "statistics/statistics.h" #include "statistics/statistics.h"
#include "storage/bufmgr.h"
#include "utils/acl.h" #include "utils/acl.h"
#include "utils/array.h" #include "utils/array.h"
#include "utils/builtins.h" #include "utils/builtins.h"
@@ -268,7 +266,7 @@ static bool get_actual_variable_endpoint(Relation heapRel,
ScanKey scankeys, ScanKey scankeys,
int16 typLen, int16 typLen,
bool typByVal, bool typByVal,
TupleTableSlot *tableslot, TupleTableSlot *slot,
MemoryContext outercontext, MemoryContext outercontext,
Datum *endpointDatum); Datum *endpointDatum);
static RelOptInfo *find_join_input_rel(PlannerInfo *root, Relids relids); static RelOptInfo *find_join_input_rel(PlannerInfo *root, Relids relids);
@@ -7142,7 +7140,8 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata,
indexRel = index_open(index->indexoid, NoLock); indexRel = index_open(index->indexoid, NoLock);
/* build some stuff needed for indexscan execution */ /* build some stuff needed for indexscan execution */
slot = table_slot_create(heapRel, NULL); slot = MakeSingleTupleTableSlot(RelationGetDescr(indexRel),
&TTSOpsVirtual);
get_typlenbyval(vardata->atttype, &typLen, &typByVal); get_typlenbyval(vardata->atttype, &typLen, &typByVal);
/* set up an IS NOT NULL scan key so that we ignore nulls */ /* set up an IS NOT NULL scan key so that we ignore nulls */
@@ -7214,8 +7213,7 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata,
* *
* scankeys is a 1-element scankey array set up to reject nulls. * scankeys is a 1-element scankey array set up to reject nulls.
* typLen/typByVal describe the datatype of the index's first column. * typLen/typByVal describe the datatype of the index's first column.
* tableslot is a slot suitable to hold table tuples, in case we need * slot is a virtual slot to receive each index tuple's values.
* to probe the heap.
* (We could compute these values locally, but that would mean computing them * (We could compute these values locally, but that would mean computing them
* twice when get_actual_variable_range needs both the min and the max.) * twice when get_actual_variable_range needs both the min and the max.)
* *
@@ -7229,19 +7227,15 @@ get_actual_variable_endpoint(Relation heapRel,
ScanKey scankeys, ScanKey scankeys,
int16 typLen, int16 typLen,
bool typByVal, bool typByVal,
TupleTableSlot *tableslot, TupleTableSlot *slot,
MemoryContext outercontext, MemoryContext outercontext,
Datum *endpointDatum) Datum *endpointDatum)
{ {
bool have_data = false; bool have_data = false;
SnapshotData SnapshotNonVacuumable; SnapshotData SnapshotNonVacuumable;
IndexScanDesc index_scan; IndexScanDesc index_scan;
Buffer vmbuffer = InvalidBuffer; Datum val;
BlockNumber last_heap_block = InvalidBlockNumber; bool isnull;
int n_visited_heap_pages = 0;
ItemPointer tid;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
MemoryContext oldcontext; MemoryContext oldcontext;
/* /*
@@ -7287,95 +7281,49 @@ get_actual_variable_endpoint(Relation heapRel,
* a huge amount of time here, so we give up once we've read too many heap * a huge amount of time here, so we give up once we've read too many heap
* pages. When we fail for that reason, the caller will end up using * pages. When we fail for that reason, the caller will end up using
* whatever extremal value is recorded in pg_statistic. * whatever extremal value is recorded in pg_statistic.
*
* We set xs_visited_pages_limit to tell the table AM to give up once it
* has switched heap pages this many times without finding a visible
* tuple.
*/ */
#define VISITED_PAGES_LIMIT 100
InitNonVacuumableSnapshot(SnapshotNonVacuumable, InitNonVacuumableSnapshot(SnapshotNonVacuumable,
GlobalVisTestFor(heapRel)); GlobalVisTestFor(heapRel));
index_scan = index_beginscan(heapRel, indexRel, index_scan = index_beginscan(heapRel, indexRel, true,
&SnapshotNonVacuumable, NULL, &SnapshotNonVacuumable, NULL,
1, 0, 1, 0,
SO_NONE); SO_NONE);
/* Set it up for index-only scan */ Assert(index_scan->xs_want_itup);
index_scan->xs_want_itup = true; index_scan->xs_visited_pages_limit = VISITED_PAGES_LIMIT;
index_rescan(index_scan, scankeys, 1, NULL, 0); index_rescan(index_scan, scankeys, 1, NULL, 0);
/* Fetch first/next tuple in specified direction */ /* Fetch first/next tuple in specified direction */
while ((tid = index_getnext_tid(index_scan, indexscandir)) != NULL) while (table_index_getnext_slot(index_scan, indexscandir, slot))
{ {
BlockNumber block = ItemPointerGetBlockNumber(tid);
if (!VM_ALL_VISIBLE(heapRel,
block,
&vmbuffer))
{
/* Rats, we have to visit the heap to check visibility */
if (!index_fetch_heap(index_scan, tableslot))
{
/*
* No visible tuple for this index entry, so we need to
* advance to the next entry. Before doing so, count heap
* page fetches and give up if we've done too many.
*
* We don't charge a page fetch if this is the same heap page
* as the previous tuple. This is on the conservative side,
* since other recently-accessed pages are probably still in
* buffers too; but it's good enough for this heuristic.
*/
#define VISITED_PAGES_LIMIT 100
if (block != last_heap_block)
{
last_heap_block = block;
n_visited_heap_pages++;
if (n_visited_heap_pages > VISITED_PAGES_LIMIT)
break;
}
continue; /* no visible tuple, try next index entry */
}
/* We don't actually need the heap tuple for anything */
ExecClearTuple(tableslot);
/*
* We don't care whether there's more than one visible tuple in
* the HOT chain; if any are visible, that's good enough.
*/
}
/*
* We expect that the index will return data in IndexTuple not
* HeapTuple format.
*/
if (!index_scan->xs_itup)
elog(ERROR, "no data returned for index-only scan");
/* /*
* We do not yet support recheck here. * We do not yet support recheck here.
*/ */
if (index_scan->xs_recheck) if (index_scan->xs_recheck)
break; break;
/* OK to deconstruct the index tuple */ /* Read the index's first column value out of the slot */
index_deform_tuple(index_scan->xs_itup, val = slot_getattr(slot, 1, &isnull);
index_scan->xs_itupdesc,
values, isnull);
/* Shouldn't have got a null, but be careful */ /* Shouldn't have got a null, but be careful */
if (isnull[0]) if (isnull)
elog(ERROR, "found unexpected null value in index \"%s\"", elog(ERROR, "found unexpected null value in index \"%s\"",
RelationGetRelationName(indexRel)); RelationGetRelationName(indexRel));
/* Copy the index column value out to caller's context */ /* Copy the index column value out to caller's context */
oldcontext = MemoryContextSwitchTo(outercontext); oldcontext = MemoryContextSwitchTo(outercontext);
*endpointDatum = datumCopy(values[0], typByVal, typLen); *endpointDatum = datumCopy(val, typByVal, typLen);
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
have_data = true; have_data = true;
break; break;
} }
if (vmbuffer != InvalidBuffer) ExecClearTuple(slot); /* defensive, slot can be reused by later scan */
ReleaseBuffer(vmbuffer);
index_endscan(index_scan); index_endscan(index_scan);
return have_data; return have_data;

View File

@@ -156,6 +156,7 @@ extern void index_insert_cleanup(Relation indexRelation,
extern IndexScanDesc index_beginscan(Relation heapRelation, extern IndexScanDesc index_beginscan(Relation heapRelation,
Relation indexRelation, Relation indexRelation,
bool index_only_scan,
Snapshot snapshot, Snapshot snapshot,
IndexScanInstrumentation *instrument, IndexScanInstrumentation *instrument,
int nkeys, int norderbys, int nkeys, int norderbys,
@@ -178,15 +179,11 @@ extern void index_parallelscan_initialize(Relation heapRelation,
extern void index_parallelrescan(IndexScanDesc scan); extern void index_parallelrescan(IndexScanDesc scan);
extern IndexScanDesc index_beginscan_parallel(Relation heaprel, extern IndexScanDesc index_beginscan_parallel(Relation heaprel,
Relation indexrel, Relation indexrel,
bool index_only_scan,
IndexScanInstrumentation *instrument, IndexScanInstrumentation *instrument,
int nkeys, int norderbys, int nkeys, int norderbys,
ParallelIndexScanDesc pscan, ParallelIndexScanDesc pscan,
uint32 flags); uint32 flags);
extern ItemPointer index_getnext_tid(IndexScanDesc scan,
ScanDirection direction);
extern bool index_fetch_heap(IndexScanDesc scan, TupleTableSlot *slot);
extern bool index_getnext_slot(IndexScanDesc scan, ScanDirection direction,
TupleTableSlot *slot);
extern int64 index_getbitmap(IndexScanDesc scan, TIDBitmap *bitmap); extern int64 index_getbitmap(IndexScanDesc scan, TIDBitmap *bitmap);
extern IndexBulkDeleteResult *index_bulk_delete(IndexVacuumInfo *info, extern IndexBulkDeleteResult *index_bulk_delete(IndexVacuumInfo *info,

View File

@@ -115,12 +115,10 @@ typedef struct BitmapHeapScanDescData
typedef struct BitmapHeapScanDescData *BitmapHeapScanDesc; typedef struct BitmapHeapScanDescData *BitmapHeapScanDesc;
/* /*
* Descriptor for fetches from heap via an index. * heapam-specific IndexScanDescData opaque state
*/ */
typedef struct IndexFetchHeapData typedef struct IndexScanHeapData
{ {
IndexFetchTableData xs_base; /* AM independent part of the descriptor */
/* /*
* Current heap buffer in scan (and its block number), if any. NB: if * Current heap buffer in scan (and its block number), if any. NB: if
* xs_blk is not InvalidBlockNumber, we hold a pin in xs_cbuf. * xs_blk is not InvalidBlockNumber, we hold a pin in xs_cbuf.
@@ -128,9 +126,12 @@ typedef struct IndexFetchHeapData
Buffer xs_cbuf; Buffer xs_cbuf;
BlockNumber xs_blk; BlockNumber xs_blk;
/* Current heap block's corresponding page in the visibility map */ Buffer xs_vmbuffer; /* visibility map buffer */
Buffer xs_vmbuffer;
} IndexFetchHeapData; bool xs_readonly; /* scan is read-only? */
uint16 xs_blkswitch_count; /* number of heap blocks fetched */
} IndexScanHeapData;
/* Result codes for HeapTupleSatisfiesVacuum */ /* Result codes for HeapTupleSatisfiesVacuum */
typedef enum typedef enum
@@ -430,16 +431,14 @@ extern TransactionId heap_index_delete_tuples(Relation rel,
TM_IndexDeleteOp *delstate); TM_IndexDeleteOp *delstate);
/* in heap/heapam_indexscan.c */ /* in heap/heapam_indexscan.c */
extern IndexFetchTableData *heapam_index_fetch_begin(Relation rel, uint32 flags); extern bool heapam_fetch_tid(Relation rel, ItemPointer tid, Snapshot snapshot,
extern void heapam_index_fetch_reset(IndexFetchTableData *scan); bool *all_dead);
extern void heapam_index_fetch_end(IndexFetchTableData *scan); extern void heapam_index_scan_begin(IndexScanDesc scan, uint32 flags);
extern void heapam_index_scan_reset(IndexScanDesc scan);
extern void heapam_index_scan_end(IndexScanDesc scan);
extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation, extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation,
Buffer buffer, Snapshot snapshot, HeapTuple heapTuple, Buffer buffer, Snapshot snapshot, HeapTuple heapTuple,
bool *all_dead, bool first_call); 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 */ /* in heap/pruneheap.c */
extern void heap_page_prune_opt(Relation relation, Buffer buffer, extern void heap_page_prune_opt(Relation relation, Buffer buffer,

View File

@@ -16,6 +16,7 @@
#include "access/htup_details.h" #include "access/htup_details.h"
#include "access/itup.h" #include "access/itup.h"
#include "access/sdir.h"
#include "nodes/tidbitmap.h" #include "nodes/tidbitmap.h"
#include "port/atomics.h" #include "port/atomics.h"
#include "storage/relfilelocator.h" #include "storage/relfilelocator.h"
@@ -24,6 +25,7 @@
struct ParallelTableScanDescData; struct ParallelTableScanDescData;
struct TableScanInstrumentation; struct TableScanInstrumentation;
struct TupleTableSlot;
/* /*
* Generic descriptor for table scans. This is the base-class for table scans, * Generic descriptor for table scans. This is the base-class for table scans,
@@ -118,22 +120,6 @@ typedef struct ParallelBlockTableScanWorkerData
} ParallelBlockTableScanWorkerData; } ParallelBlockTableScanWorkerData;
typedef struct ParallelBlockTableScanWorkerData *ParallelBlockTableScanWorker; typedef struct ParallelBlockTableScanWorkerData *ParallelBlockTableScanWorker;
/*
* Base class for fetches from a table via an index. This is the base-class
* for such scans, which needs to be embedded in the respective struct for
* individual AMs.
*/
typedef struct IndexFetchTableData
{
Relation rel;
/*
* Bitmask of ScanOptions affecting the relation. No SO_INTERNAL_FLAGS are
* permitted.
*/
uint32 flags;
} IndexFetchTableData;
struct IndexScanInstrumentation; struct IndexScanInstrumentation;
/* /*
@@ -170,10 +156,10 @@ typedef struct IndexScanDescData
struct IndexScanInstrumentation *instrument; struct IndexScanInstrumentation *instrument;
/* /*
* In an index-only scan, a successful amgettuple call must fill either * In an index-only scan, the index AM fills either xs_itup or xs_hitup
* xs_itup (and xs_itupdesc) or xs_hitup (and xs_hitupdesc) to provide the * with the data to be returned by the scan (it can fill both, in which
* data returned by the scan. It can fill both, in which case the heap * case the heap format is used). The table AM consumes these to fill the
* format will be used. * caller's slot during table_index_getnext_slot.
*/ */
IndexTuple xs_itup; /* index tuple returned by AM */ IndexTuple xs_itup; /* index tuple returned by AM */
struct TupleDescData *xs_itupdesc; /* rowtype descriptor of xs_itup */ struct TupleDescData *xs_itupdesc; /* rowtype descriptor of xs_itup */
@@ -183,10 +169,19 @@ typedef struct IndexScanDescData
ItemPointerData xs_heaptid; /* result */ ItemPointerData xs_heaptid; /* result */
bool xs_heap_continue; /* T if must keep walking, potential bool xs_heap_continue; /* T if must keep walking, potential
* further results */ * further results */
IndexFetchTableData *xs_heapfetch;
bool xs_recheck; /* T means scan keys must be rechecked */ bool xs_recheck; /* T means scan keys must be rechecked */
/* Table access method's private state (not used during bitmap scans) */
void *xs_table_opaque;
/*
* Callback that table_index_getnext_slot dispatches to, set by the table
* AM's index_scan_begin callback at the start of amgettuple scans
*/
bool (*xs_getnext_slot) (struct IndexScanDescData *scan,
ScanDirection direction,
struct TupleTableSlot *slot);
/* /*
* When fetching with an ordering operator, the values of the ORDER BY * When fetching with an ordering operator, the values of the ORDER BY
* expressions of the last returned tuple, according to the index. If * expressions of the last returned tuple, according to the index. If
@@ -198,6 +193,23 @@ typedef struct IndexScanDescData
bool *xs_orderbynulls; bool *xs_orderbynulls;
bool xs_recheckorderby; bool xs_recheckorderby;
/*
* Index attributes holding "name" columns stored as cstrings, which the
* table AM re-pads to NAMEDATALEN when filling a slot from xs_itup.
* xs_name_cstring_buf holds one NAMEDATALEN slot per such attribute.
*/
AttrNumber *xs_name_cstring_attnums;
char *xs_name_cstring_buf;
int xs_name_cstring_count;
/*
* An approximate limit on the amount of work, measured in pages touched,
* imposed on the index scan. The default, 0, means no limit. Only
* index-only scans may set a limit (plain index scans leave this zero).
* Used by selfuncs.c to bound the cost of get_actual_variable_endpoint().
*/
uint8 xs_visited_pages_limit;
/* parallel index scan information, in shared memory */ /* parallel index scan information, in shared memory */
struct ParallelIndexScanDescData *parallel_scan; struct ParallelIndexScanDescData *parallel_scan;
} IndexScanDescData; } IndexScanDescData;
@@ -211,8 +223,6 @@ typedef struct ParallelIndexScanDescData
char ps_snapshot_data[FLEXIBLE_ARRAY_MEMBER]; char ps_snapshot_data[FLEXIBLE_ARRAY_MEMBER];
} ParallelIndexScanDescData; } ParallelIndexScanDescData;
struct TupleTableSlot;
/* Struct for storage-or-index scans of system tables */ /* Struct for storage-or-index scans of system tables */
typedef struct SysScanDescData typedef struct SysScanDescData
{ {

View File

@@ -38,6 +38,7 @@ typedef struct BulkInsertStateData BulkInsertStateData;
typedef struct IndexInfo IndexInfo; typedef struct IndexInfo IndexInfo;
typedef struct SampleScanState SampleScanState; typedef struct SampleScanState SampleScanState;
typedef struct ScanKeyData ScanKeyData; typedef struct ScanKeyData ScanKeyData;
typedef struct IndexScanDescData *IndexScanDesc;
typedef struct ValidateIndexState ValidateIndexState; typedef struct ValidateIndexState ValidateIndexState;
typedef struct VacuumParams VacuumParams; typedef struct VacuumParams VacuumParams;
@@ -446,61 +447,60 @@ typedef struct TableAmRoutine
*/ */
/* /*
* Prepare to fetch tuples from the relation, as needed when fetching * Prepare for an index scan of the table. The callback stores its own
* tuples for an index scan. The callback has to return an * private scan state in the index scan descriptor's xs_table_opaque field
* IndexFetchTableData, which the AM will typically embed in a larger * (an opaque pointer).
* structure with additional information.
* *
* flags is a bitmask of ScanOptions affecting underlying table scan * flags is a bitmask of ScanOptions affecting underlying table scan
* behavior. See scan_begin() for more information on passing these. * behavior. See scan_begin() for more information on passing these.
* *
* Tuples for an index scan can then be fetched via index_fetch_tuple. * Callback is responsible for setting scan->xs_getnext_slot, the callback
* that table_index_getnext_slot() dispatches to. Tuples are then
* returned through the caller's slot. No separate xs_getnext_slot
* callback exists in this struct.
*
* In principle a single general-purpose callback (stored here) would
* suffice, but using specialized variants allows the table AM to provide
* minimal code based on conditions that are fixed for the whole scan as
* an optimization (e.g., variants for plain index scans and index-only
* scans, each with fewer branches).
*
* Plain index scans use whatever slot type the table AM's slot_callbacks
* chooses. Index-only scans always use a virtual slot, which is filled
* using index data in a standardized way (though determining which index
* tuples satisfy scan->xs_snapshot is still up to the table AM).
*
* The xs_getnext_slot callback is also responsible for whatever
* bookkeeping its callers expect of an index scan, such as maintaining
* instrumentation counters.
*/ */
struct IndexFetchTableData *(*index_fetch_begin) (Relation rel, uint32 flags); void (*index_scan_begin) (IndexScanDesc scan, uint32 flags);
/* /*
* Reset index fetch. Typically this will release cross index fetch * Inform the table AM that there's to be either a rescan or a restore of
* resources held in IndexFetchTableData. * a marked position.
*/ */
void (*index_fetch_reset) (struct IndexFetchTableData *data); void (*index_scan_reset) (IndexScanDesc scan);
/* /*
* Release resources and deallocate index fetch. * Release resources and deallocate index scan state.
*/ */
void (*index_fetch_end) (struct IndexFetchTableData *data); void (*index_scan_end) (IndexScanDesc scan);
/*
* Fetch tuple at `tid` into `slot`, after doing a visibility test
* according to `snapshot`. If a tuple was found and passed the visibility
* test, return true, false otherwise.
*
* Note that AMs that do not necessarily update indexes when indexed
* columns do not change, need to return the current/correct version of
* the tuple that is visible to the snapshot, even if the tid points to an
* older version of the tuple.
*
* *call_again is false on the first call to index_fetch_tuple for a tid.
* If there potentially is another tuple matching the tid, *call_again
* needs to be set to true by index_fetch_tuple, signaling to the caller
* that index_fetch_tuple should be called again for the same tid.
*
* *all_dead, if all_dead is not NULL, should be set to true by
* index_fetch_tuple iff it is guaranteed that no backend needs to see
* that tuple. Index AMs can use that to avoid returning that tid in
* future searches.
*/
bool (*index_fetch_tuple) (struct IndexFetchTableData *scan,
ItemPointer tid,
Snapshot snapshot,
TupleTableSlot *slot,
bool *call_again, bool *all_dead);
/* ------------------------------------------------------------------------ /* ------------------------------------------------------------------------
* Callbacks for non-modifying operations on individual tuples * Callbacks for non-modifying operations on individual tuples
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------
*/ */
/*
* Check whether any tuple reachable through `tid` passes a visibility
* test according to `snapshot`. Return true if so, false otherwise.
*/
bool (*fetch_tid) (Relation rel,
ItemPointer tid,
Snapshot snapshot,
bool *all_dead);
/* /*
* Fetch tuple at `tid` into `slot`, after doing a visibility test * Fetch tuple at `tid` into `slot`, after doing a visibility test
* according to `snapshot`. If a tuple was found and passed the visibility * according to `snapshot`. If a tuple was found and passed the visibility
@@ -1235,15 +1235,14 @@ table_parallelscan_reinitialize(Relation rel, ParallelTableScanDesc pscan)
*/ */
/* /*
* Prepare to fetch tuples from the relation, as needed when fetching tuples * Prepare an index scan descriptor by storing table AM private state in
* for an index scan. * scan->xs_table_opaque and setting scan->xs_getnext_slot. index_beginscan
* calls here after it has called ambeginscan.
* *
* flags is a bitmask of ScanOptions. No SO_INTERNAL_FLAGS are permitted. * flags is a bitmask of ScanOptions. No SO_INTERNAL_FLAGS are permitted.
*
* Tuples for an index scan can then be fetched via table_index_fetch_tuple().
*/ */
static inline IndexFetchTableData * static inline void
table_index_fetch_begin(Relation rel, uint32 flags) table_index_scan_begin(IndexScanDesc scan, uint32 flags)
{ {
Assert((flags & SO_INTERNAL_FLAGS) == 0); Assert((flags & SO_INTERNAL_FLAGS) == 0);
@@ -1255,74 +1254,61 @@ table_index_fetch_begin(Relation rel, uint32 flags)
if (unlikely(TransactionIdIsValid(CheckXidAlive) && !bsysscan)) if (unlikely(TransactionIdIsValid(CheckXidAlive) && !bsysscan))
elog(ERROR, "scan started during logical decoding"); elog(ERROR, "scan started during logical decoding");
return rel->rd_tableam->index_fetch_begin(rel, flags); scan->heapRelation->rd_tableam->index_scan_begin(scan, flags);
} }
/* /*
* Reset index fetch. Typically this will release cross index fetch resources * Inform the table AM that there's to be either a rescan or a restore of a
* held in IndexFetchTableData. * marked position
*/ */
static inline void static inline void
table_index_fetch_reset(struct IndexFetchTableData *scan) table_index_scan_reset(IndexScanDesc scan)
{ {
scan->rel->rd_tableam->index_fetch_reset(scan); Assert(scan->xs_table_opaque);
scan->heapRelation->rd_tableam->index_scan_reset(scan);
} }
/* /*
* Release resources and deallocate index fetch. * Release resources and deallocate the table AM's private index scan state
* (the scan's xs_table_opaque). index_endscan calls here right before
* calling amendscan.
*/ */
static inline void static inline void
table_index_fetch_end(struct IndexFetchTableData *scan) table_index_scan_end(IndexScanDesc scan)
{ {
scan->rel->rd_tableam->index_fetch_end(scan); Assert(scan->xs_table_opaque);
scan->heapRelation->rd_tableam->index_scan_end(scan);
} }
/* /*
* Fetches, as part of an index scan, tuple at `tid` into `slot`, after doing * Return the next tuple from an index scan through `slot`, scanning in the
* a visibility test according to `snapshot`. If a tuple was found and passed * specified direction. Returns true if a tuple satisfying the scan keys and
* the visibility test, returns true, false otherwise. Note that *tid may be * the snapshot was found, false otherwise.
* modified when we return true (see later remarks on multiple row versions
* reachable via a single index entry).
* *
* *call_again needs to be false on the first call to table_index_fetch_tuple() for * Dispatches through scan->xs_getnext_slot, which is resolved once by the
* a tid. If there potentially is another tuple matching the tid, *call_again * table AM's index_scan_begin callback.
* will be set to true, signaling that table_index_fetch_tuple() should be called
* again for the same tid.
* *
* *all_dead, if all_dead is not NULL, will be set to true by * On success, resources (like buffer pins) are likely to be held, and will be
* table_index_fetch_tuple() iff it is guaranteed that no backend needs to see * released by a future table_index_getnext_slot or table_index_scan_end call.
* that tuple. Index AMs can use that to avoid returning that tid in future
* searches.
* *
* The difference between this function and table_tuple_fetch_row_version() * Note: caller must check scan->xs_recheck, and perform rechecking of the
* is that this function returns the currently visible version of a row if * scan keys if required. We do not do that here because we don't have
* the AM supports storing multiple row versions reachable via a single index * enough information to do it efficiently in the general case. Similarly,
* entry (like heap's HOT). Whereas table_tuple_fetch_row_version() only * for ordered scans, the caller must check scan->xs_recheckorderby and
* evaluates the tuple exactly at `tid`. Outside of index entry ->table tuple * recheck the ORDER BY expressions for itself.
* lookups, table_tuple_fetch_row_version() is what's usually needed.
*/ */
static inline bool static inline bool
table_index_fetch_tuple(struct IndexFetchTableData *scan, table_index_getnext_slot(IndexScanDesc scan, ScanDirection direction,
ItemPointer tid, TupleTableSlot *slot)
Snapshot snapshot,
TupleTableSlot *slot,
bool *call_again, bool *all_dead)
{ {
return scan->rel->rd_tableam->index_fetch_tuple(scan, tid, snapshot, /* See index_scan_begin for an explanation of index-only scan slot type */
slot, call_again, Assert(!scan->xs_want_itup || TTS_IS_VIRTUAL(slot));
all_dead); Assert(scan->xs_table_opaque);
}
/* return scan->xs_getnext_slot(scan, direction, slot);
* This is a convenience wrapper around table_index_fetch_tuple() which }
* returns whether there are table tuple items corresponding to an index
* entry. This likely is only useful to verify if there's a conflict in a
* unique index.
*/
extern bool table_index_fetch_tuple_check(Relation rel,
ItemPointer tid,
Snapshot snapshot,
bool *all_dead);
/* ------------------------------------------------------------------------ /* ------------------------------------------------------------------------
@@ -1331,14 +1317,46 @@ extern bool table_index_fetch_tuple_check(Relation rel,
*/ */
/*
* Check whether any tuple reachable through `tid` passes a visibility test
* according to `snapshot`. Returns true if so, false otherwise. This is a
* low-level interface designed for use by constraint enforcement code.
*
* Unlike table_tuple_fetch_row_version(), every version reachable from `tid`
* is tested (such as the members of a heapam HOT chain), and *tid may be
* modified to point at the visible version when we return true. Caller
* should consider passing a pointer to a mutable copy of their original TID
* to avoid unwanted side-effects.
*
* If all_dead is not NULL, *all_dead will be set to true here iff it is
* guaranteed that no backend needs to see any tuple reachable through
* caller's TID. This means that it is safe to mark an index tuple containing
* this TID as LP_DEAD.
*/
static inline bool
table_fetch_tid(Relation rel,
ItemPointer tid,
Snapshot snapshot,
bool *all_dead)
{
/*
* We don't expect direct calls to table_fetch_tid with valid
* CheckXidAlive for catalog or regular tables. See detailed comments in
* xact.c where these variables are declared.
*/
if (unlikely(TransactionIdIsValid(CheckXidAlive) && !bsysscan))
elog(ERROR, "unexpected table_fetch_tid call during logical decoding");
return rel->rd_tableam->fetch_tid(rel, tid, snapshot, all_dead);
}
/* /*
* Fetch tuple at `tid` into `slot`, after doing a visibility test according to * Fetch tuple at `tid` into `slot`, after doing a visibility test according to
* `snapshot`. If a tuple was found and passed the visibility test, returns * `snapshot`. If a tuple was found and passed the visibility test, returns
* true, false otherwise. * true, false otherwise.
* *
* See table_index_fetch_tuple's comment about what the difference between * Unlike table_fetch_tid(), only the tuple at `tid` itself is tested; no
* these functions is. It is correct to use this function outside of index * version chain is followed.
* entry->table tuple lookups.
*/ */
static inline bool static inline bool
table_tuple_fetch_row_version(Relation rel, table_tuple_fetch_row_version(Relation rel,

View File

@@ -0,0 +1,112 @@
/*-------------------------------------------------------------------------
*
* tableam_indexscan.h
* Helpers for table AM index scan callbacks.
*
* Table AMs can use these functions to implement xs_getnext_slot callbacks.
* See access/tableam.h.
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* src/include/access/tableam_indexscan.h
*
*-------------------------------------------------------------------------
*/
#ifndef TABLEAM_INDEXSCAN_H
#define TABLEAM_INDEXSCAN_H
#include "access/amapi.h"
#include "access/genam.h"
#include "access/relscan.h"
#include "executor/tuptable.h"
#include "pgstat.h"
#include "utils/rel.h"
extern pg_attribute_cold void tableam_index_fill_ios_names(IndexScanDesc scan,
TupleTableSlot *slot);
/*
* Fetch the next matching TID for the scan (or the first) through the
* amgettuple interface.
*
* Returns true when a TID was found (the index AM will have saved it in
* scan->xs_heaptid), or false when we're out of index entries.
*/
static inline bool
tableam_index_getnext_tid(IndexScanDesc scan, ScanDirection direction)
{
bool found;
found = scan->indexRelation->rd_indam->amgettuple(scan, direction);
/* Reset kill flag immediately for safety */
scan->kill_prior_tuple = false;
scan->xs_heap_continue = false;
/* If we're out of index entries, we're done */
if (!found)
return false;
Assert(ItemPointerIsValid(&scan->xs_heaptid));
pgstat_count_index_tuples(scan->indexRelation, 1);
return true;
}
/*
* Fill an index-only scan's result slot from the data the index AM returned.
*
* The data is provided in either HeapTuple (xs_hitup) or IndexTuple (xs_itup)
* format. An index AM may fill both, in which case the heap format is used,
* since it's a bit cheaper to fill a slot from.
*
* Called by table AMs from their xs_getnext_slot callbacks.
*/
static inline void
tableam_index_fill_ios_slot(IndexScanDesc scan, TupleTableSlot *slot)
{
ExecClearTuple(slot);
/*
* We must deform the tuple using the tupdesc the index AM formed it with
* (xs_hitupdesc or xs_itupdesc), not the slot's tupdesc. The datums
* returned by the index AM must be binary compatible, but the descriptors
* may align each column differently in certain rare cases. (Actually,
* btree's "name" opclass stores cstring tuples that _aren't_ even binary
* compatible, in the strictest sense. tableam_index_fill_ios_names
* handles that.)
*/
if (scan->xs_hitup)
{
Assert(slot->tts_tupleDescriptor->natts == scan->xs_hitupdesc->natts);
heap_deform_tuple(scan->xs_hitup, scan->xs_hitupdesc,
slot->tts_values, slot->tts_isnull);
}
else if (scan->xs_itup)
{
Assert(slot->tts_tupleDescriptor->natts == scan->xs_itupdesc->natts);
index_deform_tuple(scan->xs_itup, scan->xs_itupdesc,
slot->tts_values, slot->tts_isnull);
/*
* Copy all name columns stored as cstrings back into NAMEDATALEN
* bytes of xs_name_cstring_buf. We mark this branch as unlikely as
* generally "name" is used only for the system catalogs and this
* would have to be a user query running on those or some other user
* table with an index on a name column.
*/
if (unlikely(scan->xs_name_cstring_attnums != NULL))
tableam_index_fill_ios_names(scan, slot);
}
else
elog(ERROR, "no data returned for index-only scan");
ExecStoreVirtualTuple(slot);
}
#endif /* TABLEAM_INDEXSCAN_H */

View File

@@ -94,8 +94,8 @@
# so we choose to omit that within an index entry. Here we declare the # so we choose to omit that within an index entry. Here we declare the
# btree opclass for name as using cstring storage type. This does require # btree opclass for name as using cstring storage type. This does require
# that we pad the cstring out with the full NAMEDATALEN bytes when performing # that we pad the cstring out with the full NAMEDATALEN bytes when performing
# index-only scans. See corresponding hacks in ExecInitIndexOnlyScan() and # index-only scans. See corresponding hacks in index_beginscan() and
# StoreIndexTuple(). # tableam_index_fill_ios_slot().
{ opcmethod => 'btree', opcname => 'name_ops', opcfamily => 'btree/text_ops', { opcmethod => 'btree', opcname => 'name_ops', opcfamily => 'btree/text_ops',
opcintype => 'name', opckeytype => 'cstring' }, opcintype => 'name', opckeytype => 'cstring' },

View File

@@ -98,13 +98,16 @@ AccumulateIOStats(IOStats *dst, IOStats *src)
/* --------------------- /* ---------------------
* Instrumentation information for indexscans (amgettuple and amgetbitmap) * Instrumentation information for index scans (used by all AM interfaces)
* --------------------- * ---------------------
*/ */
typedef struct IndexScanInstrumentation typedef struct IndexScanInstrumentation
{ {
/* Index search count (incremented with pgstat_count_index_scan call) */ /* Index search count (incremented with pgstat_count_index_scan call) */
uint64 nsearches; uint64 nsearches;
/* Table tuples fetched count (incremented during index-only scans) */
uint64 ntabletuplefetches;
} IndexScanInstrumentation; } IndexScanInstrumentation;
/* /*

View File

@@ -1769,11 +1769,7 @@ typedef struct IndexScanState
* ScanDesc index scan descriptor * ScanDesc index scan descriptor
* Instrument local index scan instrumentation * Instrument local index scan instrumentation
* SharedInfo parallel worker instrumentation (no leader entry) * SharedInfo parallel worker instrumentation (no leader entry)
* TableSlot slot for holding tuples fetched from the table
* VMBuffer buffer in use for visibility map testing, if any
* PscanLen size of parallel index-only scan descriptor * PscanLen size of parallel index-only scan descriptor
* NameCStringAttNums attnums of name typed columns to pad to NAMEDATALEN
* NameCStringCount number of elements in the NameCStringAttNums array
* ---------------- * ----------------
*/ */
typedef struct IndexOnlyScanState typedef struct IndexOnlyScanState
@@ -1792,11 +1788,7 @@ typedef struct IndexOnlyScanState
struct IndexScanDescData *ioss_ScanDesc; struct IndexScanDescData *ioss_ScanDesc;
IndexScanInstrumentation *ioss_Instrument; IndexScanInstrumentation *ioss_Instrument;
SharedIndexScanInstrumentation *ioss_SharedInfo; SharedIndexScanInstrumentation *ioss_SharedInfo;
TupleTableSlot *ioss_TableSlot;
Buffer ioss_VMBuffer;
Size ioss_PscanLen; Size ioss_PscanLen;
AttrNumber *ioss_NameCStringAttNums;
int ioss_NameCStringCount;
} IndexOnlyScanState; } IndexOnlyScanState;
/* ---------------- /* ----------------

View File

@@ -115,11 +115,11 @@ index_scan_tids(PG_FUNCTION_ARGS)
slot = table_slot_create(heaprel, NULL); slot = table_slot_create(heaprel, NULL);
scan = index_beginscan(heaprel, indexrel, snapshot, NULL, scan = index_beginscan(heaprel, indexrel, false, snapshot, NULL,
0, 0, SO_NONE); 0, 0, SO_NONE);
index_rescan(scan, NULL, 0, NULL, 0); index_rescan(scan, NULL, 0, NULL, 0);
while (index_getnext_slot(scan, dir, slot)) while (table_index_getnext_slot(scan, dir, slot))
{ {
ItemPointerData tid = slot->tts_tid; ItemPointerData tid = slot->tts_tid;
Datum values[1]; Datum values[1];

View File

@@ -1312,8 +1312,6 @@ IndexDeleteCounts
IndexDeletePrefetchState IndexDeletePrefetchState
IndexDoCheckCallback IndexDoCheckCallback
IndexElem IndexElem
IndexFetchHeapData
IndexFetchTableData
IndexInfo IndexInfo
IndexList IndexList
IndexOnlyScan IndexOnlyScan
@@ -1325,6 +1323,7 @@ IndexRuntimeKeyInfo
IndexScan IndexScan
IndexScanDesc IndexScanDesc
IndexScanDescData IndexScanDescData
IndexScanHeapData
IndexScanInstrumentation IndexScanInstrumentation
IndexScanState IndexScanState
IndexStateFlagsAction IndexStateFlagsAction