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/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 *
31 pg@bowt.ie 28 :GNC 16791746 : heapam_index_fetch_begin(Relation rel, uint32 flags)
29 : : {
30 : 16791746 : IndexFetchHeapData *hscan = palloc0_object(IndexFetchHeapData);
31 : :
32 : 16791746 : hscan->xs_base.rel = rel;
33 : 16791746 : hscan->xs_base.flags = flags;
34 : 16791746 : hscan->xs_cbuf = InvalidBuffer;
35 : 16791746 : hscan->xs_blk = InvalidBlockNumber;
36 : 16791746 : hscan->xs_vmbuffer = InvalidBuffer;
37 : :
38 : 16791746 : return &hscan->xs_base;
39 : : }
40 : :
41 : : void
42 : 14216786 : 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 : 14216786 : }
52 : :
53 : : void
54 : 16790496 : heapam_index_fetch_end(IndexFetchTableData *scan)
55 : : {
56 : 16790496 : IndexFetchHeapData *hscan = (IndexFetchHeapData *) scan;
57 : :
58 : : /* drop pin if there's a pinned heap page */
59 [ + + ]: 16790496 : if (BufferIsValid(hscan->xs_cbuf))
60 : 13902431 : ReleaseBuffer(hscan->xs_cbuf);
61 : :
62 : : /* drop pin if there's a pinned visibility map page */
63 [ + + ]: 16790496 : if (BufferIsValid(hscan->xs_vmbuffer))
64 : 54287 : ReleaseBuffer(hscan->xs_vmbuffer);
65 : :
66 : 16790496 : pfree(hscan);
67 : 16790496 : }
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 : 27476137 : heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
91 : : Snapshot snapshot, HeapTuple heapTuple,
92 : : bool *all_dead, bool first_call)
93 : : {
94 : 27476137 : Page page = BufferGetPage(buffer);
95 : 27476137 : TransactionId prev_xmax = InvalidTransactionId;
96 : : BlockNumber blkno;
97 : : OffsetNumber offnum;
98 : : bool at_chain_start;
99 : : bool valid;
100 : : bool skip;
101 : 27476137 : GlobalVisState *vistest = NULL;
102 : :
103 : : /* If this is not the first call, previous call returned a (live!) tuple */
104 [ + + ]: 27476137 : if (all_dead)
105 : 23464427 : *all_dead = first_call;
106 : :
107 : 27476137 : blkno = ItemPointerGetBlockNumber(tid);
108 : 27476137 : offnum = ItemPointerGetOffsetNumber(tid);
109 : 27476137 : at_chain_start = first_call;
110 : 27476137 : skip = !first_call;
111 : :
112 : : /* XXX: we should assert that a snapshot is pushed or registered */
113 [ - + ]: 27476137 : Assert(TransactionIdIsValid(RecentXmin));
114 [ + - ]: 27476137 : Assert(BufferGetBlockNumber(buffer) == blkno);
115 : :
116 : : /* Scan through possible multiple members of HOT-chain */
117 : : for (;;)
118 : 1904680 : {
119 : : ItemId lp;
120 : :
121 : : /* check for bogus TID */
122 [ + - + - ]: 29380817 : if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page))
123 : : break;
124 : :
125 : 29380817 : lp = PageGetItemId(page, offnum);
126 : :
127 : : /* check for unused, dead, or redirected items */
128 [ + + ]: 29380817 : if (!ItemIdIsNormal(lp))
129 : : {
130 : : /* We should only see a redirect at start of chain */
131 [ + + + - ]: 972606 : if (ItemIdIsRedirected(lp) && at_chain_start)
132 : : {
133 : : /* Follow the redirect */
134 : 552499 : offnum = ItemIdGetRedirect(lp);
135 : 552499 : at_chain_start = false;
136 : 552499 : continue;
137 : : }
138 : : /* else must be end of chain */
139 : 420107 : 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 : 28408211 : heapTuple->t_data = (HeapTupleHeader) PageGetItem(page, lp);
149 : 28408211 : heapTuple->t_len = ItemIdGetLength(lp);
150 : 28408211 : heapTuple->t_tableOid = RelationGetRelid(relation);
151 : 28408211 : ItemPointerSet(&heapTuple->t_self, blkno, offnum);
152 : :
153 : : /*
154 : : * Shouldn't see a HEAP_ONLY tuple at chain start.
155 : : */
156 [ + + - + ]: 28408211 : if (at_chain_start && HeapTupleIsHeapOnly(heapTuple))
31 pg@bowt.ie 157 :UNC 0 : break;
158 : :
159 : : /*
160 : : * The xmin should match the previous xmax value, else chain is
161 : : * broken.
162 : : */
31 pg@bowt.ie 163 [ + + - + ]:GNC 29760392 : if (TransactionIdIsValid(prev_xmax) &&
164 : 1352181 : !TransactionIdEquals(prev_xmax,
165 : : HeapTupleHeaderGetXmin(heapTuple->t_data)))
31 pg@bowt.ie 166 :UNC 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 : : */
31 pg@bowt.ie 175 [ + + ]:GNC 28408211 : if (!skip)
176 : : {
177 : : /* If it's visible per the snapshot, we must return it */
178 : 28307802 : valid = HeapTupleSatisfiesVisibility(heapTuple, snapshot, buffer);
179 : 28307802 : HeapCheckForSerializableConflictOut(valid, relation, heapTuple,
180 : : buffer, snapshot);
181 : :
182 [ + + ]: 28307797 : if (valid)
183 : : {
184 : 18815416 : ItemPointerSetOffsetNumber(tid, offnum);
185 : 18815416 : PredicateLockTID(relation, &heapTuple->t_self, snapshot,
186 : 18815416 : HeapTupleHeaderGetXmin(heapTuple->t_data));
187 [ + + ]: 18815416 : if (all_dead)
188 : 15172756 : *all_dead = false;
189 : 18815416 : return true;
190 : : }
191 : : }
192 : 9592790 : 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 [ + + + + ]: 9592790 : if (all_dead && *all_dead)
203 : : {
204 [ + + ]: 8562330 : if (!vistest)
205 : 8404060 : vistest = GlobalVisTestFor(relation);
206 : :
207 [ + + ]: 8562330 : if (!HeapTupleIsSurelyDead(heapTuple, vistest))
208 : 8111280 : *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 [ + + ]: 9592790 : if (HeapTupleIsHotUpdated(heapTuple))
216 : : {
217 [ - + ]: 1352181 : Assert(ItemPointerGetBlockNumber(&heapTuple->t_data->t_ctid) ==
218 : : blkno);
219 : 1352181 : offnum = ItemPointerGetOffsetNumber(&heapTuple->t_data->t_ctid);
220 : 1352181 : at_chain_start = false;
221 : 1352181 : prev_xmax = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
222 : : }
223 : : else
224 : 8240609 : break; /* end of chain */
225 : :
226 : : }
227 : :
228 : 8660716 : return false;
229 : : }
230 : :
231 : : bool
232 : 23464832 : 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 : 23464832 : IndexFetchHeapData *hscan = (IndexFetchHeapData *) scan;
239 : 23464832 : BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
240 : : bool got_heap_tuple;
241 : :
242 [ - + ]: 23464832 : Assert(TTS_IS_BUFFERTUPLE(slot));
243 : :
244 : : /* We can skip the buffer-switching logic if we're on the same page. */
245 [ + + ]: 23464832 : if (hscan->xs_blk != ItemPointerGetBlockNumber(tid))
246 : : {
247 [ - + ]: 16095389 : Assert(!*heap_continue);
248 : :
249 : : /* Remember this buffer's block number for next time */
250 : 16095389 : hscan->xs_blk = ItemPointerGetBlockNumber(tid);
251 : :
252 [ + + ]: 16095389 : if (BufferIsValid(hscan->xs_cbuf))
253 : 2191756 : ReleaseBuffer(hscan->xs_cbuf);
254 : :
255 : 16095389 : 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 : 16095386 : heap_page_prune_opt(hscan->xs_base.rel, hscan->xs_cbuf,
261 : : &hscan->xs_vmbuffer,
262 : 16095386 : hscan->xs_base.flags & SO_HINT_REL_READ_ONLY);
263 : : }
264 : :
265 [ - + ]: 23464829 : Assert(BufferGetBlockNumber(hscan->xs_cbuf) == hscan->xs_blk);
266 [ - + ]: 23464829 : Assert(hscan->xs_blk == ItemPointerGetBlockNumber(tid));
267 : :
268 : : /* Obtain share-lock on the buffer so we can examine visibility */
269 : 23464829 : LockBuffer(hscan->xs_cbuf, BUFFER_LOCK_SHARE);
270 : 23464829 : 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 : 23464829 : !*heap_continue);
277 : 23464827 : bslot->base.tupdata.t_self = *tid;
278 : 23464827 : LockBuffer(hscan->xs_cbuf, BUFFER_LOCK_UNLOCK);
279 : :
280 [ + + ]: 23464827 : 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 [ + + + + ]: 15173158 : *heap_continue = !IsMVCCLikeSnapshot(snapshot);
287 : :
288 : 15173158 : slot->tts_tableOid = RelationGetRelid(scan->rel);
289 : 15173158 : ExecStoreBufferHeapTuple(&bslot->base.tupdata, slot, hscan->xs_cbuf);
290 : : }
291 : : else
292 : : {
293 : : /* We've reached the end of the HOT chain. */
294 : 8291669 : *heap_continue = false;
295 : : }
296 : :
297 : 23464827 : return got_heap_tuple;
298 : : }
|