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/heapam.h"
18 : #include "access/relscan.h"
19 : #include "storage/predicate.h"
20 :
21 :
22 : /* ------------------------------------------------------------------------
23 : * Index Scan Callbacks for heap AM
24 : * ------------------------------------------------------------------------
25 : */
26 :
27 : IndexFetchTableData *
28 17431992 : heapam_index_fetch_begin(Relation rel, uint32 flags)
29 : {
30 17431992 : IndexFetchHeapData *hscan = palloc0_object(IndexFetchHeapData);
31 :
32 17431992 : hscan->xs_base.rel = rel;
33 17431992 : hscan->xs_base.flags = flags;
34 17431992 : hscan->xs_cbuf = InvalidBuffer;
35 17431992 : hscan->xs_blk = InvalidBlockNumber;
36 17431992 : hscan->xs_vmbuffer = InvalidBuffer;
37 :
38 17431992 : return &hscan->xs_base;
39 : }
40 :
41 : void
42 14840218 : heapam_index_fetch_reset(IndexFetchTableData *scan)
43 : {
44 : /*
45 : * Resets are a no-op.
46 : *
47 : * Deliberately avoid dropping pins now held in xs_cbuf and xs_vmbuffer.
48 : * This saves cycles during certain tight nested loop joins (it can avoid
49 : * repeated pinning and unpinning of the same buffer across rescans).
50 : */
51 14840218 : }
52 :
53 : void
54 17430751 : heapam_index_fetch_end(IndexFetchTableData *scan)
55 : {
56 17430751 : IndexFetchHeapData *hscan = (IndexFetchHeapData *) scan;
57 :
58 : /* drop pin if there's a pinned heap page */
59 17430751 : if (BufferIsValid(hscan->xs_cbuf))
60 14644897 : ReleaseBuffer(hscan->xs_cbuf);
61 :
62 : /* drop pin if there's a pinned visibility map page */
63 17430751 : if (BufferIsValid(hscan->xs_vmbuffer))
64 53699 : ReleaseBuffer(hscan->xs_vmbuffer);
65 :
66 17430751 : pfree(hscan);
67 17430751 : }
68 :
69 : /*
70 : * heap_hot_search_buffer - search HOT chain for tuple satisfying snapshot
71 : *
72 : * On entry, *tid is the TID of a tuple (either a simple tuple, or the root
73 : * of a HOT chain), and buffer is the buffer holding this tuple. We search
74 : * for the first chain member satisfying the given snapshot. If one is
75 : * found, we update *tid to reference that tuple's offset number, and
76 : * return true. If no match, return false without modifying *tid.
77 : *
78 : * heapTuple is a caller-supplied buffer. When a match is found, we return
79 : * the tuple here, in addition to updating *tid. If no match is found, the
80 : * contents of this buffer on return are undefined.
81 : *
82 : * If all_dead is not NULL, we check non-visible tuples to see if they are
83 : * globally dead; *all_dead is set true if all members of the HOT chain
84 : * are vacuumable, false if not.
85 : *
86 : * Unlike heap_fetch, the caller must already have pin and (at least) share
87 : * lock on the buffer; it is still pinned/locked at exit.
88 : */
89 : bool
90 28572158 : heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
91 : Snapshot snapshot, HeapTuple heapTuple,
92 : bool *all_dead, bool first_call)
93 : {
94 28572158 : Page page = BufferGetPage(buffer);
95 28572158 : TransactionId prev_xmax = InvalidTransactionId;
96 : BlockNumber blkno;
97 : OffsetNumber offnum;
98 : bool at_chain_start;
99 : bool valid;
100 : bool skip;
101 28572158 : GlobalVisState *vistest = NULL;
102 :
103 : /* If this is not the first call, previous call returned a (live!) tuple */
104 28572158 : if (all_dead)
105 24520420 : *all_dead = first_call;
106 :
107 28572158 : blkno = ItemPointerGetBlockNumber(tid);
108 28572158 : offnum = ItemPointerGetOffsetNumber(tid);
109 28572158 : at_chain_start = first_call;
110 28572158 : skip = !first_call;
111 :
112 : /* XXX: we should assert that a snapshot is pushed or registered */
113 : Assert(TransactionIdIsValid(RecentXmin));
114 : Assert(BufferGetBlockNumber(buffer) == blkno);
115 :
116 : /* Scan through possible multiple members of HOT-chain */
117 : for (;;)
118 1794500 : {
119 : ItemId lp;
120 :
121 : /* check for bogus TID */
122 30366658 : if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
123 : break;
124 :
125 30366658 : lp = PageGetItemId(page, offnum);
126 :
127 : /* check for unused, dead, or redirected items */
128 30366658 : if (!ItemIdIsNormal(lp))
129 : {
130 : /* We should only see a redirect at start of chain */
131 1130225 : if (ItemIdIsRedirected(lp) && at_chain_start)
132 : {
133 : /* Follow the redirect */
134 627312 : offnum = ItemIdGetRedirect(lp);
135 627312 : at_chain_start = false;
136 627312 : continue;
137 : }
138 : /* else must be end of chain */
139 502913 : break;
140 : }
141 :
142 : /*
143 : * Update heapTuple to point to the element of the HOT chain we're
144 : * currently investigating. Having t_self set correctly is important
145 : * because the SSI checks and the *Satisfies routine for historical
146 : * MVCC snapshots need the correct tid to decide about the visibility.
147 : */
148 29236433 : heapTuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
149 29236433 : heapTuple->t_len = ItemIdGetLength(lp);
150 29236433 : heapTuple->t_tableOid = RelationGetRelid(relation);
151 29236433 : ItemPointerSet(&heapTuple->t_self, blkno, offnum);
152 :
153 : /*
154 : * Shouldn't see a HEAP_ONLY tuple at chain start.
155 : */
156 29236433 : if (at_chain_start && HeapTupleIsHeapOnly(heapTuple))
157 0 : break;
158 :
159 : /*
160 : * The xmin should match the previous xmax value, else chain is
161 : * broken.
162 : */
163 30403621 : if (TransactionIdIsValid(prev_xmax) &&
164 1167188 : !TransactionIdEquals(prev_xmax,
165 : HeapTupleHeaderGetXmin(heapTuple->t_data)))
166 0 : break;
167 :
168 : /*
169 : * When first_call is true (and thus, skip is initially false) we'll
170 : * return the first tuple we find. But on later passes, heapTuple
171 : * will initially be pointing to the tuple we returned last time.
172 : * Returning it again would be incorrect (and would loop forever), so
173 : * we skip it and return the next match we find.
174 : */
175 29236433 : if (!skip)
176 : {
177 : /* If it's visible per the snapshot, we must return it */
178 29137197 : valid = HeapTupleSatisfiesVisibility(heapTuple, snapshot, buffer);
179 29137197 : HeapCheckForSerializableConflictOut(valid, relation, heapTuple,
180 : buffer, snapshot);
181 :
182 29137192 : if (valid)
183 : {
184 19854509 : ItemPointerSetOffsetNumber(tid, offnum);
185 19854509 : PredicateLockTID(relation, &heapTuple->t_self, snapshot,
186 19854509 : HeapTupleHeaderGetXmin(heapTuple->t_data));
187 19854509 : if (all_dead)
188 16190784 : *all_dead = false;
189 19854509 : return true;
190 : }
191 : }
192 9381919 : skip = false;
193 :
194 : /*
195 : * If we can't see it, maybe no one else can either. At caller
196 : * request, check whether all chain members are dead to all
197 : * transactions.
198 : *
199 : * Note: if you change the criterion here for what is "dead", fix the
200 : * planner's get_actual_variable_range() function to match.
201 : */
202 9381919 : if (all_dead && *all_dead)
203 : {
204 8537002 : if (!vistest)
205 8368540 : vistest = GlobalVisTestFor(relation);
206 :
207 8537002 : if (!HeapTupleIsSurelyDead(heapTuple, vistest))
208 8058194 : *all_dead = false;
209 : }
210 :
211 : /*
212 : * Check to see if HOT chain continues past this tuple; if so fetch
213 : * the next offnum and loop around.
214 : */
215 9381919 : if (HeapTupleIsHotUpdated(heapTuple))
216 : {
217 : Assert(ItemPointerGetBlockNumber(&heapTuple->t_data->t_ctid) ==
218 : blkno);
219 1167188 : offnum = ItemPointerGetOffsetNumber(&heapTuple->t_data->t_ctid);
220 1167188 : at_chain_start = false;
221 1167188 : prev_xmax = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
222 : }
223 : else
224 8214731 : break; /* end of chain */
225 :
226 : }
227 :
228 8717644 : return false;
229 : }
230 :
231 : bool
232 24520827 : heapam_index_fetch_tuple(struct IndexFetchTableData *scan,
233 : ItemPointer tid,
234 : Snapshot snapshot,
235 : TupleTableSlot *slot,
236 : bool *heap_continue, bool *all_dead)
237 : {
238 24520827 : IndexFetchHeapData *hscan = (IndexFetchHeapData *) scan;
239 24520827 : BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
240 : bool got_heap_tuple;
241 :
242 : Assert(TTS_IS_BUFFERTUPLE(slot));
243 :
244 : /* We can skip the buffer-switching logic if we're on the same page. */
245 24520827 : if (hscan->xs_blk != ItemPointerGetBlockNumber(tid))
246 : {
247 : Assert(!*heap_continue);
248 :
249 : /* Remember this buffer's block number for next time */
250 16797285 : hscan->xs_blk = ItemPointerGetBlockNumber(tid);
251 :
252 16797285 : if (BufferIsValid(hscan->xs_cbuf))
253 2151194 : ReleaseBuffer(hscan->xs_cbuf);
254 :
255 16797285 : hscan->xs_cbuf = ReadBuffer(hscan->xs_base.rel, hscan->xs_blk);
256 :
257 : /*
258 : * Prune page when it is pinned for the first time
259 : */
260 16797282 : heap_page_prune_opt(hscan->xs_base.rel, hscan->xs_cbuf,
261 : &hscan->xs_vmbuffer,
262 16797282 : hscan->xs_base.flags & SO_HINT_REL_READ_ONLY);
263 : }
264 :
265 : Assert(BufferGetBlockNumber(hscan->xs_cbuf) == hscan->xs_blk);
266 : Assert(hscan->xs_blk == ItemPointerGetBlockNumber(tid));
267 :
268 : /* Obtain share-lock on the buffer so we can examine visibility */
269 24520824 : LockBuffer(hscan->xs_cbuf, BUFFER_LOCK_SHARE);
270 24520824 : got_heap_tuple = heap_hot_search_buffer(tid,
271 : hscan->xs_base.rel,
272 : hscan->xs_cbuf,
273 : snapshot,
274 : &bslot->base.tupdata,
275 : all_dead,
276 24520824 : !*heap_continue);
277 24520822 : bslot->base.tupdata.t_self = *tid;
278 24520822 : LockBuffer(hscan->xs_cbuf, BUFFER_LOCK_UNLOCK);
279 :
280 24520822 : if (got_heap_tuple)
281 : {
282 : /*
283 : * Only in a non-MVCC snapshot can more than one member of the HOT
284 : * chain be visible.
285 : */
286 16191188 : *heap_continue = !IsMVCCLikeSnapshot(snapshot);
287 :
288 16191188 : slot->tts_tableOid = RelationGetRelid(scan->rel);
289 16191188 : ExecStoreBufferHeapTuple(&bslot->base.tupdata, slot, hscan->xs_cbuf);
290 : }
291 : else
292 : {
293 : /* We've reached the end of the HOT chain. */
294 8329634 : *heap_continue = false;
295 : }
296 :
297 24520822 : return got_heap_tuple;
298 : }
|