Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * heapam_indexscan.c
4 : : * heap table plain index scan and index-only scan code
5 : : *
6 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : *
10 : : * IDENTIFICATION
11 : : * src/backend/access/heap/heapam_indexscan.c
12 : : *
13 : : *-------------------------------------------------------------------------
14 : : */
15 : : #include "postgres.h"
16 : :
17 : : #include "access/amapi.h"
18 : : #include "access/heapam.h"
19 : : #include "access/relscan.h"
20 : : #include "access/tableam_indexscan.h"
21 : : #include "access/visibilitymap.h"
22 : : #include "pgstat.h"
23 : : #include "storage/predicate.h"
24 : :
25 : :
26 : : static bool heapam_index_plain_tuple_getnext_slot(IndexScanDesc scan,
27 : : ScanDirection direction,
28 : : TupleTableSlot *slot);
29 : : static bool heapam_index_only_tuple_getnext_slot(IndexScanDesc scan,
30 : : ScanDirection direction,
31 : : TupleTableSlot *slot);
32 : : static pg_always_inline bool heapam_index_getnext_slot(IndexScanDesc scan,
33 : : ScanDirection direction,
34 : : TupleTableSlot *slot,
35 : : bool index_only);
36 : : static pg_always_inline bool heapam_index_heap_fetch(IndexScanDesc scan,
37 : : IndexScanHeapData *hscan,
38 : : TupleTableSlot *slot,
39 : : bool index_only);
40 : : static pg_noinline bool heapam_index_only_heap_fetch(IndexScanDesc scan);
41 : : static pg_noinline void heapam_index_kill_item(IndexScanDesc scan);
42 : : static inline bool heapam_index_visited_pages_exceeded(IndexScanDesc scan);
43 : :
44 : : /*
45 : : * TID-based lookup used by constraint enforcement code (e.g., unique index
46 : : * enforcement).
47 : : *
48 : : * This isn't actually used by index scans, but this is as good a place for it
49 : : * as anywhere else.
50 : : */
51 : : bool
5 pg@bowt.ie 52 :GNC 7610081 : heapam_fetch_tid(Relation rel, ItemPointer tid, Snapshot snapshot,
53 : : bool *all_dead)
54 : : {
55 : : HeapTupleData heapTuple;
56 : : Buffer buf;
57 : : bool found;
58 : :
59 : 7610081 : buf = ReadBuffer(rel, ItemPointerGetBlockNumber(tid));
60 : 7610081 : LockBuffer(buf, BUFFER_LOCK_SHARE);
61 : 7610081 : found = heap_hot_search_buffer(tid, rel, buf, snapshot, &heapTuple,
62 : : all_dead, true);
63 : 7610081 : UnlockReleaseBuffer(buf);
64 : :
65 : 7610081 : return found;
66 : : }
67 : :
68 : : /* ------------------------------------------------------------------------
69 : : * Index Scan Callbacks for heap AM
70 : : * ------------------------------------------------------------------------
71 : : */
72 : :
73 : : void
74 : 8664744 : heapam_index_scan_begin(IndexScanDesc scan, uint32 flags)
75 : : {
76 : 8664744 : IndexScanHeapData *hscan = palloc0_object(IndexScanHeapData);
77 : :
169 pg@bowt.ie 78 :CBC 8664744 : hscan->xs_cbuf = InvalidBuffer;
79 : 8664744 : hscan->xs_blk = InvalidBlockNumber;
80 : 8664744 : hscan->xs_vmbuffer = InvalidBuffer;
81 : :
82 : : /* Remember if scan is read-only */
5 pg@bowt.ie 83 :GNC 8664744 : hscan->xs_readonly = (flags & SO_HINT_REL_READ_ONLY) != 0;
84 : :
85 : : /* Resolve which xs_getnext_slot implementation to use for this scan */
86 [ + + ]: 8664744 : if (scan->xs_want_itup)
87 : 90753 : scan->xs_getnext_slot = heapam_index_only_tuple_getnext_slot;
88 : : else
89 : 8573991 : scan->xs_getnext_slot = heapam_index_plain_tuple_getnext_slot;
90 : :
91 : : /* Expose heapam's private scan state through the scan's opaque pointer */
92 : 8664744 : scan->xs_table_opaque = hscan;
169 pg@bowt.ie 93 :GIC 8664744 : }
94 : :
95 : : void
5 pg@bowt.ie 96 :GNC 9136816 : heapam_index_scan_reset(IndexScanDesc scan)
97 : : {
98 : 9136816 : IndexScanHeapData *hscan = (IndexScanHeapData *) scan->xs_table_opaque;
99 : :
100 : : /* Heap fetches from the last rescan don't count towards this limit */
101 : 9136816 : hscan->xs_blkswitch_count = 0;
102 : :
103 : : /*
104 : : * Deliberately avoid dropping pins now held in xs_cbuf and xs_vmbuffer.
105 : : * This saves cycles during certain tight nested loop joins (it can avoid
106 : : * repeated pinning and unpinning of the same buffer across rescans).
107 : : */
169 pg@bowt.ie 108 :CBC 9136816 : }
109 : :
110 : : void
5 pg@bowt.ie 111 :GNC 8663538 : heapam_index_scan_end(IndexScanDesc scan)
112 : : {
113 : 8663538 : IndexScanHeapData *hscan = (IndexScanHeapData *) scan->xs_table_opaque;
114 : :
115 : : /* drop pin if there's a pinned heap page */
169 pg@bowt.ie 116 [ + + ]:CBC 8663538 : if (BufferIsValid(hscan->xs_cbuf))
117 : 5934696 : ReleaseBuffer(hscan->xs_cbuf);
118 : :
119 : : /* drop pin if there's a pinned visibility map page */
120 [ + + ]: 8663538 : if (BufferIsValid(hscan->xs_vmbuffer))
121 : 139059 : ReleaseBuffer(hscan->xs_vmbuffer);
122 : :
123 : 8663538 : pfree(hscan);
124 : 8663538 : }
125 : :
126 : : /*
127 : : * heap_hot_search_buffer - search HOT chain for tuple satisfying snapshot
128 : : *
129 : : * On entry, *tid is the TID of a tuple (either a simple tuple, or the root
130 : : * of a HOT chain), and buffer is the buffer holding this tuple. We search
131 : : * for the first chain member satisfying the given snapshot. If one is
132 : : * found, we update *tid to reference that tuple's offset number, and
133 : : * return true. If no match, return false without modifying *tid.
134 : : *
135 : : * heapTuple is a caller-supplied buffer. When a match is found, we return
136 : : * the tuple here, in addition to updating *tid. If no match is found, the
137 : : * contents of this buffer on return are undefined.
138 : : *
139 : : * If all_dead is not NULL, we check non-visible tuples to see if they are
140 : : * globally dead; *all_dead is set true if all members of the HOT chain
141 : : * are vacuumable, false if not.
142 : : *
143 : : * Unlike heap_fetch, the caller must already have pin and (at least) share
144 : : * lock on the buffer; it is still pinned/locked at exit.
145 : : */
146 : : bool
147 : 26636961 : heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
148 : : Snapshot snapshot, HeapTuple heapTuple,
149 : : bool *all_dead, bool first_call)
150 : : {
151 : 26636961 : Page page = BufferGetPage(buffer);
152 : 26636961 : TransactionId prev_xmax = InvalidTransactionId;
153 : : BlockNumber blkno;
154 : : OffsetNumber offnum;
155 : : bool at_chain_start;
156 : : bool valid;
157 : : bool skip;
158 : 26636961 : GlobalVisState *vistest = NULL;
159 : :
160 : : /* If this is not the first call, previous call returned a (live!) tuple */
161 [ + + ]: 26636961 : if (all_dead)
162 : 22671415 : *all_dead = first_call;
163 : :
164 : 26636961 : blkno = ItemPointerGetBlockNumber(tid);
165 : 26636961 : offnum = ItemPointerGetOffsetNumber(tid);
166 : 26636961 : at_chain_start = first_call;
167 : 26636961 : skip = !first_call;
168 : :
169 : : /* XXX: we should assert that a snapshot is pushed or registered */
170 [ - + ]: 26636961 : Assert(TransactionIdIsValid(RecentXmin));
171 [ + - ]: 26636961 : Assert(BufferGetBlockNumber(buffer) == blkno);
172 : :
173 : : /* Scan through possible multiple members of HOT-chain */
174 : : for (;;)
175 : 1811762 : {
176 : : ItemId lp;
177 : :
178 : : /* check for bogus TID */
179 [ + - + - ]: 28448723 : if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
180 : : break;
181 : :
182 : 28448723 : lp = PageGetItemId(page, offnum);
183 : :
184 : : /* check for unused, dead, or redirected items */
185 [ + + ]: 28448723 : if (!ItemIdIsNormal(lp))
186 : : {
187 : : /* We should only see a redirect at start of chain */
188 [ + + + - ]: 831157 : if (ItemIdIsRedirected(lp) && at_chain_start)
189 : : {
190 : : /* Follow the redirect */
191 : 468498 : offnum = ItemIdGetRedirect(lp);
192 : 468498 : at_chain_start = false;
193 : 468498 : continue;
194 : : }
195 : : /* else must be end of chain */
196 : 362659 : break;
197 : : }
198 : :
199 : : /*
200 : : * Update heapTuple to point to the element of the HOT chain we're
201 : : * currently investigating. Having t_self set correctly is important
202 : : * because the SSI checks and the *Satisfies routine for historical
203 : : * MVCC snapshots need the correct tid to decide about the visibility.
204 : : */
205 : 27617566 : heapTuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
206 : 27617566 : heapTuple->t_len = ItemIdGetLength(lp);
207 : 27617566 : heapTuple->t_tableOid = RelationGetRelid(relation);
208 : 27617566 : ItemPointerSet(&heapTuple->t_self, blkno, offnum);
209 : :
210 : : /*
211 : : * Shouldn't see a HEAP_ONLY tuple at chain start.
212 : : */
213 [ + + - + ]: 27617566 : if (at_chain_start && HeapTupleIsHeapOnly(heapTuple))
169 pg@bowt.ie 214 :UBC 0 : break;
215 : :
216 : : /*
217 : : * The xmin should match the previous xmax value, else chain is
218 : : * broken.
219 : : */
169 pg@bowt.ie 220 [ + + - + ]:CBC 28960830 : if (TransactionIdIsValid(prev_xmax) &&
221 : 1343264 : !TransactionIdEquals(prev_xmax,
222 : : HeapTupleHeaderGetXmin(heapTuple->t_data)))
169 pg@bowt.ie 223 :UBC 0 : break;
224 : :
225 : : /*
226 : : * When first_call is true (and thus, skip is initially false) we'll
227 : : * return the first tuple we find. But on later passes, heapTuple
228 : : * will initially be pointing to the tuple we returned last time.
229 : : * Returning it again would be incorrect (and would loop forever), so
230 : : * we skip it and return the next match we find.
231 : : */
169 pg@bowt.ie 232 [ + + ]:CBC 27617566 : if (!skip)
233 : : {
234 : : /* If it's visible per the snapshot, we must return it */
235 : 27515650 : valid = HeapTupleSatisfiesVisibility(heapTuple, snapshot, buffer);
236 : 27515650 : HeapCheckForSerializableConflictOut(valid, relation, heapTuple,
237 : : buffer, snapshot);
238 : :
239 [ + + ]: 27515645 : if (valid)
240 : : {
241 : 17964082 : ItemPointerSetOffsetNumber(tid, offnum);
242 : 17964082 : PredicateLockTID(relation, &heapTuple->t_self, snapshot,
243 : 17964082 : HeapTupleHeaderGetXmin(heapTuple->t_data));
244 [ + + ]: 17964082 : if (all_dead)
245 : 14346856 : *all_dead = false;
246 : 17964082 : return true;
247 : : }
248 : : }
249 : 9653479 : skip = false;
250 : :
251 : : /*
252 : : * If we can't see it, maybe no one else can either. At caller
253 : : * request, check whether all chain members are dead to all
254 : : * transactions.
255 : : *
256 : : * Note: if you change the criterion here for what is "dead", fix the
257 : : * planner's get_actual_variable_range() function to match.
258 : : */
259 [ + + + + ]: 9653479 : if (all_dead && *all_dead)
260 : : {
261 [ + + ]: 8605023 : if (!vistest)
262 : 8443210 : vistest = GlobalVisTestFor(relation);
263 : :
264 [ + + ]: 8605023 : if (!HeapTupleIsSurelyDead(heapTuple, vistest))
265 : 8136507 : *all_dead = false;
266 : : }
267 : :
268 : : /*
269 : : * Check to see if HOT chain continues past this tuple; if so fetch
270 : : * the next offnum and loop around.
271 : : */
272 [ + + ]: 9653479 : if (HeapTupleIsHotUpdated(heapTuple))
273 : : {
274 [ - + ]: 1343264 : Assert(ItemPointerGetBlockNumber(&heapTuple->t_data->t_ctid) ==
275 : : blkno);
276 : 1343264 : offnum = ItemPointerGetOffsetNumber(&heapTuple->t_data->t_ctid);
277 : 1343264 : at_chain_start = false;
278 : 1343264 : prev_xmax = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
279 : : }
280 : : else
281 : 8310215 : break; /* end of chain */
282 : :
283 : : }
284 : :
285 : 8672874 : return false;
286 : : }
287 : :
288 : : /* xs_getnext_slot callback: amgettuple, plain index scan */
289 : : static bool
5 pg@bowt.ie 290 :GNC 18127223 : heapam_index_plain_tuple_getnext_slot(IndexScanDesc scan,
291 : : ScanDirection direction,
292 : : TupleTableSlot *slot)
293 : : {
294 [ - + ]: 18127223 : Assert(!scan->xs_want_itup);
295 [ - + ]: 18127223 : Assert(scan->indexRelation->rd_indam->amgettuple != NULL);
296 : :
297 : 18127223 : return heapam_index_getnext_slot(scan, direction, slot, false);
298 : : }
299 : :
300 : : /* xs_getnext_slot callback: amgettuple, index-only scan */
301 : : static bool
302 : 3950887 : heapam_index_only_tuple_getnext_slot(IndexScanDesc scan,
303 : : ScanDirection direction,
304 : : TupleTableSlot *slot)
305 : : {
306 [ - + ]: 3950887 : Assert(scan->xs_want_itup);
307 [ - + ]: 3950887 : Assert(scan->indexRelation->rd_indam->amgettuple != NULL);
308 : :
309 : 3950887 : return heapam_index_getnext_slot(scan, direction, slot, true);
310 : : }
311 : :
312 : : /*
313 : : * Common implementation for both heapam_index_*_getnext_slot variants.
314 : : *
315 : : * The result is true if a tuple satisfying the scan keys and the snapshot was
316 : : * found, false otherwise. This is per the table_index_getnext_slot
317 : : * interface.
318 : : *
319 : : * The index_only parameter is a compile-time constant at each call site,
320 : : * allowing the compiler to specialize the code for each variant.
321 : : */
322 : : static pg_always_inline bool
323 : 22078110 : heapam_index_getnext_slot(IndexScanDesc scan, ScanDirection direction,
324 : : TupleTableSlot *slot, bool index_only)
325 : : {
326 [ - + ]: 22078110 : Assert(TransactionIdIsValid(RecentXmin));
327 [ + + + - ]: 22078110 : Assert(index_only || scan->xs_visited_pages_limit == 0);
328 : :
329 : : for (;;)
330 : 715583 : {
331 : : IndexScanHeapData *hscan;
332 : : bool all_visible;
333 : :
334 : : /*
335 : : * Get the next TID from the index, unless we're still working through
336 : : * a HOT chain (index-only scans never do that, and plain index scans
337 : : * only do it with a non-MVCC snapshot)
338 : : */
339 [ + + - + ]: 22793693 : Assert(!index_only || !scan->xs_heap_continue);
340 [ + + + + ]: 22793693 : if (index_only || likely(!scan->xs_heap_continue))
341 : : {
342 [ + + ]: 22691777 : if (!tableam_index_getnext_tid(scan, direction))
343 : 4359296 : return false;
344 : : }
345 : :
346 : : /* The scan's next TID was set in scan->xs_heaptid for us */
347 [ - + ]: 18434397 : Assert(ItemPointerIsValid(&scan->xs_heaptid));
348 : :
349 : 18434397 : hscan = (IndexScanHeapData *) scan->xs_table_opaque;
350 : :
351 [ + + ]: 18434397 : if (!index_only)
352 : : {
353 : : /* Plain index scan */
354 [ + + ]: 14546657 : if (!heapam_index_heap_fetch(scan, hscan, slot, false))
355 : 648568 : continue; /* no visible tuple, try next index entry */
356 : : }
357 : : else
358 : : {
359 : : /*
360 : : * Note: VM_ALL_VISIBLE does not lock the visibility map buffer,
361 : : * so the result could be slightly stale. See the comments above
362 : : * visibilitymap_get_status for why this is okay.
363 : : */
364 : 3887740 : all_visible = VM_ALL_VISIBLE(scan->heapRelation,
365 : : ItemPointerGetBlockNumber(&scan->xs_heaptid),
366 : : &hscan->xs_vmbuffer);
367 : :
368 : : /* Page isn't all-visible, so verify visibility with a heap fetch */
369 [ + + ]: 3887740 : if (unlikely(!all_visible))
370 : : {
371 [ + + ]: 515173 : if (!heapam_index_only_heap_fetch(scan))
372 : : {
373 : : /* No visible tuple */
374 [ - + ]: 67015 : if (heapam_index_visited_pages_exceeded(scan))
5 pg@bowt.ie 375 :UNC 0 : return false; /* give up */
376 : :
5 pg@bowt.ie 377 :GNC 67015 : continue; /* try next index entry */
378 : : }
379 : : }
380 : : else
381 : : {
382 : : /*
383 : : * Index-only scan with all-visible item.
384 : : *
385 : : * We won't access the heap, so we'll need to take a predicate
386 : : * lock explicitly, as if we had. For now we do that at page
387 : : * level.
388 : : */
389 : 3372567 : PredicateLockPage(scan->heapRelation,
390 : 3372567 : ItemPointerGetBlockNumber(&scan->xs_heaptid),
391 : : scan->xs_snapshot);
392 : : }
393 : :
394 : : /*
395 : : * Fill slot with data returned by the index AM (during plain
396 : : * scans heapam_index_heap_fetch does this for us instead)
397 : : */
398 : 3820725 : tableam_index_fill_ios_slot(scan, slot);
399 : : }
400 : :
401 : 17718809 : return true;
402 : : }
403 : :
404 : : pg_unreachable();
405 : :
406 : : return false;
407 : : }
408 : :
409 : : /*
410 : : * Get the scan's next heap tuple.
411 : : *
412 : : * Returns true if a visible heap tuple associated with the index TID most
413 : : * recently fetched by our caller in scan->xs_heaptid was found, false if no
414 : : * more matching tuples exist. (There can be more than one matching tuple
415 : : * because of HOT chains, although when using an MVCC snapshot it should be
416 : : * impossible for more than one such tuple to exist.)
417 : : *
418 : : * Plain index scans have us store the tuple in their slot, and its buffer
419 : : * stays pinned until a later call here (or heapam_index_scan_end) releases
420 : : * it. Index-only scans just need us to verify tuple visibility, so they pass
421 : : * a NULL slot.
422 : : *
423 : : * When the TID's whole HOT chain turns out to be dead, we arrange for the
424 : : * index AM to kill its entry for the TID before returning false.
425 : : */
426 : : static pg_always_inline bool
427 : 15061830 : heapam_index_heap_fetch(IndexScanDesc scan, IndexScanHeapData *hscan,
428 : : TupleTableSlot *slot, bool index_only)
429 : : {
430 : 15061830 : Relation rel = scan->heapRelation;
431 : 15061830 : ItemPointer tid = &scan->xs_heaptid;
432 : 15061830 : Snapshot snapshot = scan->xs_snapshot;
433 : : HeapTupleData tupdata;
434 : : HeapTuple heapTuple;
435 : : bool got_heap_tuple;
436 : : bool all_dead;
437 : :
438 [ + + ]: 15061830 : if (!index_only)
439 : : {
440 : : /* Plain index scans have us store fetched tuple in their slot */
441 : 14546657 : BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
442 : :
443 [ - + ]: 14546657 : Assert(TTS_IS_BUFFERTUPLE(slot));
444 : 14546657 : heapTuple = &bslot->base.tupdata;
445 : : }
446 : : else
447 : : {
448 : : /* Index-only scans only need to verify tuple visibility */
449 [ - + ]: 515173 : pg_assume(slot == NULL);
450 : 515173 : heapTuple = &tupdata;
451 : :
452 [ + + ]: 515173 : if (scan->instrument)
453 : 330 : scan->instrument->ntabletuplefetches++;
454 : : }
455 : :
456 : : /* We can skip the buffer-switching logic if we're on the same page. */
169 pg@bowt.ie 457 [ + + ]:CBC 15061830 : if (hscan->xs_blk != ItemPointerGetBlockNumber(tid))
458 : : {
5 pg@bowt.ie 459 [ - + ]:GNC 8039382 : Assert(!scan->xs_heap_continue);
460 : :
461 : : /* Remember this buffer's block number for next time */
169 pg@bowt.ie 462 :CBC 8039382 : hscan->xs_blk = ItemPointerGetBlockNumber(tid);
463 : :
464 : : /* We're switching to a new heap block, so count it */
5 pg@bowt.ie 465 :GNC 8039382 : hscan->xs_blkswitch_count++;
466 : :
169 pg@bowt.ie 467 [ + + ]:CBC 8039382 : if (BufferIsValid(hscan->xs_cbuf))
468 : 2103529 : ReleaseBuffer(hscan->xs_cbuf);
469 : :
5 pg@bowt.ie 470 :GNC 8039382 : hscan->xs_cbuf = ReadBuffer(rel, hscan->xs_blk);
471 : :
472 : : /*
473 : : * Prune page when it is pinned for the first time
474 : : */
475 : 8039379 : heap_page_prune_opt(rel, hscan->xs_cbuf, &hscan->xs_vmbuffer,
476 : 8039379 : hscan->xs_readonly);
477 : : }
478 : :
169 pg@bowt.ie 479 [ - + ]:CBC 15061827 : Assert(BufferGetBlockNumber(hscan->xs_cbuf) == hscan->xs_blk);
480 [ - + ]: 15061827 : Assert(hscan->xs_blk == ItemPointerGetBlockNumber(tid));
481 : :
482 : : /* Obtain share-lock on the buffer so we can examine visibility */
483 : 15061827 : LockBuffer(hscan->xs_cbuf, BUFFER_LOCK_SHARE);
484 : 15061827 : got_heap_tuple = heap_hot_search_buffer(tid,
485 : : rel,
486 : : hscan->xs_cbuf,
487 : : snapshot,
488 : : heapTuple,
489 : : &all_dead,
5 pg@bowt.ie 490 :GNC 15061827 : !scan->xs_heap_continue);
491 : 15061825 : heapTuple->t_self = *tid;
169 pg@bowt.ie 492 :CBC 15061825 : LockBuffer(hscan->xs_cbuf, BUFFER_LOCK_UNLOCK);
493 : :
494 [ + + ]: 15061825 : if (got_heap_tuple)
495 : : {
5 pg@bowt.ie 496 [ + + ]:GNC 14346242 : if (!index_only)
497 : : {
498 : : /*
499 : : * Only with a non-MVCC snapshot can more than one HOT chain
500 : : * member be visible, so only then must we keep walking the chain
501 : : */
502 [ + + + + ]: 13898084 : scan->xs_heap_continue = !IsMVCCLikeSnapshot(snapshot);
503 : :
504 : 13898084 : ExecStoreBufferHeapTuple(heapTuple, slot, hscan->xs_cbuf);
505 : :
506 [ - + ]: 13898084 : Assert(slot->tts_tableOid == RelationGetRelid(rel));
507 : : }
508 : : else
509 : : {
510 : : /*
511 : : * Index-only scans stop at the first visible HOT chain member.
512 : : * With a non-MVCC snapshot a later member could also be visible,
513 : : * but we never look. That's fine for the only non-MVCC
514 : : * index-only scan caller (selfuncs.c), which only needs to know
515 : : * that some version is visible.
516 : : */
517 : 448158 : scan->xs_heap_continue = false;
518 : : }
519 : :
520 [ + + - + : 14346242 : pgstat_count_heap_fetch(scan->indexRelation);
+ + + - ]
521 : : }
522 : : else
523 : : {
524 : : /* We've reached the end of the HOT chain. */
525 : 715583 : scan->xs_heap_continue = false;
526 : :
527 [ + + ]: 715583 : if (unlikely(all_dead))
528 : 256025 : heapam_index_kill_item(scan);
529 : : }
530 : :
169 pg@bowt.ie 531 :CBC 15061825 : return got_heap_tuple;
532 : : }
533 : :
534 : : /*
535 : : * Out-of-line heapam_index_heap_fetch wrapper for index-only scans.
536 : : *
537 : : * Index-only scans usually avoid heap fetches using the visibility map, so
538 : : * keeping their fetch out of line keeps the frame of their getnext_slot
539 : : * callback small.
540 : : */
541 : : static pg_noinline bool
5 pg@bowt.ie 542 :GNC 515173 : heapam_index_only_heap_fetch(IndexScanDesc scan)
543 : : {
544 : 515173 : IndexScanHeapData *hscan = (IndexScanHeapData *) scan->xs_table_opaque;
545 : :
546 : 515173 : return heapam_index_heap_fetch(scan, hscan, NULL, true);
547 : : }
548 : :
549 : : /*
550 : : * Called when we scanned a whole HOT chain and found only dead tuples:
551 : : * arrange for the index AM to kill its entry for that TID. We do not do this
552 : : * when in recovery because it may violate MVCC to do so. See comments in
553 : : * RelationGetIndexScan().
554 : : */
555 : : static pg_noinline void
556 : 256025 : heapam_index_kill_item(IndexScanDesc scan)
557 : : {
558 [ + + ]: 256025 : if (scan->xactStartedInRecovery)
559 : 22285 : return;
560 : :
561 : : /*
562 : : * Tell amgettuple-based index AM to kill its entry for that TID. The
563 : : * next tableam_index_getnext_tid call will pass that along to the index
564 : : * AM, before unsetting the flag again.
565 : : */
566 : 233740 : scan->kill_prior_tuple = true;
567 : : }
568 : :
569 : : /*
570 : : * Did an index-only scan switch heap pages more times than the caller's
571 : : * visited-pages limit allows?
572 : : *
573 : : * Caller passes scan rather than hscan to avoiding keeping hscan live across
574 : : * heap fetches.
575 : : */
576 : : static inline bool
577 : 67015 : heapam_index_visited_pages_exceeded(IndexScanDesc scan)
578 : : {
579 : : IndexScanHeapData *hscan;
580 : :
581 [ + + ]: 67015 : if (likely(scan->xs_visited_pages_limit == 0))
582 : 37882 : return false;
583 : :
584 : 29133 : hscan = (IndexScanHeapData *) scan->xs_table_opaque;
585 : :
586 : 29133 : return hscan->xs_blkswitch_count > scan->xs_visited_pages_limit;
587 : : }
|