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