Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * hashsearch.c
4 : : * search code for postgres hash tables
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/hash/hashsearch.c
12 : : *
13 : : *-------------------------------------------------------------------------
14 : : */
15 : : #include "postgres.h"
16 : :
17 : : #include "access/hash.h"
18 : : #include "access/relscan.h"
19 : : #include "miscadmin.h"
20 : : #include "executor/instrument_node.h"
21 : : #include "pgstat.h"
22 : : #include "storage/predicate.h"
23 : : #include "utils/rel.h"
24 : :
25 : : static bool _hash_readpage(IndexScanDesc scan, Buffer *bufP,
26 : : ScanDirection dir);
27 : : static int _hash_load_qualified_items(IndexScanDesc scan, Page page,
28 : : OffsetNumber offnum, ScanDirection dir);
29 : : static inline void _hash_saveitem(HashScanOpaque so, int itemIndex,
30 : : OffsetNumber offnum, IndexTuple itup);
31 : : static void _hash_readnext(IndexScanDesc scan, Buffer *bufp,
32 : : Page *pagep, HashPageOpaque *opaquep);
33 : :
34 : : /*
35 : : * _hash_next() -- Get the next item in a scan.
36 : : *
37 : : * On entry, so->currPos describes the current page, which may
38 : : * be pinned but not locked, and so->currPos.itemIndex identifies
39 : : * which item was previously returned.
40 : : *
41 : : * On successful exit, scan->xs_heaptid is set to the TID of the next
42 : : * heap tuple. so->currPos is updated as needed.
43 : : *
44 : : * On failure exit (no more tuples), we return false with pin
45 : : * held on bucket page but no pins or locks held on overflow
46 : : * page.
47 : : */
48 : : bool
49 : 16559 : _hash_next(IndexScanDesc scan, ScanDirection dir)
50 : : {
51 : 16559 : Relation rel = scan->indexRelation;
52 : 16559 : HashScanOpaque so = (HashScanOpaque) scan->opaque;
53 : 16559 : HashScanPosItem *currItem;
54 : 16559 : BlockNumber blkno;
55 : 16559 : Buffer buf;
56 : 16559 : bool end_of_scan = false;
57 : :
58 : : /*
59 : : * Advance to the next tuple on the current page; or if done, try to read
60 : : * data from the next or previous page based on the scan direction. Before
61 : : * moving to the next or previous page make sure that we deal with all the
62 : : * killed items.
63 : : */
64 [ + + ]: 16559 : if (ScanDirectionIsForward(dir))
65 : : {
66 [ + + ]: 11059 : if (++so->currPos.itemIndex > so->currPos.lastItem)
67 : : {
68 [ + - ]: 66 : if (so->numKilled > 0)
69 : 0 : _hash_kill_items(scan);
70 : :
71 : 66 : blkno = so->currPos.nextPage;
72 [ + + ]: 66 : if (BlockNumberIsValid(blkno))
73 : : {
74 : 26 : buf = _hash_getbuf(rel, blkno, HASH_READ, LH_OVERFLOW_PAGE);
75 [ + - ]: 26 : if (!_hash_readpage(scan, &buf, dir))
76 : 0 : end_of_scan = true;
77 : 26 : }
78 : : else
79 : 40 : end_of_scan = true;
80 : 66 : }
81 : 11059 : }
82 : : else
83 : : {
84 [ + + ]: 5500 : if (--so->currPos.itemIndex < so->currPos.firstItem)
85 : : {
86 [ + - ]: 14 : if (so->numKilled > 0)
87 : 0 : _hash_kill_items(scan);
88 : :
89 : 14 : blkno = so->currPos.prevPage;
90 [ + + ]: 14 : if (BlockNumberIsValid(blkno))
91 : : {
92 : 13 : buf = _hash_getbuf(rel, blkno, HASH_READ,
93 : : LH_BUCKET_PAGE | LH_OVERFLOW_PAGE);
94 : :
95 : : /*
96 : : * We always maintain the pin on bucket page for whole scan
97 : : * operation, so releasing the additional pin we have acquired
98 : : * here.
99 : : */
100 [ + + - + ]: 13 : if (buf == so->hashso_bucket_buf ||
101 : 12 : buf == so->hashso_split_bucket_buf)
102 : 1 : _hash_dropbuf(rel, buf);
103 : :
104 [ + - ]: 13 : if (!_hash_readpage(scan, &buf, dir))
105 : 0 : end_of_scan = true;
106 : 13 : }
107 : : else
108 : 1 : end_of_scan = true;
109 : 14 : }
110 : : }
111 : :
112 [ + + ]: 16559 : if (end_of_scan)
113 : : {
114 : 41 : _hash_dropscanbuf(rel, so);
115 : 41 : HashScanPosInvalidate(so->currPos);
116 : 41 : return false;
117 : : }
118 : :
119 : : /* OK, itemIndex says what to return */
120 : 16518 : currItem = &so->currPos.items[so->currPos.itemIndex];
121 : 16518 : scan->xs_heaptid = currItem->heapTid;
122 : :
123 : 16518 : return true;
124 : 16559 : }
125 : :
126 : : /*
127 : : * Advance to next page in a bucket, if any. If we are scanning the bucket
128 : : * being populated during split operation then this function advances to the
129 : : * bucket being split after the last bucket page of bucket being populated.
130 : : */
131 : : static void
132 : 32 : _hash_readnext(IndexScanDesc scan,
133 : : Buffer *bufp, Page *pagep, HashPageOpaque *opaquep)
134 : : {
135 : 32 : BlockNumber blkno;
136 : 32 : Relation rel = scan->indexRelation;
137 : 32 : HashScanOpaque so = (HashScanOpaque) scan->opaque;
138 : 32 : bool block_found = false;
139 : :
140 : 32 : blkno = (*opaquep)->hasho_nextblkno;
141 : :
142 : : /*
143 : : * Retain the pin on primary bucket page till the end of scan. Refer the
144 : : * comments in _hash_first to know the reason of retaining pin.
145 : : */
146 [ + + - + ]: 32 : if (*bufp == so->hashso_bucket_buf || *bufp == so->hashso_split_bucket_buf)
147 : 20 : LockBuffer(*bufp, BUFFER_LOCK_UNLOCK);
148 : : else
149 : 12 : _hash_relbuf(rel, *bufp);
150 : :
151 : 32 : *bufp = InvalidBuffer;
152 : : /* check for interrupts while we're not holding any buffer lock */
153 [ + - ]: 32 : CHECK_FOR_INTERRUPTS();
154 [ + + ]: 32 : if (BlockNumberIsValid(blkno))
155 : : {
156 : 13 : *bufp = _hash_getbuf(rel, blkno, HASH_READ, LH_OVERFLOW_PAGE);
157 : 13 : block_found = true;
158 : 13 : }
159 [ - + # # ]: 19 : else if (so->hashso_buc_populated && !so->hashso_buc_split)
160 : : {
161 : : /*
162 : : * end of bucket, scan bucket being split if there was a split in
163 : : * progress at the start of scan.
164 : : */
165 : 0 : *bufp = so->hashso_split_bucket_buf;
166 : :
167 : : /*
168 : : * buffer for bucket being split must be valid as we acquire the pin
169 : : * on it before the start of scan and retain it till end of scan.
170 : : */
171 [ # # ]: 0 : Assert(BufferIsValid(*bufp));
172 : :
173 : 0 : LockBuffer(*bufp, BUFFER_LOCK_SHARE);
174 : 0 : PredicateLockPage(rel, BufferGetBlockNumber(*bufp), scan->xs_snapshot);
175 : :
176 : : /*
177 : : * setting hashso_buc_split to true indicates that we are scanning
178 : : * bucket being split.
179 : : */
180 : 0 : so->hashso_buc_split = true;
181 : :
182 : 0 : block_found = true;
183 : 0 : }
184 : :
185 [ + + ]: 32 : if (block_found)
186 : : {
187 : 13 : *pagep = BufferGetPage(*bufp);
188 : 13 : *opaquep = HashPageGetOpaque(*pagep);
189 : 13 : }
190 : 32 : }
191 : :
192 : : /*
193 : : * Advance to previous page in a bucket, if any. If the current scan has
194 : : * started during split operation then this function advances to bucket
195 : : * being populated after the first bucket page of bucket being split.
196 : : */
197 : : static void
198 : 0 : _hash_readprev(IndexScanDesc scan,
199 : : Buffer *bufp, Page *pagep, HashPageOpaque *opaquep)
200 : : {
201 : 0 : BlockNumber blkno;
202 : 0 : Relation rel = scan->indexRelation;
203 : 0 : HashScanOpaque so = (HashScanOpaque) scan->opaque;
204 : 0 : bool haveprevblk;
205 : :
206 : 0 : blkno = (*opaquep)->hasho_prevblkno;
207 : :
208 : : /*
209 : : * Retain the pin on primary bucket page till the end of scan. Refer the
210 : : * comments in _hash_first to know the reason of retaining pin.
211 : : */
212 [ # # # # ]: 0 : if (*bufp == so->hashso_bucket_buf || *bufp == so->hashso_split_bucket_buf)
213 : : {
214 : 0 : LockBuffer(*bufp, BUFFER_LOCK_UNLOCK);
215 : 0 : haveprevblk = false;
216 : 0 : }
217 : : else
218 : : {
219 : 0 : _hash_relbuf(rel, *bufp);
220 : 0 : haveprevblk = true;
221 : : }
222 : :
223 : 0 : *bufp = InvalidBuffer;
224 : : /* check for interrupts while we're not holding any buffer lock */
225 [ # # ]: 0 : CHECK_FOR_INTERRUPTS();
226 : :
227 [ # # ]: 0 : if (haveprevblk)
228 : : {
229 [ # # ]: 0 : Assert(BlockNumberIsValid(blkno));
230 : 0 : *bufp = _hash_getbuf(rel, blkno, HASH_READ,
231 : : LH_BUCKET_PAGE | LH_OVERFLOW_PAGE);
232 : 0 : *pagep = BufferGetPage(*bufp);
233 : 0 : *opaquep = HashPageGetOpaque(*pagep);
234 : :
235 : : /*
236 : : * We always maintain the pin on bucket page for whole scan operation,
237 : : * so releasing the additional pin we have acquired here.
238 : : */
239 [ # # # # ]: 0 : if (*bufp == so->hashso_bucket_buf || *bufp == so->hashso_split_bucket_buf)
240 : 0 : _hash_dropbuf(rel, *bufp);
241 : 0 : }
242 [ # # # # ]: 0 : else if (so->hashso_buc_populated && so->hashso_buc_split)
243 : : {
244 : : /*
245 : : * end of bucket, scan bucket being populated if there was a split in
246 : : * progress at the start of scan.
247 : : */
248 : 0 : *bufp = so->hashso_bucket_buf;
249 : :
250 : : /*
251 : : * buffer for bucket being populated must be valid as we acquire the
252 : : * pin on it before the start of scan and retain it till end of scan.
253 : : */
254 [ # # ]: 0 : Assert(BufferIsValid(*bufp));
255 : :
256 : 0 : LockBuffer(*bufp, BUFFER_LOCK_SHARE);
257 : 0 : *pagep = BufferGetPage(*bufp);
258 : 0 : *opaquep = HashPageGetOpaque(*pagep);
259 : :
260 : : /* move to the end of bucket chain */
261 [ # # ]: 0 : while (BlockNumberIsValid((*opaquep)->hasho_nextblkno))
262 : 0 : _hash_readnext(scan, bufp, pagep, opaquep);
263 : :
264 : : /*
265 : : * setting hashso_buc_split to false indicates that we are scanning
266 : : * bucket being populated.
267 : : */
268 : 0 : so->hashso_buc_split = false;
269 : 0 : }
270 : 0 : }
271 : :
272 : : /*
273 : : * _hash_first() -- Find the first item in a scan.
274 : : *
275 : : * We find the first item (or, if backward scan, the last item) in the
276 : : * index that satisfies the qualification associated with the scan
277 : : * descriptor.
278 : : *
279 : : * On successful exit, if the page containing current index tuple is an
280 : : * overflow page, both pin and lock are released whereas if it is a bucket
281 : : * page then it is pinned but not locked and data about the matching
282 : : * tuple(s) on the page has been loaded into so->currPos,
283 : : * scan->xs_heaptid is set to the heap TID of the current tuple.
284 : : *
285 : : * On failure exit (no more tuples), we return false, with pin held on
286 : : * bucket page but no pins or locks held on overflow page.
287 : : */
288 : : bool
289 : 60 : _hash_first(IndexScanDesc scan, ScanDirection dir)
290 : : {
291 : 60 : Relation rel = scan->indexRelation;
292 : 60 : HashScanOpaque so = (HashScanOpaque) scan->opaque;
293 : 60 : ScanKey cur;
294 : 60 : uint32 hashkey;
295 : 60 : Bucket bucket;
296 : 60 : Buffer buf;
297 : 60 : Page page;
298 : 60 : HashPageOpaque opaque;
299 : 60 : HashScanPosItem *currItem;
300 : :
301 [ + + + - : 60 : pgstat_count_index_scan(rel);
+ - ]
302 [ - + ]: 60 : if (scan->instrument)
303 : 60 : scan->instrument->nsearches++;
304 : :
305 : : /*
306 : : * We do not support hash scans with no index qualification, because we
307 : : * would have to read the whole index rather than just one bucket. That
308 : : * creates a whole raft of problems, since we haven't got a practical way
309 : : * to lock all the buckets against splits or compactions.
310 : : */
311 [ + - ]: 60 : if (scan->numberOfKeys < 1)
312 [ # # # # ]: 0 : ereport(ERROR,
313 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
314 : : errmsg("hash indexes do not support whole-index scans")));
315 : :
316 : : /* There may be more than one index qual, but we hash only the first */
317 : 60 : cur = &scan->keyData[0];
318 : :
319 : : /* We support only single-column hash indexes */
320 [ + - ]: 60 : Assert(cur->sk_attno == 1);
321 : : /* And there's only one operator strategy, too */
322 [ + - ]: 60 : Assert(cur->sk_strategy == HTEqualStrategyNumber);
323 : :
324 : : /*
325 : : * If the constant in the index qual is NULL, assume it cannot match any
326 : : * items in the index.
327 : : */
328 [ - + ]: 60 : if (cur->sk_flags & SK_ISNULL)
329 : 0 : return false;
330 : :
331 : : /*
332 : : * Okay to compute the hash key. We want to do this before acquiring any
333 : : * locks, in case a user-defined hash function happens to be slow.
334 : : *
335 : : * If scankey operator is not a cross-type comparison, we can use the
336 : : * cached hash function; otherwise gotta look it up in the catalogs.
337 : : *
338 : : * We support the convention that sk_subtype == InvalidOid means the
339 : : * opclass input type; this is a hack to simplify life for ScanKeyInit().
340 : : */
341 [ - + # # ]: 60 : if (cur->sk_subtype == rel->rd_opcintype[0] ||
342 : 0 : cur->sk_subtype == InvalidOid)
343 : 60 : hashkey = _hash_datum2hashkey(rel, cur->sk_argument);
344 : : else
345 : 0 : hashkey = _hash_datum2hashkey_type(rel, cur->sk_argument,
346 : 0 : cur->sk_subtype);
347 : :
348 : 60 : so->hashso_sk_hash = hashkey;
349 : :
350 : 60 : buf = _hash_getbucketbuf_from_hashkey(rel, hashkey, HASH_READ, NULL);
351 : 60 : PredicateLockPage(rel, BufferGetBlockNumber(buf), scan->xs_snapshot);
352 : 60 : page = BufferGetPage(buf);
353 : 60 : opaque = HashPageGetOpaque(page);
354 : 60 : bucket = opaque->hasho_bucket;
355 : :
356 : 60 : so->hashso_bucket_buf = buf;
357 : :
358 : : /*
359 : : * If a bucket split is in progress, then while scanning the bucket being
360 : : * populated, we need to skip tuples that were copied from bucket being
361 : : * split. We also need to maintain a pin on the bucket being split to
362 : : * ensure that split-cleanup work done by vacuum doesn't remove tuples
363 : : * from it till this scan is done. We need to maintain a pin on the
364 : : * bucket being populated to ensure that vacuum doesn't squeeze that
365 : : * bucket till this scan is complete; otherwise, the ordering of tuples
366 : : * can't be maintained during forward and backward scans. Here, we have
367 : : * to be cautious about locking order: first, acquire the lock on bucket
368 : : * being split; then, release the lock on it but not the pin; then,
369 : : * acquire a lock on bucket being populated and again re-verify whether
370 : : * the bucket split is still in progress. Acquiring the lock on bucket
371 : : * being split first ensures that the vacuum waits for this scan to
372 : : * finish.
373 : : */
374 [ + - ]: 60 : if (H_BUCKET_BEING_POPULATED(opaque))
375 : : {
376 : 0 : BlockNumber old_blkno;
377 : 0 : Buffer old_buf;
378 : :
379 : 0 : old_blkno = _hash_get_oldblock_from_newbucket(rel, bucket);
380 : :
381 : : /*
382 : : * release the lock on new bucket and re-acquire it after acquiring
383 : : * the lock on old bucket.
384 : : */
385 : 0 : LockBuffer(buf, BUFFER_LOCK_UNLOCK);
386 : :
387 : 0 : old_buf = _hash_getbuf(rel, old_blkno, HASH_READ, LH_BUCKET_PAGE);
388 : :
389 : : /*
390 : : * remember the split bucket buffer so as to use it later for
391 : : * scanning.
392 : : */
393 : 0 : so->hashso_split_bucket_buf = old_buf;
394 : 0 : LockBuffer(old_buf, BUFFER_LOCK_UNLOCK);
395 : :
396 : 0 : LockBuffer(buf, BUFFER_LOCK_SHARE);
397 : 0 : page = BufferGetPage(buf);
398 : 0 : opaque = HashPageGetOpaque(page);
399 [ # # ]: 0 : Assert(opaque->hasho_bucket == bucket);
400 : :
401 [ # # ]: 0 : if (H_BUCKET_BEING_POPULATED(opaque))
402 : 0 : so->hashso_buc_populated = true;
403 : : else
404 : : {
405 : 0 : _hash_dropbuf(rel, so->hashso_split_bucket_buf);
406 : 0 : so->hashso_split_bucket_buf = InvalidBuffer;
407 : : }
408 : 0 : }
409 : :
410 : : /* If a backwards scan is requested, move to the end of the chain */
411 [ + + ]: 60 : if (ScanDirectionIsBackward(dir))
412 : : {
413 : : /*
414 : : * Backward scans that start during split needs to start from end of
415 : : * bucket being split.
416 : : */
417 [ + + + + ]: 15 : while (BlockNumberIsValid(opaque->hasho_nextblkno) ||
418 [ + - ]: 1 : (so->hashso_buc_populated && !so->hashso_buc_split))
419 : 13 : _hash_readnext(scan, &buf, &page, &opaque);
420 : 1 : }
421 : :
422 : : /* remember which buffer we have pinned, if any */
423 [ + - ]: 60 : Assert(BufferIsInvalid(so->currPos.buf));
424 : 60 : so->currPos.buf = buf;
425 : :
426 : : /* Now find all the tuples satisfying the qualification from a page */
427 [ + + ]: 60 : if (!_hash_readpage(scan, &buf, dir))
428 : 19 : return false;
429 : :
430 : : /* OK, itemIndex says what to return */
431 : 41 : currItem = &so->currPos.items[so->currPos.itemIndex];
432 : 41 : scan->xs_heaptid = currItem->heapTid;
433 : :
434 : : /* if we're here, _hash_readpage found a valid tuples */
435 : 41 : return true;
436 : 60 : }
437 : :
438 : : /*
439 : : * _hash_readpage() -- Load data from current index page into so->currPos
440 : : *
441 : : * We scan all the items in the current index page and save them into
442 : : * so->currPos if it satisfies the qualification. If no matching items
443 : : * are found in the current page, we move to the next or previous page
444 : : * in a bucket chain as indicated by the direction.
445 : : *
446 : : * Return true if any matching items are found else return false.
447 : : */
448 : : static bool
449 : 99 : _hash_readpage(IndexScanDesc scan, Buffer *bufP, ScanDirection dir)
450 : : {
451 : 99 : Relation rel = scan->indexRelation;
452 : 99 : HashScanOpaque so = (HashScanOpaque) scan->opaque;
453 : 99 : Buffer buf;
454 : 99 : Page page;
455 : 99 : HashPageOpaque opaque;
456 : 99 : OffsetNumber offnum;
457 : 99 : uint16 itemIndex;
458 : :
459 : 99 : buf = *bufP;
460 [ + - ]: 99 : Assert(BufferIsValid(buf));
461 : 99 : _hash_checkpage(rel, buf, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE);
462 : 99 : page = BufferGetPage(buf);
463 : 99 : opaque = HashPageGetOpaque(page);
464 : :
465 : 99 : so->currPos.buf = buf;
466 : 99 : so->currPos.currPage = BufferGetBlockNumber(buf);
467 : :
468 [ + + ]: 99 : if (ScanDirectionIsForward(dir))
469 : : {
470 : 85 : BlockNumber prev_blkno = InvalidBlockNumber;
471 : :
472 : 85 : for (;;)
473 : : {
474 : : /* new page, locate starting position by binary search */
475 : 85 : offnum = _hash_binsearch(page, so->hashso_sk_hash);
476 : :
477 : 85 : itemIndex = _hash_load_qualified_items(scan, page, offnum, dir);
478 : :
479 [ + + ]: 85 : if (itemIndex != 0)
480 : 66 : break;
481 : :
482 : : /*
483 : : * Could not find any matching tuples in the current page, move to
484 : : * the next page. Before leaving the current page, deal with any
485 : : * killed items.
486 : : */
487 [ + - ]: 19 : if (so->numKilled > 0)
488 : 0 : _hash_kill_items(scan);
489 : :
490 : : /*
491 : : * If this is a primary bucket page, hasho_prevblkno is not a real
492 : : * block number.
493 : : */
494 [ - + # # ]: 19 : if (so->currPos.buf == so->hashso_bucket_buf ||
495 : 0 : so->currPos.buf == so->hashso_split_bucket_buf)
496 : 19 : prev_blkno = InvalidBlockNumber;
497 : : else
498 : 0 : prev_blkno = opaque->hasho_prevblkno;
499 : :
500 : 19 : _hash_readnext(scan, &buf, &page, &opaque);
501 [ + - ]: 19 : if (BufferIsValid(buf))
502 : : {
503 : 0 : so->currPos.buf = buf;
504 : 0 : so->currPos.currPage = BufferGetBlockNumber(buf);
505 : 0 : }
506 : : else
507 : : {
508 : : /*
509 : : * Remember next and previous block numbers for scrollable
510 : : * cursors to know the start position and return false
511 : : * indicating that no more matching tuples were found. Also,
512 : : * don't reset currPage or lsn, because we expect
513 : : * _hash_kill_items to be called for the old page after this
514 : : * function returns.
515 : : */
516 : 19 : so->currPos.prevPage = prev_blkno;
517 : 19 : so->currPos.nextPage = InvalidBlockNumber;
518 : 19 : so->currPos.buf = buf;
519 : 19 : return false;
520 : : }
521 : : }
522 : :
523 : 66 : so->currPos.firstItem = 0;
524 : 66 : so->currPos.lastItem = itemIndex - 1;
525 : 66 : so->currPos.itemIndex = 0;
526 [ + + ]: 85 : }
527 : : else
528 : : {
529 : 14 : BlockNumber next_blkno = InvalidBlockNumber;
530 : :
531 : 14 : for (;;)
532 : : {
533 : : /* new page, locate starting position by binary search */
534 : 14 : offnum = _hash_binsearch_last(page, so->hashso_sk_hash);
535 : :
536 : 14 : itemIndex = _hash_load_qualified_items(scan, page, offnum, dir);
537 : :
538 [ - + ]: 14 : if (itemIndex != MaxIndexTuplesPerPage)
539 : 14 : break;
540 : :
541 : : /*
542 : : * Could not find any matching tuples in the current page, move to
543 : : * the previous page. Before leaving the current page, deal with
544 : : * any killed items.
545 : : */
546 [ # # ]: 0 : if (so->numKilled > 0)
547 : 0 : _hash_kill_items(scan);
548 : :
549 [ # # # # ]: 0 : if (so->currPos.buf == so->hashso_bucket_buf ||
550 : 0 : so->currPos.buf == so->hashso_split_bucket_buf)
551 : 0 : next_blkno = opaque->hasho_nextblkno;
552 : :
553 : 0 : _hash_readprev(scan, &buf, &page, &opaque);
554 [ # # ]: 0 : if (BufferIsValid(buf))
555 : : {
556 : 0 : so->currPos.buf = buf;
557 : 0 : so->currPos.currPage = BufferGetBlockNumber(buf);
558 : 0 : }
559 : : else
560 : : {
561 : : /*
562 : : * Remember next and previous block numbers for scrollable
563 : : * cursors to know the start position and return false
564 : : * indicating that no more matching tuples were found. Also,
565 : : * don't reset currPage or lsn, because we expect
566 : : * _hash_kill_items to be called for the old page after this
567 : : * function returns.
568 : : */
569 : 0 : so->currPos.prevPage = InvalidBlockNumber;
570 : 0 : so->currPos.nextPage = next_blkno;
571 : 0 : so->currPos.buf = buf;
572 : 0 : return false;
573 : : }
574 : : }
575 : :
576 : 14 : so->currPos.firstItem = itemIndex;
577 : 14 : so->currPos.lastItem = MaxIndexTuplesPerPage - 1;
578 : 14 : so->currPos.itemIndex = MaxIndexTuplesPerPage - 1;
579 [ - + ]: 14 : }
580 : :
581 [ + + - + ]: 80 : if (so->currPos.buf == so->hashso_bucket_buf ||
582 : 39 : so->currPos.buf == so->hashso_split_bucket_buf)
583 : : {
584 : 41 : so->currPos.prevPage = InvalidBlockNumber;
585 : 41 : so->currPos.nextPage = opaque->hasho_nextblkno;
586 : 41 : LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
587 : 41 : }
588 : : else
589 : : {
590 : 39 : so->currPos.prevPage = opaque->hasho_prevblkno;
591 : 39 : so->currPos.nextPage = opaque->hasho_nextblkno;
592 : 39 : _hash_relbuf(rel, so->currPos.buf);
593 : 39 : so->currPos.buf = InvalidBuffer;
594 : : }
595 : :
596 [ + - ]: 80 : Assert(so->currPos.firstItem <= so->currPos.lastItem);
597 : 80 : return true;
598 : 99 : }
599 : :
600 : : /*
601 : : * Load all the qualified items from a current index page
602 : : * into so->currPos. Helper function for _hash_readpage.
603 : : */
604 : : static int
605 : 99 : _hash_load_qualified_items(IndexScanDesc scan, Page page,
606 : : OffsetNumber offnum, ScanDirection dir)
607 : : {
608 : 99 : HashScanOpaque so = (HashScanOpaque) scan->opaque;
609 : 99 : IndexTuple itup;
610 : 99 : int itemIndex;
611 : 99 : OffsetNumber maxoff;
612 : :
613 : 99 : maxoff = PageGetMaxOffsetNumber(page);
614 : :
615 [ + + ]: 99 : if (ScanDirectionIsForward(dir))
616 : : {
617 : : /* load items[] in ascending order */
618 : 85 : itemIndex = 0;
619 : :
620 [ + + ]: 11144 : while (offnum <= maxoff)
621 : : {
622 [ + - ]: 11094 : Assert(offnum >= FirstOffsetNumber);
623 : 11094 : itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum));
624 : :
625 : : /*
626 : : * skip the tuples that are moved by split operation for the scan
627 : : * that has started when split was in progress. Also, skip the
628 : : * tuples that are marked as dead.
629 : : */
630 [ - + # # ]: 11094 : if ((so->hashso_buc_populated && !so->hashso_buc_split &&
631 [ - + ]: 11094 : (itup->t_info & INDEX_MOVED_BY_SPLIT_MASK)) ||
632 [ + - ]: 11094 : (scan->ignore_killed_tuples &&
633 : 11094 : (ItemIdIsDead(PageGetItemId(page, offnum)))))
634 : : {
635 : 0 : offnum = OffsetNumberNext(offnum); /* move forward */
636 : 0 : continue;
637 : : }
638 : :
639 [ + + - + ]: 11094 : if (so->hashso_sk_hash == _hash_get_indextuple_hashkey(itup) &&
640 : 11059 : _hash_checkqual(scan, itup))
641 : : {
642 : : /* tuple is qualified, so remember it */
643 : 11059 : _hash_saveitem(so, itemIndex, offnum, itup);
644 : 11059 : itemIndex++;
645 : 11059 : }
646 : : else
647 : : {
648 : : /*
649 : : * No more matching tuples exist in this page. so, exit while
650 : : * loop.
651 : : */
652 : 35 : break;
653 : : }
654 : :
655 : 11059 : offnum = OffsetNumberNext(offnum);
656 : : }
657 : :
658 [ + - ]: 85 : Assert(itemIndex <= MaxIndexTuplesPerPage);
659 : 85 : return itemIndex;
660 : : }
661 : : else
662 : : {
663 : : /* load items[] in descending order */
664 : 14 : itemIndex = MaxIndexTuplesPerPage;
665 : :
666 [ + + ]: 5514 : while (offnum >= FirstOffsetNumber)
667 : : {
668 [ + - ]: 5500 : Assert(offnum <= maxoff);
669 : 5500 : itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum));
670 : :
671 : : /*
672 : : * skip the tuples that are moved by split operation for the scan
673 : : * that has started when split was in progress. Also, skip the
674 : : * tuples that are marked as dead.
675 : : */
676 [ - + # # ]: 5500 : if ((so->hashso_buc_populated && !so->hashso_buc_split &&
677 [ - + ]: 5500 : (itup->t_info & INDEX_MOVED_BY_SPLIT_MASK)) ||
678 [ + - ]: 5500 : (scan->ignore_killed_tuples &&
679 : 5500 : (ItemIdIsDead(PageGetItemId(page, offnum)))))
680 : : {
681 : 0 : offnum = OffsetNumberPrev(offnum); /* move back */
682 : 0 : continue;
683 : : }
684 : :
685 [ + - - + ]: 5500 : if (so->hashso_sk_hash == _hash_get_indextuple_hashkey(itup) &&
686 : 5500 : _hash_checkqual(scan, itup))
687 : : {
688 : 5500 : itemIndex--;
689 : : /* tuple is qualified, so remember it */
690 : 5500 : _hash_saveitem(so, itemIndex, offnum, itup);
691 : 5500 : }
692 : : else
693 : : {
694 : : /*
695 : : * No more matching tuples exist in this page. so, exit while
696 : : * loop.
697 : : */
698 : 0 : break;
699 : : }
700 : :
701 : 5500 : offnum = OffsetNumberPrev(offnum);
702 : : }
703 : :
704 [ + - ]: 14 : Assert(itemIndex >= 0);
705 : 14 : return itemIndex;
706 : : }
707 : 99 : }
708 : :
709 : : /* Save an index item into so->currPos.items[itemIndex] */
710 : : static inline void
711 : 16559 : _hash_saveitem(HashScanOpaque so, int itemIndex,
712 : : OffsetNumber offnum, IndexTuple itup)
713 : : {
714 : 16559 : HashScanPosItem *currItem = &so->currPos.items[itemIndex];
715 : :
716 : 16559 : currItem->heapTid = itup->t_tid;
717 : 16559 : currItem->indexOffset = offnum;
718 : 16559 : }
|