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