diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c index 3ef2d66f826..c5e1f901098 100644 --- a/contrib/amcheck/verify_nbtree.c +++ b/contrib/amcheck/verify_nbtree.c @@ -395,7 +395,7 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace, 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. */ Assert(TransactionIdIsValid(RecentXmin)); diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index dd2218bbbaf..6adb760b54f 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -665,7 +665,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, tableScan = NULL; heapScan = NULL; - indexScan = index_beginscan(OldHeap, OldIndex, + indexScan = index_beginscan(OldHeap, OldIndex, false, snapshot ? snapshot : SnapshotAny, NULL, 0, 0, SO_NONE); @@ -708,7 +708,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, if (indexScan != NULL) { - if (!index_getnext_slot(indexScan, ForwardScanDirection, slot)) + if (!table_index_getnext_slot(indexScan, ForwardScanDirection, + slot)) break; /* 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_reinitialize = table_block_parallelscan_reinitialize, - .index_fetch_begin = heapam_index_fetch_begin, - .index_fetch_reset = heapam_index_fetch_reset, - .index_fetch_end = heapam_index_fetch_end, - .index_fetch_tuple = heapam_index_fetch_tuple, + .index_scan_begin = heapam_index_scan_begin, + .index_scan_reset = heapam_index_scan_reset, + .index_scan_end = heapam_index_scan_end, .tuple_insert = heapam_tuple_insert, .tuple_insert_speculative = heapam_tuple_insert_speculative, @@ -2678,6 +2678,7 @@ static const TableAmRoutine heapam_methods = { .tuple_update = heapam_tuple_update, .tuple_lock = heapam_tuple_lock, + .fetch_tid = heapam_fetch_tid, .tuple_fetch_row_version = heapam_fetch_row_version, .tuple_get_latest_tid = heap_get_latest_tid, .tuple_tid_valid = heapam_tuple_tid_valid, diff --git a/src/backend/access/heap/heapam_indexscan.c b/src/backend/access/heap/heapam_indexscan.c index 33d14f1de7d..0ae028ecd41 100644 --- a/src/backend/access/heap/heapam_indexscan.c +++ b/src/backend/access/heap/heapam_indexscan.c @@ -14,36 +14,93 @@ */ #include "postgres.h" +#include "access/amapi.h" #include "access/heapam.h" #include "access/relscan.h" +#include "access/tableam_indexscan.h" +#include "access/visibilitymap.h" +#include "pgstat.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 * ------------------------------------------------------------------------ */ -IndexFetchTableData * -heapam_index_fetch_begin(Relation rel, uint32 flags) +void +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_blk = InvalidBlockNumber; 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 -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. * This saves cycles during certain tight nested loop joins (it can avoid * repeated pinning and unpinning of the same buffer across rescans). @@ -51,9 +108,9 @@ heapam_index_fetch_reset(IndexFetchTableData *scan) } 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 */ if (BufferIsValid(hscan->xs_cbuf)) @@ -228,38 +285,195 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer, return false; } -bool -heapam_index_fetch_tuple(struct IndexFetchTableData *scan, - ItemPointer tid, - Snapshot snapshot, - TupleTableSlot *slot, - bool *heap_continue, bool *all_dead) +/* xs_getnext_slot callback: amgettuple, plain index scan */ +static bool +heapam_index_plain_tuple_getnext_slot(IndexScanDesc scan, + ScanDirection direction, + TupleTableSlot *slot) { - IndexFetchHeapData *hscan = (IndexFetchHeapData *) scan; - BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; - bool got_heap_tuple; + Assert(!scan->xs_want_itup); + Assert(scan->indexRelation->rd_indam->amgettuple != NULL); - 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. */ if (hscan->xs_blk != ItemPointerGetBlockNumber(tid)) { - Assert(!*heap_continue); + Assert(!scan->xs_heap_continue); /* Remember this buffer's block number for next time */ 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)) 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 */ - heap_page_prune_opt(hscan->xs_base.rel, hscan->xs_cbuf, - &hscan->xs_vmbuffer, - hscan->xs_base.flags & SO_HINT_REL_READ_ONLY); + heap_page_prune_opt(rel, hscan->xs_cbuf, &hscan->xs_vmbuffer, + hscan->xs_readonly); } 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 */ LockBuffer(hscan->xs_cbuf, BUFFER_LOCK_SHARE); got_heap_tuple = heap_hot_search_buffer(tid, - hscan->xs_base.rel, + rel, hscan->xs_cbuf, snapshot, - &bslot->base.tupdata, - all_dead, - !*heap_continue); - bslot->base.tupdata.t_self = *tid; + heapTuple, + &all_dead, + !scan->xs_heap_continue); + heapTuple->t_self = *tid; LockBuffer(hscan->xs_cbuf, BUFFER_LOCK_UNLOCK); if (got_heap_tuple) { - /* - * Only in a non-MVCC snapshot can more than one member of the HOT - * chain be visible. - */ - *heap_continue = !IsMVCCLikeSnapshot(snapshot); + if (!index_only) + { + /* + * Only with a non-MVCC snapshot can more than one HOT chain + * 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(&bslot->base.tupdata, slot, hscan->xs_cbuf); + ExecStoreBufferHeapTuple(heapTuple, 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 { /* 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; } + +/* + * 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; +} diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c index 1408989c568..c4bb251bf83 100644 --- a/src/backend/access/index/genam.c +++ b/src/backend/access/index/genam.c @@ -84,7 +84,7 @@ RelationGetIndexScan(Relation indexRelation, int nkeys, int norderbys) scan = palloc_object(IndexScanDescData); scan->heapRelation = NULL; /* may be set later */ - scan->xs_heapfetch = NULL; + scan->xs_table_opaque = NULL; scan->indexRelation = indexRelation; scan->xs_snapshot = InvalidSnapshot; /* caller must initialize this */ scan->numberOfKeys = nkeys; @@ -126,6 +126,14 @@ RelationGetIndexScan(Relation indexRelation, int nkeys, int norderbys) scan->xs_hitup = 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; } @@ -148,6 +156,10 @@ IndexScanEnd(IndexScanDesc scan) pfree(scan->keyData); if (scan->orderByData != NULL) 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); } @@ -454,7 +466,7 @@ systable_beginscan(Relation heapRelation, elog(ERROR, "column is not in index"); } - sysscan->iscan = index_beginscan(heapRelation, irel, + sysscan->iscan = index_beginscan(heapRelation, irel, false, snapshot, NULL, nkeys, 0, SO_NONE); index_rescan(sysscan->iscan, idxkey, nkeys, NULL, 0); @@ -518,7 +530,8 @@ systable_getnext(SysScanDesc sysscan) if (sysscan->irel) { - if (index_getnext_slot(sysscan->iscan, ForwardScanDirection, sysscan->slot)) + if (table_index_getnext_slot(sysscan->iscan, ForwardScanDirection, + sysscan->slot)) { bool shouldFree; @@ -643,9 +656,9 @@ systable_endscan(SysScanDesc sysscan) * 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.) * For the moment, therefore, these functions are merely the thinest of - * wrappers around index_beginscan/index_getnext_slot. The main reason for - * their existence is to centralize possible future support of lossy operators - * in catalog scans. + * wrappers around index_beginscan/table_index_getnext_slot. The main reason + * for their existence is to centralize possible future support of lossy + * operators in catalog scans. */ SysScanDesc systable_beginscan_ordered(Relation heapRelation, @@ -716,7 +729,7 @@ systable_beginscan_ordered(Relation heapRelation, if (TransactionIdIsValid(CheckXidAlive)) bsysscan = true; - sysscan->iscan = index_beginscan(heapRelation, indexRelation, + sysscan->iscan = index_beginscan(heapRelation, indexRelation, false, snapshot, NULL, nkeys, 0, SO_NONE); index_rescan(sysscan->iscan, idxkey, nkeys, NULL, 0); @@ -736,7 +749,7 @@ systable_getnext_ordered(SysScanDesc sysscan, ScanDirection direction) HeapTuple htup = NULL; 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); /* See notes in systable_getnext */ diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c index 7967e939847..db811056991 100644 --- a/src/backend/access/index/indexam.c +++ b/src/backend/access/index/indexam.c @@ -24,9 +24,6 @@ * index_parallelscan_initialize - initialize parallel scan * index_parallelrescan - (re)start a parallel scan of an index * 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_bulk_delete - bulk deletion of index tuples * index_vacuum_cleanup - post-deletion cleanup of an index @@ -105,10 +102,17 @@ do { \ CppAsString(pname), RelationGetRelationName(scan->indexRelation)); \ } 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 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 index_beginscan(Relation heapRelation, Relation indexRelation, + bool index_only_scan, Snapshot snapshot, IndexScanInstrumentation *instrument, int nkeys, int norderbys, uint32 flags) { - IndexScanDesc scan; - Assert(snapshot != InvalidSnapshot); + pg_assume(heapRelation != NULL); /* Check that a historic snapshot is not used for non-catalog tables */ if (IsHistoricMVCCSnapshot(snapshot) && @@ -275,20 +279,10 @@ index_beginscan(Relation heapRelation, RelationGetRelationName(heapRelation)))); } - scan = index_beginscan_internal(indexRelation, nkeys, norderbys, snapshot, NULL, false); - - /* - * Save additional parameters into the scandesc. Everything else was set - * 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; + return index_beginscan_internal(indexRelation, heapRelation, + nkeys, norderbys, + snapshot, NULL, instrument, + index_only_scan, false, flags); } /* @@ -303,29 +297,24 @@ index_beginscan_bitmap(Relation indexRelation, IndexScanInstrumentation *instrument, int nkeys) { - IndexScanDesc scan; - Assert(snapshot != InvalidSnapshot); + Assert(IsMVCCLikeSnapshot(snapshot)); - scan = index_beginscan_internal(indexRelation, nkeys, 0, snapshot, NULL, false); - - /* - * Save additional parameters into the scandesc. Everything else was set - * up by RelationGetIndexScan. - */ - scan->xs_snapshot = snapshot; - scan->instrument = instrument; - - return scan; + return index_beginscan_internal(indexRelation, NULL, nkeys, 0, snapshot, + NULL, instrument, false, false, SO_NONE); } /* * index_beginscan_internal --- common code for index_beginscan variants + * + * When heapRelation is not NULL, also initializes table AM index scan state. */ -static IndexScanDesc -index_beginscan_internal(Relation indexRelation, +static pg_always_inline IndexScanDesc +index_beginscan_internal(Relation indexRelation, Relation heapRelation, 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; @@ -349,6 +338,63 @@ index_beginscan_internal(Relation indexRelation, scan->parallel_scan = pscan; 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; } @@ -376,8 +422,8 @@ index_rescan(IndexScanDesc scan, Assert(norderbys == scan->numberOfOrderBys); /* reset table AM state for rescan */ - if (scan->xs_heapfetch) - table_index_fetch_reset(scan->xs_heapfetch); + if (scan->xs_table_opaque) + table_index_scan_reset(scan); scan->kill_prior_tuple = false; /* for safety */ scan->xs_heap_continue = false; @@ -397,11 +443,8 @@ index_endscan(IndexScanDesc scan) CHECK_SCAN_PROCEDURE(amendscan); /* Release resources (like buffer pins) from table accesses */ - if (scan->xs_heapfetch) - { - table_index_fetch_end(scan->xs_heapfetch); - scan->xs_heapfetch = NULL; - } + if (scan->xs_table_opaque) + table_index_scan_end(scan); /* End the AM's scan */ scan->indexRelation->rd_indam->amendscan(scan); @@ -453,8 +496,8 @@ index_restrpos(IndexScanDesc scan) CHECK_SCAN_PROCEDURE(amrestrpos); /* reset table AM state for restoring the marked position */ - if (scan->xs_heapfetch) - table_index_fetch_reset(scan->xs_heapfetch); + if (scan->xs_table_opaque) + table_index_scan_reset(scan); scan->kill_prior_tuple = false; /* for safety */ scan->xs_heap_continue = false; @@ -540,8 +583,8 @@ index_parallelrescan(IndexScanDesc scan) SCAN_CHECKS; /* reset table AM state for rescan */ - if (scan->xs_heapfetch) - table_index_fetch_reset(scan->xs_heapfetch); + if (scan->xs_table_opaque) + table_index_scan_reset(scan); /* amparallelrescan is optional; assume no-op if not provided by AM */ if (scan->indexRelation->rd_indam->amparallelrescan != NULL) @@ -558,172 +601,24 @@ index_parallelrescan(IndexScanDesc scan) */ IndexScanDesc index_beginscan_parallel(Relation heaprel, Relation indexrel, + bool index_only_scan, IndexScanInstrumentation *instrument, int nkeys, int norderbys, ParallelIndexScanDesc pscan, uint32 flags) { Snapshot snapshot; - IndexScanDesc scan; Assert(RelFileLocatorEquals(heaprel->rd_locator, pscan->ps_locator)); Assert(RelFileLocatorEquals(indexrel->rd_locator, pscan->ps_indexlocator)); + pg_assume(heaprel != NULL); snapshot = RestoreSnapshot(pscan->ps_snapshot_data); RegisterSnapshot(snapshot); - scan = index_beginscan_internal(indexrel, nkeys, norderbys, snapshot, - pscan, true); - /* - * Save additional parameters into the scandesc. Everything else was set - * up by index_beginscan_internal. - */ - 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; + return index_beginscan_internal(indexrel, heaprel, nkeys, norderbys, + snapshot, pscan, instrument, + index_only_scan, true, flags); } /* ---------------- diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c index 3b945342d83..d5d964301c1 100644 --- a/src/backend/access/nbtree/nbtinsert.c +++ b/src/backend/access/nbtree/nbtinsert.c @@ -560,9 +560,8 @@ _bt_check_unique(Relation rel, BTInsertState insertstate, Relation heapRel, * with optimizations like heap's HOT, we have just a single * index entry for the entire chain. */ - else if (table_index_fetch_tuple_check(heapRel, &htid, - &SnapshotDirty, - &all_dead)) + else if (table_fetch_tid(heapRel, &htid, &SnapshotDirty, + &all_dead)) { TransactionId xwait; @@ -618,8 +617,7 @@ _bt_check_unique(Relation rel, BTInsertState insertstate, Relation heapRel, * entry. */ htid = itup->t_tid; - if (table_index_fetch_tuple_check(heapRel, &htid, - SnapshotSelf, NULL)) + if (table_fetch_tid(heapRel, &htid, SnapshotSelf, NULL)) { /* Normal case --- it's still live */ } diff --git a/src/backend/access/table/Makefile b/src/backend/access/table/Makefile index 9aba3ff489e..866286c6013 100644 --- a/src/backend/access/table/Makefile +++ b/src/backend/access/table/Makefile @@ -15,6 +15,7 @@ include $(top_builddir)/src/Makefile.global OBJS = \ table.o \ tableam.o \ + tableam_indexscan.o \ tableamapi.o \ toast_helper.o diff --git a/src/backend/access/table/meson.build b/src/backend/access/table/meson.build index 7c9e96e9874..3ea2398131d 100644 --- a/src/backend/access/table/meson.build +++ b/src/backend/access/table/meson.build @@ -3,6 +3,7 @@ backend_sources += files( 'table.c', 'tableam.c', + 'tableam_indexscan.c', 'tableamapi.c', 'toast_helper.c', ) diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c index f2038ea9205..7aaa8d87150 100644 --- a/src/backend/access/table/tableam.c +++ b/src/backend/access/table/tableam.c @@ -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 * ------------------------------------------------------------------------ diff --git a/src/backend/access/table/tableam_indexscan.c b/src/backend/access/table/tableam_indexscan.c new file mode 100644 index 00000000000..d63eeb7ae6c --- /dev/null +++ b/src/backend/access/table/tableam_indexscan.c @@ -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); + } +} diff --git a/src/backend/access/table/tableamapi.c b/src/backend/access/table/tableamapi.c index 5450a27faeb..72d2c662bf2 100644 --- a/src/backend/access/table/tableamapi.c +++ b/src/backend/access/table/tableamapi.c @@ -50,11 +50,11 @@ GetTableAmRoutine(Oid amhandler) Assert(routine->parallelscan_initialize != NULL); Assert(routine->parallelscan_reinitialize != NULL); - Assert(routine->index_fetch_begin != NULL); - Assert(routine->index_fetch_reset != NULL); - Assert(routine->index_fetch_end != NULL); - Assert(routine->index_fetch_tuple != NULL); + Assert(routine->index_scan_begin != NULL); + Assert(routine->index_scan_reset != NULL); + Assert(routine->index_scan_end != NULL); + Assert(routine->fetch_tid != NULL); Assert(routine->tuple_fetch_row_version != NULL); Assert(routine->tuple_tid_valid != NULL); Assert(routine->tuple_get_latest_tid != NULL); diff --git a/src/backend/commands/constraint.c b/src/backend/commands/constraint.c index 421d8c359f0..d1d383ddb7a 100644 --- a/src/backend/commands/constraint.c +++ b/src/backend/commands/constraint.c @@ -72,19 +72,18 @@ unique_key_recheck(PG_FUNCTION_ARGS) * Get the new data that was inserted/updated. */ 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)) - checktid = trigdata->tg_newslot->tts_tid; + slot = trigdata->tg_newslot; else { ereport(ERROR, (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), errmsg("function \"%s\" must be fired for INSERT or UPDATE", funcname))); - ItemPointerSetInvalid(&checktid); /* keep compiler quiet */ + slot = NULL; /* keep compiler quiet */ } - - slot = table_slot_create(trigdata->tg_relation, NULL); + checktid = slot->tts_tid; /* * 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. */ tmptid = checktid; + if (!table_fetch_tid(trigdata->tg_relation, &tmptid, SnapshotSelf, NULL)) { - IndexFetchTableData *scan = table_index_fetch_begin(trigdata->tg_relation, - SO_NONE); - bool call_again = false; - - 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); + /* + * All rows referenced by the index entry are dead, so skip the check + */ + return PointerGetDatum(NULL); } /* @@ -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 * that has already been inserted is unique. Passing the tuple's tid - * (i.e. unmodified by table_index_fetch_tuple()) is correct even if - * the row is now dead, because that is the TID the index will know - * about. + * (i.e. unmodified by table_fetch_tid()) is correct even if the row + * is now dead, because that is the TID the index will know about. */ index_insert(indexRel, values, isnull, &checktid, trigdata->tg_relation, UNIQUE_CHECK_EXISTING, @@ -199,8 +186,6 @@ unique_key_recheck(PG_FUNCTION_ARGS) if (estate != NULL) FreeExecutorState(estate); - ExecDropSingleTupleTableSlot(slot); - index_close(indexRel, RowExclusiveLock); return PointerGetDatum(NULL); diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index e35e0a649b3..96f2f0e2e74 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -137,7 +137,7 @@ static void show_recursive_union_info(RecursiveUnionState *rstate, static void show_memoize_info(MemoizeState *mstate, List *ancestors, 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, ExplainState *es); static void show_scan_io_usage(ScanState *planstate, @@ -1978,7 +1978,7 @@ ExplainNode(PlanState *planstate, List *ancestors, if (plan->qual) show_instrumentation_count("Rows Removed by Filter", 1, planstate, es); - show_indexsearches_info(planstate, es); + show_indexscan_info(planstate, es); break; case T_IndexOnlyScan: show_scan_qual(((IndexOnlyScan *) plan)->indexqual, @@ -1992,15 +1992,12 @@ ExplainNode(PlanState *planstate, List *ancestors, if (plan->qual) show_instrumentation_count("Rows Removed by Filter", 1, planstate, es); - if (es->analyze) - ExplainPropertyFloat("Heap Fetches", NULL, - planstate->instrument->ntuples2, 0, es); - show_indexsearches_info(planstate, es); + show_indexscan_info(planstate, es); break; case T_BitmapIndexScan: show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig, "Index Cond", planstate, ancestors, es); - show_indexsearches_info(planstate, es); + show_indexscan_info(planstate, es); break; case T_BitmapHeapScan: 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 */ static void -show_indexsearches_info(PlanState *planstate, ExplainState *es) +show_indexscan_info(PlanState *planstate, ExplainState *es) { Plan *plan = planstate->plan; SharedIndexScanInstrumentation *SharedInfo = NULL; - uint64 nsearches = 0; + uint64 nsearches = 0, + ntabletuplefetches = 0; if (!es->analyze) return; @@ -3901,6 +3899,7 @@ show_indexsearches_info(PlanState *planstate, ExplainState *es) IndexOnlyScanState *indexstate = ((IndexOnlyScanState *) planstate); nsearches = indexstate->ioss_Instrument->nsearches; + ntabletuplefetches = indexstate->ioss_Instrument->ntabletuplefetches; SharedInfo = indexstate->ioss_SharedInfo; break; } @@ -3924,9 +3923,13 @@ show_indexsearches_info(PlanState *planstate, ExplainState *es) IndexScanInstrumentation *winstrument = &SharedInfo->winstrument[i]; nsearches += winstrument->nsearches; + ntabletuplefetches += winstrument->ntabletuplefetches; } } + if (nodeTag(plan) == T_IndexOnlyScan) + ExplainPropertyUInteger("Heap Fetches", NULL, ntabletuplefetches, es); + ExplainPropertyUInteger("Index Searches", NULL, nsearches, es); } diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 609e2c1d77e..e0fc96558d9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -3031,10 +3031,10 @@ find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator, } /* 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); 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 */ if (scan->xs_recheck && !identity_key_equal(chgcxt, locator, retrieved)) diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c index 1e3a86aca1b..d0a714a42f2 100644 --- a/src/backend/executor/execIndexing.c +++ b/src/backend/executor/execIndexing.c @@ -823,12 +823,13 @@ check_exclusion_or_unique_constraint(Relation heap, Relation index, retry: conflict = false; found_self = false; - index_scan = index_beginscan(heap, index, + index_scan = index_beginscan(heap, index, false, &DirtySnapshot, NULL, indnkeyatts, 0, SO_NONE); 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; XLTW_Oper reason_wait; diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c index b2ca5cbf117..fd9efd94737 100644 --- a/src/backend/executor/execReplication.c +++ b/src/backend/executor/execReplication.c @@ -205,7 +205,7 @@ RelationFindReplTupleByIndex(Relation rel, Oid idxoid, skey_attoff = build_replindex_scan_key(skey, rel, idxrel, searchslot); /* Start an index scan. */ - scan = index_beginscan(rel, idxrel, + scan = index_beginscan(rel, idxrel, false, &snap, NULL, skey_attoff, 0, SO_NONE); retry: @@ -214,7 +214,7 @@ retry: index_rescan(scan, skey, skey_attoff, NULL, 0); /* 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 @@ -669,13 +669,13 @@ RelationFindDeletedTupleInfoByIndex(Relation rel, Oid idxoid, * not yet committed or those just committed prior to the scan are * 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); index_rescan(scan, skey, skey_attoff, NULL, 0); /* 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 diff --git a/src/backend/executor/nodeBitmapIndexscan.c b/src/backend/executor/nodeBitmapIndexscan.c index 7978514e1bc..90b010f9b71 100644 --- a/src/backend/executor/nodeBitmapIndexscan.c +++ b/src/backend/executor/nodeBitmapIndexscan.c @@ -204,6 +204,7 @@ ExecEndBitmapIndexScan(BitmapIndexScanState *node) * which will have a new BitmapIndexScanState and zeroed stats. */ winstrument->nsearches += node->biss_Instrument->nsearches; + Assert(node->biss_Instrument->ntabletuplefetches == 0); } /* diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c index 856df2ba51d..b1009027ac8 100644 --- a/src/backend/executor/nodeIndexonlyscan.c +++ b/src/backend/executor/nodeIndexonlyscan.c @@ -31,26 +31,17 @@ #include "postgres.h" #include "access/genam.h" -#include "access/htup_details.h" #include "access/relscan.h" #include "access/tableam.h" -#include "access/tupdesc.h" -#include "access/visibilitymap.h" -#include "catalog/pg_type.h" #include "executor/executor.h" #include "executor/instrument.h" #include "executor/nodeIndexonlyscan.h" #include "executor/nodeIndexscan.h" #include "miscadmin.h" -#include "storage/bufmgr.h" -#include "storage/predicate.h" -#include "utils/builtins.h" #include "utils/rel.h" static TupleTableSlot *IndexOnlyNext(IndexOnlyScanState *node); -static void StoreIndexTuple(IndexOnlyScanState *node, TupleTableSlot *slot, - IndexScanDesc scandesc); /* ---------------------------------------------------------------- @@ -67,7 +58,6 @@ IndexOnlyNext(IndexOnlyScanState *node) ScanDirection direction; IndexScanDesc scandesc; TupleTableSlot *slot; - ItemPointer tid; /* * extract necessary information from index scan node @@ -93,6 +83,7 @@ IndexOnlyNext(IndexOnlyScanState *node) */ scandesc = index_beginscan(node->ss.ss_currentRelation, node->ioss_RelationDesc, + true, estate->es_snapshot, node->ioss_Instrument, node->ioss_NumScanKeys, @@ -101,11 +92,7 @@ IndexOnlyNext(IndexOnlyScanState *node) SO_HINT_REL_READ_ONLY : SO_NONE); node->ioss_ScanDesc = scandesc; - - - /* Set it up for index-only scan */ - node->ioss_ScanDesc->xs_want_itup = true; - node->ioss_VMBuffer = InvalidBuffer; + Assert(node->ioss_ScanDesc->xs_want_itup); /* * 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. */ - while ((tid = index_getnext_tid(scandesc, direction)) != NULL) + while (table_index_getnext_slot(scandesc, direction, slot)) { - bool tuple_from_heap = false; - 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. */ @@ -202,16 +139,6 @@ IndexOnlyNext(IndexOnlyScanState *node) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 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; } @@ -222,85 +149,6 @@ IndexOnlyNext(IndexOnlyScanState *node) 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 * @@ -394,13 +242,6 @@ ExecEndIndexOnlyScan(IndexOnlyScanState *node) indexRelationDesc = node->ioss_RelationDesc; 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 * 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. */ 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; LOCKMODE lockmode; TupleDesc tupDesc; - int indnkeyatts; - int namecount; /* * 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 * planner. We use this, rather than the index's physical tuple * descriptor, because the latter contains storage column types not the - * types of the original datums. (It's the AM's responsibility to return - * suitable data anyway.) + * types of the original datums. (It's the index AM's responsibility to + * return suitable data anyway. The table AM fills our slot with that + * returned data.) */ tupDesc = ExecTypeFromTL(node->indextlist); ExecInitScanTupleSlot(estate, &indexstate->ss, tupDesc, &TTSOpsVirtual, 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 * 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_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. */ @@ -752,14 +542,14 @@ ExecIndexOnlyScanInitializeDSM(IndexOnlyScanState *node, node->ioss_ScanDesc = index_beginscan_parallel(node->ss.ss_currentRelation, node->ioss_RelationDesc, + true, node->ioss_Instrument, node->ioss_NumScanKeys, node->ioss_NumOrderByKeys, piscan, ScanRelIsReadOnly(&node->ss) ? SO_HINT_REL_READ_ONLY : SO_NONE); - node->ioss_ScanDesc->xs_want_itup = true; - node->ioss_VMBuffer = InvalidBuffer; + Assert(node->ioss_ScanDesc->xs_want_itup); /* * 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 = index_beginscan_parallel(node->ss.ss_currentRelation, node->ioss_RelationDesc, + true, node->ioss_Instrument, node->ioss_NumScanKeys, node->ioss_NumOrderByKeys, piscan, ScanRelIsReadOnly(&node->ss) ? 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 diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c index 6566150cddc..129d005f187 100644 --- a/src/backend/executor/nodeIndexscan.c +++ b/src/backend/executor/nodeIndexscan.c @@ -110,6 +110,7 @@ IndexNext(IndexScanState *node) */ scandesc = index_beginscan(node->ss.ss_currentRelation, node->iss_RelationDesc, + false, estate->es_snapshot, node->iss_Instrument, node->iss_NumScanKeys, @@ -132,7 +133,7 @@ IndexNext(IndexScanState *node) /* * 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(); @@ -208,6 +209,7 @@ IndexNextWithReorder(IndexScanState *node) */ scandesc = index_beginscan(node->ss.ss_currentRelation, node->iss_RelationDesc, + false, estate->es_snapshot, node->iss_Instrument, node->iss_NumScanKeys, @@ -266,7 +268,7 @@ IndexNextWithReorder(IndexScanState *node) * Fetch next tuple from the index. */ 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 @@ -818,6 +820,7 @@ ExecEndIndexScan(IndexScanState *node) * which will have a new IndexOnlyScanState and zeroed stats. */ winstrument->nsearches += node->iss_Instrument->nsearches; + Assert(node->iss_Instrument->ntabletuplefetches == 0); } /* @@ -1696,6 +1699,7 @@ ExecIndexScanInitializeDSM(IndexScanState *node, node->iss_ScanDesc = index_beginscan_parallel(node->ss.ss_currentRelation, node->iss_RelationDesc, + false, node->iss_Instrument, node->iss_NumScanKeys, node->iss_NumOrderByKeys, @@ -1744,6 +1748,7 @@ ExecIndexScanInitializeWorker(IndexScanState *node, node->iss_ScanDesc = index_beginscan_parallel(node->ss.ss_currentRelation, node->iss_RelationDesc, + false, node->iss_Instrument, node->iss_NumScanKeys, node->iss_NumOrderByKeys, diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index c46f789ed14..43f40172b67 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -2947,7 +2947,7 @@ ri_FastPathCheck(RI_ConstraintInfo *riinfo, * btree this has no functional consequence, but it keeps the ordering * correct for out-of-tree access methods. */ - scandesc = index_beginscan(pk_rel, idx_rel, + scandesc = index_beginscan(pk_rel, idx_rel, false, snapshot, NULL, riinfo->nkeys, 0, 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 * 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); 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 * 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; bool found_null; @@ -3397,7 +3397,7 @@ ri_FastPathProbeOne(Relation pk_rel, Relation idx_rel, 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; diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index e27ec9e5c25..5ee377595bd 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -102,7 +102,6 @@ #include "access/gin.h" #include "access/table.h" #include "access/tableam.h" -#include "access/visibilitymap.h" #include "catalog/pg_collation.h" #include "catalog/pg_operator.h" #include "catalog/pg_statistic.h" @@ -122,7 +121,6 @@ #include "parser/parsetree.h" #include "rewrite/rewriteManip.h" #include "statistics/statistics.h" -#include "storage/bufmgr.h" #include "utils/acl.h" #include "utils/array.h" #include "utils/builtins.h" @@ -268,7 +266,7 @@ static bool get_actual_variable_endpoint(Relation heapRel, ScanKey scankeys, int16 typLen, bool typByVal, - TupleTableSlot *tableslot, + TupleTableSlot *slot, MemoryContext outercontext, Datum *endpointDatum); 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); /* build some stuff needed for indexscan execution */ - slot = table_slot_create(heapRel, NULL); + slot = MakeSingleTupleTableSlot(RelationGetDescr(indexRel), + &TTSOpsVirtual); get_typlenbyval(vardata->atttype, &typLen, &typByVal); /* 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. * typLen/typByVal describe the datatype of the index's first column. - * tableslot is a slot suitable to hold table tuples, in case we need - * to probe the heap. + * slot is a virtual slot to receive each index tuple's values. * (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.) * @@ -7229,19 +7227,15 @@ get_actual_variable_endpoint(Relation heapRel, ScanKey scankeys, int16 typLen, bool typByVal, - TupleTableSlot *tableslot, + TupleTableSlot *slot, MemoryContext outercontext, Datum *endpointDatum) { bool have_data = false; SnapshotData SnapshotNonVacuumable; IndexScanDesc index_scan; - Buffer vmbuffer = InvalidBuffer; - BlockNumber last_heap_block = InvalidBlockNumber; - int n_visited_heap_pages = 0; - ItemPointer tid; - Datum values[INDEX_MAX_KEYS]; - bool isnull[INDEX_MAX_KEYS]; + Datum val; + bool isnull; 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 * pages. When we fail for that reason, the caller will end up using * 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, GlobalVisTestFor(heapRel)); - index_scan = index_beginscan(heapRel, indexRel, + index_scan = index_beginscan(heapRel, indexRel, true, &SnapshotNonVacuumable, NULL, 1, 0, SO_NONE); - /* Set it up for index-only scan */ - index_scan->xs_want_itup = true; + Assert(index_scan->xs_want_itup); + index_scan->xs_visited_pages_limit = VISITED_PAGES_LIMIT; index_rescan(index_scan, scankeys, 1, NULL, 0); /* 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. */ if (index_scan->xs_recheck) break; - /* OK to deconstruct the index tuple */ - index_deform_tuple(index_scan->xs_itup, - index_scan->xs_itupdesc, - values, isnull); + /* Read the index's first column value out of the slot */ + val = slot_getattr(slot, 1, &isnull); /* Shouldn't have got a null, but be careful */ - if (isnull[0]) + if (isnull) elog(ERROR, "found unexpected null value in index \"%s\"", RelationGetRelationName(indexRel)); /* Copy the index column value out to caller's context */ oldcontext = MemoryContextSwitchTo(outercontext); - *endpointDatum = datumCopy(values[0], typByVal, typLen); + *endpointDatum = datumCopy(val, typByVal, typLen); MemoryContextSwitchTo(oldcontext); have_data = true; break; } - if (vmbuffer != InvalidBuffer) - ReleaseBuffer(vmbuffer); + ExecClearTuple(slot); /* defensive, slot can be reused by later scan */ index_endscan(index_scan); return have_data; diff --git a/src/include/access/genam.h b/src/include/access/genam.h index 68bfe405db3..72b256ecf43 100644 --- a/src/include/access/genam.h +++ b/src/include/access/genam.h @@ -156,6 +156,7 @@ extern void index_insert_cleanup(Relation indexRelation, extern IndexScanDesc index_beginscan(Relation heapRelation, Relation indexRelation, + bool index_only_scan, Snapshot snapshot, IndexScanInstrumentation *instrument, int nkeys, int norderbys, @@ -178,15 +179,11 @@ extern void index_parallelscan_initialize(Relation heapRelation, extern void index_parallelrescan(IndexScanDesc scan); extern IndexScanDesc index_beginscan_parallel(Relation heaprel, Relation indexrel, + bool index_only_scan, IndexScanInstrumentation *instrument, int nkeys, int norderbys, ParallelIndexScanDesc pscan, 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 IndexBulkDeleteResult *index_bulk_delete(IndexVacuumInfo *info, diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 5176478c295..9e35961fb9e 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -115,12 +115,10 @@ typedef struct BitmapHeapScanDescData 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 * xs_blk is not InvalidBlockNumber, we hold a pin in xs_cbuf. @@ -128,9 +126,12 @@ typedef struct IndexFetchHeapData Buffer xs_cbuf; BlockNumber xs_blk; - /* Current heap block's corresponding page in the visibility map */ - Buffer xs_vmbuffer; -} IndexFetchHeapData; + Buffer xs_vmbuffer; /* visibility map buffer */ + + bool xs_readonly; /* scan is read-only? */ + + uint16 xs_blkswitch_count; /* number of heap blocks fetched */ +} IndexScanHeapData; /* Result codes for HeapTupleSatisfiesVacuum */ typedef enum @@ -430,16 +431,14 @@ extern TransactionId heap_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstate); /* in heap/heapam_indexscan.c */ -extern IndexFetchTableData *heapam_index_fetch_begin(Relation rel, uint32 flags); -extern void heapam_index_fetch_reset(IndexFetchTableData *scan); -extern void heapam_index_fetch_end(IndexFetchTableData *scan); +extern bool heapam_fetch_tid(Relation rel, ItemPointer tid, Snapshot snapshot, + bool *all_dead); +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, Buffer buffer, Snapshot snapshot, HeapTuple heapTuple, bool *all_dead, bool first_call); -extern bool heapam_index_fetch_tuple(struct IndexFetchTableData *scan, - ItemPointer tid, Snapshot snapshot, - TupleTableSlot *slot, bool *heap_continue, - bool *all_dead); /* in heap/pruneheap.c */ extern void heap_page_prune_opt(Relation relation, Buffer buffer, diff --git a/src/include/access/relscan.h b/src/include/access/relscan.h index 2305d0159f3..d7614104730 100644 --- a/src/include/access/relscan.h +++ b/src/include/access/relscan.h @@ -16,6 +16,7 @@ #include "access/htup_details.h" #include "access/itup.h" +#include "access/sdir.h" #include "nodes/tidbitmap.h" #include "port/atomics.h" #include "storage/relfilelocator.h" @@ -24,6 +25,7 @@ struct ParallelTableScanDescData; struct TableScanInstrumentation; +struct TupleTableSlot; /* * Generic descriptor for table scans. This is the base-class for table scans, @@ -118,22 +120,6 @@ typedef struct ParallelBlockTableScanWorkerData } ParallelBlockTableScanWorkerData; 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; /* @@ -170,10 +156,10 @@ typedef struct IndexScanDescData struct IndexScanInstrumentation *instrument; /* - * In an index-only scan, a successful amgettuple call must fill either - * xs_itup (and xs_itupdesc) or xs_hitup (and xs_hitupdesc) to provide the - * data returned by the scan. It can fill both, in which case the heap - * format will be used. + * In an index-only scan, the index AM fills either xs_itup or xs_hitup + * with the data to be returned by the scan (it can fill both, in which + * case the heap format is used). The table AM consumes these to fill the + * caller's slot during table_index_getnext_slot. */ IndexTuple xs_itup; /* index tuple returned by AM */ struct TupleDescData *xs_itupdesc; /* rowtype descriptor of xs_itup */ @@ -183,10 +169,19 @@ typedef struct IndexScanDescData ItemPointerData xs_heaptid; /* result */ bool xs_heap_continue; /* T if must keep walking, potential * further results */ - IndexFetchTableData *xs_heapfetch; - 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 * expressions of the last returned tuple, according to the index. If @@ -198,6 +193,23 @@ typedef struct IndexScanDescData bool *xs_orderbynulls; 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 */ struct ParallelIndexScanDescData *parallel_scan; } IndexScanDescData; @@ -211,8 +223,6 @@ typedef struct ParallelIndexScanDescData char ps_snapshot_data[FLEXIBLE_ARRAY_MEMBER]; } ParallelIndexScanDescData; -struct TupleTableSlot; - /* Struct for storage-or-index scans of system tables */ typedef struct SysScanDescData { diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index ebec31a22e4..ea3f2a6be99 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -38,6 +38,7 @@ typedef struct BulkInsertStateData BulkInsertStateData; typedef struct IndexInfo IndexInfo; typedef struct SampleScanState SampleScanState; typedef struct ScanKeyData ScanKeyData; +typedef struct IndexScanDescData *IndexScanDesc; typedef struct ValidateIndexState ValidateIndexState; typedef struct VacuumParams VacuumParams; @@ -446,61 +447,60 @@ typedef struct TableAmRoutine */ /* - * Prepare to fetch tuples from the relation, as needed when fetching - * tuples for an index scan. The callback has to return an - * IndexFetchTableData, which the AM will typically embed in a larger - * structure with additional information. + * Prepare for an index scan of the table. The callback stores its own + * private scan state in the index scan descriptor's xs_table_opaque field + * (an opaque pointer). * * flags is a bitmask of ScanOptions affecting underlying table scan * 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 - * resources held in IndexFetchTableData. + * Inform the table AM that there's to be either a rescan or a restore of + * 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); - - /* - * 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); - + void (*index_scan_end) (IndexScanDesc scan); /* ------------------------------------------------------------------------ * 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 * 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 - * for an index scan. + * Prepare an index scan descriptor by storing table AM private state in + * 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. - * - * Tuples for an index scan can then be fetched via table_index_fetch_tuple(). */ -static inline IndexFetchTableData * -table_index_fetch_begin(Relation rel, uint32 flags) +static inline void +table_index_scan_begin(IndexScanDesc scan, uint32 flags) { Assert((flags & SO_INTERNAL_FLAGS) == 0); @@ -1255,74 +1254,61 @@ table_index_fetch_begin(Relation rel, uint32 flags) if (unlikely(TransactionIdIsValid(CheckXidAlive) && !bsysscan)) 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 - * held in IndexFetchTableData. + * Inform the table AM that there's to be either a rescan or a restore of a + * marked position */ 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 -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 - * a visibility test according to `snapshot`. If a tuple was found and passed - * the visibility test, returns true, false otherwise. Note that *tid may be - * modified when we return true (see later remarks on multiple row versions - * reachable via a single index entry). + * Return the next tuple from an index scan through `slot`, scanning in the + * specified direction. Returns true if a tuple satisfying the scan keys and + * the snapshot was found, false otherwise. * - * *call_again needs to be false on the first call to table_index_fetch_tuple() for - * a tid. If there potentially is another tuple matching the tid, *call_again - * will be set to true, signaling that table_index_fetch_tuple() should be called - * again for the same tid. + * Dispatches through scan->xs_getnext_slot, which is resolved once by the + * table AM's index_scan_begin callback. * - * *all_dead, if all_dead is not NULL, will be set to true by - * table_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. + * On success, resources (like buffer pins) are likely to be held, and will be + * released by a future table_index_getnext_slot or table_index_scan_end call. * - * The difference between this function and table_tuple_fetch_row_version() - * is that this function returns the currently visible version of a row if - * the AM supports storing multiple row versions reachable via a single index - * entry (like heap's HOT). Whereas table_tuple_fetch_row_version() only - * evaluates the tuple exactly at `tid`. Outside of index entry ->table tuple - * lookups, table_tuple_fetch_row_version() is what's usually needed. + * 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. Similarly, + * for ordered scans, the caller must check scan->xs_recheckorderby and + * recheck the ORDER BY expressions for itself. */ static inline bool -table_index_fetch_tuple(struct IndexFetchTableData *scan, - ItemPointer tid, - Snapshot snapshot, - TupleTableSlot *slot, - bool *call_again, bool *all_dead) +table_index_getnext_slot(IndexScanDesc scan, ScanDirection direction, + TupleTableSlot *slot) { - return scan->rel->rd_tableam->index_fetch_tuple(scan, tid, snapshot, - slot, call_again, - all_dead); -} + /* See index_scan_begin for an explanation of index-only scan slot type */ + Assert(!scan->xs_want_itup || TTS_IS_VIRTUAL(slot)); + Assert(scan->xs_table_opaque); -/* - * 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); + return scan->xs_getnext_slot(scan, direction, slot); +} /* ------------------------------------------------------------------------ @@ -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 * `snapshot`. If a tuple was found and passed the visibility test, returns * true, false otherwise. * - * See table_index_fetch_tuple's comment about what the difference between - * these functions is. It is correct to use this function outside of index - * entry->table tuple lookups. + * Unlike table_fetch_tid(), only the tuple at `tid` itself is tested; no + * version chain is followed. */ static inline bool table_tuple_fetch_row_version(Relation rel, diff --git a/src/include/access/tableam_indexscan.h b/src/include/access/tableam_indexscan.h new file mode 100644 index 00000000000..87a1964c668 --- /dev/null +++ b/src/include/access/tableam_indexscan.h @@ -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 */ diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat index b84c2bb7a8c..76bb4440223 100644 --- a/src/include/catalog/pg_opclass.dat +++ b/src/include/catalog/pg_opclass.dat @@ -94,8 +94,8 @@ # 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 # that we pad the cstring out with the full NAMEDATALEN bytes when performing -# index-only scans. See corresponding hacks in ExecInitIndexOnlyScan() and -# StoreIndexTuple(). +# index-only scans. See corresponding hacks in index_beginscan() and +# tableam_index_fill_ios_slot(). { opcmethod => 'btree', opcname => 'name_ops', opcfamily => 'btree/text_ops', opcintype => 'name', opckeytype => 'cstring' }, diff --git a/src/include/executor/instrument_node.h b/src/include/executor/instrument_node.h index 4076990408e..41a7d33f19c 100644 --- a/src/include/executor/instrument_node.h +++ b/src/include/executor/instrument_node.h @@ -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 { /* Index search count (incremented with pgstat_count_index_scan call) */ uint64 nsearches; + + /* Table tuples fetched count (incremented during index-only scans) */ + uint64 ntabletuplefetches; } IndexScanInstrumentation; /* diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 642527e1191..91bb0bd2e13 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1769,11 +1769,7 @@ typedef struct IndexScanState * ScanDesc index scan descriptor * Instrument local index scan instrumentation * 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 - * NameCStringAttNums attnums of name typed columns to pad to NAMEDATALEN - * NameCStringCount number of elements in the NameCStringAttNums array * ---------------- */ typedef struct IndexOnlyScanState @@ -1792,11 +1788,7 @@ typedef struct IndexOnlyScanState struct IndexScanDescData *ioss_ScanDesc; IndexScanInstrumentation *ioss_Instrument; SharedIndexScanInstrumentation *ioss_SharedInfo; - TupleTableSlot *ioss_TableSlot; - Buffer ioss_VMBuffer; Size ioss_PscanLen; - AttrNumber *ioss_NameCStringAttNums; - int ioss_NameCStringCount; } IndexOnlyScanState; /* ---------------- diff --git a/src/test/modules/index/test_indexscan.c b/src/test/modules/index/test_indexscan.c index 55f50b0615b..14bf71ade24 100644 --- a/src/test/modules/index/test_indexscan.c +++ b/src/test/modules/index/test_indexscan.c @@ -115,11 +115,11 @@ index_scan_tids(PG_FUNCTION_ARGS) 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); 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; Datum values[1]; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 16d17d8f55b..0dc817cc2b8 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1312,8 +1312,6 @@ IndexDeleteCounts IndexDeletePrefetchState IndexDoCheckCallback IndexElem -IndexFetchHeapData -IndexFetchTableData IndexInfo IndexList IndexOnlyScan @@ -1325,6 +1323,7 @@ IndexRuntimeKeyInfo IndexScan IndexScanDesc IndexScanDescData +IndexScanHeapData IndexScanInstrumentation IndexScanState IndexStateFlagsAction