Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * gistget.c
4 : * fetch tuples from a GiST scan.
5 : *
6 : *
7 : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
8 : * Portions Copyright (c) 1994, Regents of the University of California
9 : *
10 : * IDENTIFICATION
11 : * src/backend/access/gist/gistget.c
12 : *
13 : *-------------------------------------------------------------------------
14 : */
15 : #include "postgres.h"
16 :
17 : #include "access/genam.h"
18 : #include "access/gist_private.h"
19 : #include "access/relscan.h"
20 : #include "executor/instrument_node.h"
21 : #include "lib/pairingheap.h"
22 : #include "miscadmin.h"
23 : #include "pgstat.h"
24 : #include "storage/predicate.h"
25 : #include "utils/float.h"
26 : #include "utils/memutils.h"
27 : #include "utils/rel.h"
28 :
29 : /*
30 : * gistkillitems() -- set LP_DEAD state for items an indexscan caller has
31 : * told us were killed.
32 : *
33 : * We re-read page here, so it's important to check page LSN. If the page
34 : * has been modified since the last read (as determined by LSN), we cannot
35 : * flag any entries because it is possible that the old entry was vacuumed
36 : * away and the TID was re-used by a completely different heap tuple.
37 : */
38 : static void
39 1 : gistkillitems(IndexScanDesc scan)
40 : {
41 1 : GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
42 : Buffer buffer;
43 : Page page;
44 : OffsetNumber offnum;
45 : ItemId iid;
46 : int i;
47 1 : bool killedsomething = false;
48 :
49 : Assert(so->curBlkno != InvalidBlockNumber);
50 : Assert(XLogRecPtrIsValid(so->curPageLSN));
51 : Assert(so->killedItems != NULL);
52 :
53 1 : buffer = ReadBuffer(scan->indexRelation, so->curBlkno);
54 1 : if (!BufferIsValid(buffer))
55 0 : return;
56 :
57 1 : LockBuffer(buffer, GIST_SHARE);
58 1 : gistcheckpage(scan->indexRelation, buffer);
59 1 : page = BufferGetPage(buffer);
60 :
61 : /*
62 : * If page LSN differs it means that the page was modified since the last
63 : * read. killedItems could be not valid so LP_DEAD hints applying is not
64 : * safe.
65 : */
66 1 : if (BufferGetLSNAtomic(buffer) != so->curPageLSN)
67 0 : goto unlock;
68 :
69 : Assert(GistPageIsLeaf(page));
70 :
71 : /*
72 : * Mark all killedItems as dead. We need no additional recheck, because,
73 : * if page was modified, curPageLSN must have changed.
74 : */
75 2 : for (i = 0; i < so->numKilled; i++)
76 : {
77 1 : if (!killedsomething)
78 : {
79 : /*
80 : * Use the hint bit infrastructure to check if we can update the
81 : * page while just holding a share lock. If we are not allowed,
82 : * there's no point continuing.
83 : */
84 1 : if (!BufferBeginSetHintBits(buffer))
85 0 : goto unlock;
86 : }
87 :
88 1 : offnum = so->killedItems[i];
89 1 : iid = PageGetItemId(page, offnum);
90 1 : ItemIdMarkDead(iid);
91 1 : killedsomething = true;
92 : }
93 :
94 1 : if (killedsomething)
95 : {
96 1 : GistMarkPageHasGarbage(page);
97 1 : BufferFinishSetHintBits(buffer, true, true);
98 : }
99 :
100 0 : unlock:
101 1 : UnlockReleaseBuffer(buffer);
102 :
103 : /*
104 : * Always reset the scan state, so we don't look for same items on other
105 : * pages.
106 : */
107 1 : so->numKilled = 0;
108 : }
109 :
110 : /*
111 : * gistindex_keytest() -- does this index tuple satisfy the scan key(s)?
112 : *
113 : * The index tuple might represent either a heap tuple or a lower index page,
114 : * depending on whether the containing page is a leaf page or not.
115 : *
116 : * On success return for a heap tuple, *recheck_p is set to indicate whether
117 : * the quals need to be rechecked. We recheck if any of the consistent()
118 : * functions request it. recheck is not interesting when examining a non-leaf
119 : * entry, since we must visit the lower index page if there's any doubt.
120 : * Similarly, *recheck_distances_p is set to indicate whether the distances
121 : * need to be rechecked, and it is also ignored for non-leaf entries.
122 : *
123 : * If we are doing an ordered scan, so->distances[] is filled with distance
124 : * data from the distance() functions before returning success.
125 : *
126 : * We must decompress the key in the IndexTuple before passing it to the
127 : * sk_funcs (which actually are the opclass Consistent or Distance methods).
128 : *
129 : * Note that this function is always invoked in a short-lived memory context,
130 : * so we don't need to worry about cleaning up allocated memory, either here
131 : * or in the implementation of any Consistent or Distance methods.
132 : */
133 : static bool
134 1326093 : gistindex_keytest(IndexScanDesc scan,
135 : IndexTuple tuple,
136 : Page page,
137 : OffsetNumber offset,
138 : bool *recheck_p,
139 : bool *recheck_distances_p)
140 : {
141 1326093 : GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
142 1326093 : GISTSTATE *giststate = so->giststate;
143 1326093 : ScanKey key = scan->keyData;
144 1326093 : int keySize = scan->numberOfKeys;
145 : IndexOrderByDistance *distance_p;
146 1326093 : Relation r = scan->indexRelation;
147 :
148 1326093 : *recheck_p = false;
149 1326093 : *recheck_distances_p = false;
150 :
151 : /*
152 : * If it's a leftover invalid tuple from pre-9.1, treat it as a match with
153 : * minimum possible distances. This means we'll always follow it to the
154 : * referenced page.
155 : */
156 1326093 : if (GistTupleIsInvalid(tuple))
157 : {
158 : int i;
159 :
160 0 : if (GistPageIsLeaf(page)) /* shouldn't happen */
161 0 : elog(ERROR, "invalid GiST tuple found on leaf page");
162 0 : for (i = 0; i < scan->numberOfOrderBys; i++)
163 : {
164 0 : so->distances[i].value = -get_float8_infinity();
165 0 : so->distances[i].isnull = false;
166 : }
167 0 : return true;
168 : }
169 :
170 : /* Check whether it matches according to the Consistent functions */
171 2047660 : while (keySize > 0)
172 : {
173 : Datum datum;
174 : bool isNull;
175 :
176 1279706 : datum = index_getattr(tuple,
177 1279706 : key->sk_attno,
178 : giststate->leafTupdesc,
179 : &isNull);
180 :
181 1279706 : if (key->sk_flags & SK_ISNULL)
182 : {
183 : /*
184 : * On non-leaf page we can't conclude that child hasn't NULL
185 : * values because of assumption in GiST: union (VAL, NULL) is VAL.
186 : * But if on non-leaf page key IS NULL, then all children are
187 : * NULL.
188 : */
189 29932 : if (key->sk_flags & SK_SEARCHNULL)
190 : {
191 29888 : if (GistPageIsLeaf(page) && !isNull)
192 558139 : return false;
193 : }
194 : else
195 : {
196 : Assert(key->sk_flags & SK_SEARCHNOTNULL);
197 44 : if (isNull)
198 4 : return false;
199 : }
200 : }
201 1249774 : else if (isNull)
202 : {
203 2379 : return false;
204 : }
205 : else
206 : {
207 : Datum test;
208 : bool recheck;
209 : GISTENTRY de;
210 :
211 1247395 : gistdentryinit(giststate, key->sk_attno - 1, &de,
212 : datum, r, page, offset,
213 : false, isNull);
214 :
215 : /*
216 : * Call the Consistent function to evaluate the test. The
217 : * arguments are the index datum (as a GISTENTRY*), the comparison
218 : * datum, the comparison operator's strategy number and subtype
219 : * from pg_amop, and the recheck flag.
220 : *
221 : * (Presently there's no need to pass the subtype since it'll
222 : * always be zero, but might as well pass it for possible future
223 : * use.)
224 : *
225 : * We initialize the recheck flag to true (the safest assumption)
226 : * in case the Consistent function forgets to set it.
227 : */
228 1247395 : recheck = true;
229 :
230 2494790 : test = FunctionCall5Coll(&key->sk_func,
231 : key->sk_collation,
232 : PointerGetDatum(&de),
233 : key->sk_argument,
234 1247395 : UInt16GetDatum(key->sk_strategy),
235 : ObjectIdGetDatum(key->sk_subtype),
236 : PointerGetDatum(&recheck));
237 :
238 1247395 : if (!DatumGetBool(test))
239 528339 : return false;
240 719056 : *recheck_p |= recheck;
241 : }
242 :
243 721567 : key++;
244 721567 : keySize--;
245 : }
246 :
247 : /* OK, it passes --- now let's compute the distances */
248 767954 : key = scan->orderByData;
249 767954 : distance_p = so->distances;
250 767954 : keySize = scan->numberOfOrderBys;
251 785770 : while (keySize > 0)
252 : {
253 : Datum datum;
254 : bool isNull;
255 :
256 17816 : datum = index_getattr(tuple,
257 17816 : key->sk_attno,
258 : giststate->leafTupdesc,
259 : &isNull);
260 :
261 17816 : if ((key->sk_flags & SK_ISNULL) || isNull)
262 : {
263 : /* Assume distance computes as null */
264 68 : distance_p->value = 0.0;
265 68 : distance_p->isnull = true;
266 : }
267 : else
268 : {
269 : Datum dist;
270 : bool recheck;
271 : GISTENTRY de;
272 :
273 17748 : gistdentryinit(giststate, key->sk_attno - 1, &de,
274 : datum, r, page, offset,
275 : false, isNull);
276 :
277 : /*
278 : * Call the Distance function to evaluate the distance. The
279 : * arguments are the index datum (as a GISTENTRY*), the comparison
280 : * datum, the ordering operator's strategy number and subtype from
281 : * pg_amop, and the recheck flag.
282 : *
283 : * (Presently there's no need to pass the subtype since it'll
284 : * always be zero, but might as well pass it for possible future
285 : * use.)
286 : *
287 : * If the function sets the recheck flag, the returned distance is
288 : * a lower bound on the true distance and needs to be rechecked.
289 : * We initialize the flag to 'false'. This flag was added in
290 : * version 9.5; distance functions written before that won't know
291 : * about the flag, but are expected to never be lossy.
292 : */
293 17748 : recheck = false;
294 35496 : dist = FunctionCall5Coll(&key->sk_func,
295 : key->sk_collation,
296 : PointerGetDatum(&de),
297 : key->sk_argument,
298 17748 : UInt16GetDatum(key->sk_strategy),
299 : ObjectIdGetDatum(key->sk_subtype),
300 : PointerGetDatum(&recheck));
301 17748 : *recheck_distances_p |= recheck;
302 17748 : distance_p->value = DatumGetFloat8(dist);
303 17748 : distance_p->isnull = false;
304 : }
305 :
306 17816 : key++;
307 17816 : distance_p++;
308 17816 : keySize--;
309 : }
310 :
311 767954 : return true;
312 : }
313 :
314 : /*
315 : * Scan all items on the GiST index page identified by *pageItem, and insert
316 : * them into the queue (or directly to output areas)
317 : *
318 : * scan: index scan we are executing
319 : * pageItem: search queue item identifying an index page to scan
320 : * myDistances: distances array associated with pageItem, or NULL at the root
321 : * tbm: if not NULL, gistgetbitmap's output bitmap
322 : * ntids: if not NULL, gistgetbitmap's output tuple counter
323 : *
324 : * If tbm/ntids aren't NULL, we are doing an amgetbitmap scan, and heap
325 : * tuples should be reported directly into the bitmap. If they are NULL,
326 : * we're doing a plain or ordered indexscan. For a plain indexscan, heap
327 : * tuple TIDs are returned into so->pageData[]. For an ordered indexscan,
328 : * heap tuple TIDs are pushed into individual search queue items. In an
329 : * index-only scan, reconstructed index tuples are returned along with the
330 : * TIDs.
331 : *
332 : * If we detect that the index page has split since we saw its downlink
333 : * in the parent, we push its new right sibling onto the queue so the
334 : * sibling will be processed next.
335 : */
336 : static void
337 45688 : gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem,
338 : IndexOrderByDistance *myDistances, TIDBitmap *tbm, int64 *ntids)
339 : {
340 45688 : GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
341 45688 : GISTSTATE *giststate = so->giststate;
342 45688 : Relation r = scan->indexRelation;
343 : Buffer buffer;
344 : Page page;
345 : GISTPageOpaque opaque;
346 : OffsetNumber maxoff;
347 : OffsetNumber i;
348 : MemoryContext oldcxt;
349 :
350 : Assert(!GISTSearchItemIsHeap(*pageItem));
351 :
352 45688 : buffer = ReadBuffer(scan->indexRelation, pageItem->blkno);
353 45688 : LockBuffer(buffer, GIST_SHARE);
354 45688 : PredicateLockPage(r, BufferGetBlockNumber(buffer), scan->xs_snapshot);
355 45688 : gistcheckpage(scan->indexRelation, buffer);
356 45688 : page = BufferGetPage(buffer);
357 45688 : opaque = GistPageGetOpaque(page);
358 :
359 : /*
360 : * Check if we need to follow the rightlink. We need to follow it if the
361 : * page was concurrently split since we visited the parent (in which case
362 : * parentlsn < nsn), or if the system crashed after a page split but
363 : * before the downlink was inserted into the parent.
364 : */
365 45688 : if (XLogRecPtrIsValid(pageItem->data.parentlsn) &&
366 78684 : (GistFollowRight(page) ||
367 39342 : pageItem->data.parentlsn < GistPageGetNSN(page)) &&
368 0 : opaque->rightlink != InvalidBlockNumber /* sanity check */ )
369 : {
370 : /* There was a page split, follow right link to add pages */
371 : GISTSearchItem *item;
372 :
373 : /* This can't happen when starting at the root */
374 : Assert(myDistances != NULL);
375 :
376 0 : oldcxt = MemoryContextSwitchTo(so->queueCxt);
377 :
378 : /* Create new GISTSearchItem for the right sibling index page */
379 0 : item = palloc(SizeOfGISTSearchItem(scan->numberOfOrderBys));
380 0 : item->blkno = opaque->rightlink;
381 0 : item->data.parentlsn = pageItem->data.parentlsn;
382 :
383 : /* Insert it into the queue using same distances as for this page */
384 0 : memcpy(item->distances, myDistances,
385 0 : sizeof(item->distances[0]) * scan->numberOfOrderBys);
386 :
387 0 : pairingheap_add(so->queue, &item->phNode);
388 :
389 0 : MemoryContextSwitchTo(oldcxt);
390 : }
391 :
392 : /*
393 : * Check if the page was deleted after we saw the downlink. There's
394 : * nothing of interest on a deleted page. Note that we must do this after
395 : * checking the NSN for concurrent splits! It's possible that the page
396 : * originally contained some tuples that are visible to us, but was split
397 : * so that all the visible tuples were moved to another page, and then
398 : * this page was deleted.
399 : */
400 45688 : if (GistPageIsDeleted(page))
401 : {
402 0 : UnlockReleaseBuffer(buffer);
403 0 : return;
404 : }
405 :
406 45688 : so->nPageData = so->curPageData = 0;
407 45688 : scan->xs_hitup = NULL; /* might point into pageDataCxt */
408 45688 : if (so->pageDataCxt)
409 4105 : MemoryContextReset(so->pageDataCxt);
410 :
411 : /*
412 : * We save the LSN of the page as we read it, so that we know whether it
413 : * is safe to apply LP_DEAD hints to the page later. This allows us to
414 : * drop the pin for MVCC scans, which allows vacuum to avoid blocking.
415 : */
416 45688 : so->curPageLSN = BufferGetLSNAtomic(buffer);
417 :
418 : /*
419 : * check all tuples on page
420 : */
421 45688 : maxoff = PageGetMaxOffsetNumber(page);
422 1371782 : for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
423 : {
424 1326094 : ItemId iid = PageGetItemId(page, i);
425 : IndexTuple it;
426 : bool match;
427 : bool recheck;
428 : bool recheck_distances;
429 :
430 : /*
431 : * If the scan specifies not to return killed tuples, then we treat a
432 : * killed tuple as not passing the qual.
433 : */
434 1326094 : if (scan->ignore_killed_tuples && ItemIdIsDead(iid))
435 558140 : continue;
436 :
437 1326093 : it = (IndexTuple) PageGetItem(page, iid);
438 :
439 : /*
440 : * Must call gistindex_keytest in tempCxt, and clean up any leftover
441 : * junk afterward.
442 : */
443 1326093 : oldcxt = MemoryContextSwitchTo(so->giststate->tempCxt);
444 :
445 1326093 : match = gistindex_keytest(scan, it, page, i,
446 : &recheck, &recheck_distances);
447 :
448 1326093 : MemoryContextSwitchTo(oldcxt);
449 1326093 : MemoryContextReset(so->giststate->tempCxt);
450 :
451 : /* Ignore tuple if it doesn't match */
452 1326093 : if (!match)
453 558139 : continue;
454 :
455 767954 : if (tbm && GistPageIsLeaf(page))
456 : {
457 : /*
458 : * getbitmap scan, so just push heap tuple TIDs into the bitmap
459 : * without worrying about ordering
460 : */
461 197655 : tbm_add_tuples(tbm, &it->t_tid, 1, recheck);
462 197655 : (*ntids)++;
463 : }
464 570299 : else if (scan->numberOfOrderBys == 0 && GistPageIsLeaf(page))
465 : {
466 : /*
467 : * Non-ordered scan, so report tuples in so->pageData[]
468 : */
469 513327 : so->pageData[so->nPageData].heapPtr = it->t_tid;
470 513327 : so->pageData[so->nPageData].recheck = recheck;
471 513327 : so->pageData[so->nPageData].offnum = i;
472 :
473 : /*
474 : * In an index-only scan, also fetch the data from the tuple. The
475 : * reconstructed tuples are stored in pageDataCxt.
476 : */
477 513327 : if (scan->xs_want_itup)
478 : {
479 355626 : oldcxt = MemoryContextSwitchTo(so->pageDataCxt);
480 711252 : so->pageData[so->nPageData].recontup =
481 355626 : gistFetchTuple(giststate, r, it);
482 355626 : MemoryContextSwitchTo(oldcxt);
483 : }
484 513327 : so->nPageData++;
485 : }
486 : else
487 : {
488 : /*
489 : * Must push item into search queue. We get here for any lower
490 : * index page, and also for heap tuples if doing an ordered
491 : * search.
492 : */
493 : GISTSearchItem *item;
494 56972 : int nOrderBys = scan->numberOfOrderBys;
495 :
496 56972 : oldcxt = MemoryContextSwitchTo(so->queueCxt);
497 :
498 : /* Create new GISTSearchItem for this item */
499 56972 : item = palloc(SizeOfGISTSearchItem(scan->numberOfOrderBys));
500 :
501 56972 : if (GistPageIsLeaf(page))
502 : {
503 : /* Creating heap-tuple GISTSearchItem */
504 15660 : item->blkno = InvalidBlockNumber;
505 15660 : item->data.heap.heapPtr = it->t_tid;
506 15660 : item->data.heap.recheck = recheck;
507 15660 : item->data.heap.recheckDistances = recheck_distances;
508 :
509 : /*
510 : * In an index-only scan, also fetch the data from the tuple.
511 : */
512 15660 : if (scan->xs_want_itup)
513 7292 : item->data.heap.recontup = gistFetchTuple(giststate, r, it);
514 : }
515 : else
516 : {
517 : /* Creating index-page GISTSearchItem */
518 41312 : item->blkno = ItemPointerGetBlockNumber(&it->t_tid);
519 :
520 : /*
521 : * LSN of current page is lsn of parent page for child. We
522 : * only have a shared lock, so we need to get the LSN
523 : * atomically.
524 : */
525 41312 : item->data.parentlsn = BufferGetLSNAtomic(buffer);
526 : }
527 :
528 : /* Insert it into the queue using new distance data */
529 56972 : memcpy(item->distances, so->distances,
530 : sizeof(item->distances[0]) * nOrderBys);
531 :
532 56972 : pairingheap_add(so->queue, &item->phNode);
533 :
534 56972 : MemoryContextSwitchTo(oldcxt);
535 : }
536 : }
537 :
538 45688 : UnlockReleaseBuffer(buffer);
539 : }
540 :
541 : /*
542 : * Extract next item (in order) from search queue
543 : *
544 : * Returns a GISTSearchItem or NULL. Caller must pfree item when done with it.
545 : */
546 : static GISTSearchItem *
547 46147 : getNextGISTSearchItem(GISTScanOpaque so)
548 : {
549 : GISTSearchItem *item;
550 :
551 46147 : if (!pairingheap_is_empty(so->queue))
552 : {
553 40105 : item = (GISTSearchItem *) pairingheap_remove_first(so->queue);
554 : }
555 : else
556 : {
557 : /* Done when both heaps are empty */
558 6042 : item = NULL;
559 : }
560 :
561 : /* Return item; caller is responsible to pfree it */
562 46147 : return item;
563 : }
564 :
565 : /*
566 : * Fetch next heap tuple in an ordered search
567 : */
568 : static bool
569 791 : getNextNearest(IndexScanDesc scan)
570 : {
571 791 : GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
572 791 : bool res = false;
573 :
574 791 : if (scan->xs_hitup)
575 : {
576 : /* free previously returned tuple */
577 518 : pfree(scan->xs_hitup);
578 518 : scan->xs_hitup = NULL;
579 : }
580 :
581 : do
582 : {
583 977 : GISTSearchItem *item = getNextGISTSearchItem(so);
584 :
585 977 : if (!item)
586 28 : break;
587 :
588 949 : if (GISTSearchItemIsHeap(*item))
589 : {
590 : /* found a heap item at currently minimal distance */
591 763 : scan->xs_heaptid = item->data.heap.heapPtr;
592 763 : scan->xs_recheck = item->data.heap.recheck;
593 :
594 763 : index_store_float8_orderby_distances(scan, so->orderByTypes,
595 763 : item->distances,
596 763 : item->data.heap.recheckDistances);
597 :
598 : /* in an index-only scan, also return the reconstructed tuple. */
599 763 : if (scan->xs_want_itup)
600 556 : scan->xs_hitup = item->data.heap.recontup;
601 763 : res = true;
602 : }
603 : else
604 : {
605 : /* visit an index page, extract its items into queue */
606 186 : CHECK_FOR_INTERRUPTS();
607 :
608 186 : gistScanPage(scan, item, item->distances, NULL, NULL);
609 : }
610 :
611 949 : pfree(item);
612 949 : } while (!res);
613 :
614 791 : return res;
615 : }
616 :
617 : /*
618 : * gistgettuple() -- Get the next tuple in the scan
619 : */
620 : bool
621 518734 : gistgettuple(IndexScanDesc scan, ScanDirection dir)
622 : {
623 518734 : GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
624 :
625 518734 : if (dir != ForwardScanDirection)
626 0 : elog(ERROR, "GiST only supports forward scan direction");
627 :
628 518734 : if (!so->qual_ok)
629 0 : return false;
630 :
631 518734 : if (so->firstCall)
632 : {
633 : /* Begin the scan by processing the root page */
634 : GISTSearchItem fakeItem;
635 :
636 5127 : pgstat_count_index_scan(scan->indexRelation);
637 5127 : if (scan->instrument)
638 8 : scan->instrument->nsearches++;
639 :
640 5127 : so->firstCall = false;
641 5127 : so->curPageData = so->nPageData = 0;
642 5127 : scan->xs_hitup = NULL;
643 5127 : if (so->pageDataCxt)
644 466 : MemoryContextReset(so->pageDataCxt);
645 :
646 5127 : fakeItem.blkno = GIST_ROOT_BLKNO;
647 5127 : memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
648 5127 : gistScanPage(scan, &fakeItem, NULL, NULL, NULL);
649 : }
650 :
651 518734 : if (scan->numberOfOrderBys > 0)
652 : {
653 : /* Must fetch tuples in strict distance order */
654 791 : return getNextNearest(scan);
655 : }
656 : else
657 : {
658 : /* Fetch tuples index-page-at-a-time */
659 : for (;;)
660 : {
661 523425 : if (so->curPageData < so->nPageData)
662 : {
663 513148 : if (scan->kill_prior_tuple && so->curPageData > 0)
664 : {
665 :
666 1101 : if (so->killedItems == NULL)
667 : {
668 : MemoryContext oldCxt =
669 908 : MemoryContextSwitchTo(so->giststate->scanCxt);
670 :
671 908 : so->killedItems =
672 908 : (OffsetNumber *) palloc(MaxIndexTuplesPerPage
673 : * sizeof(OffsetNumber));
674 :
675 908 : MemoryContextSwitchTo(oldCxt);
676 : }
677 1101 : if (so->numKilled < MaxIndexTuplesPerPage)
678 1101 : so->killedItems[so->numKilled++] =
679 1101 : so->pageData[so->curPageData - 1].offnum;
680 : }
681 : /* continuing to return tuples from a leaf page */
682 513148 : scan->xs_heaptid = so->pageData[so->curPageData].heapPtr;
683 513148 : scan->xs_recheck = so->pageData[so->curPageData].recheck;
684 :
685 : /* in an index-only scan, also return the reconstructed tuple */
686 513148 : if (scan->xs_want_itup)
687 355626 : scan->xs_hitup = so->pageData[so->curPageData].recontup;
688 :
689 513148 : so->curPageData++;
690 :
691 513148 : return true;
692 : }
693 :
694 : /*
695 : * Check the last returned tuple and add it to killedItems if
696 : * necessary
697 : */
698 10277 : if (scan->kill_prior_tuple
699 29 : && so->curPageData > 0
700 29 : && so->curPageData == so->nPageData)
701 : {
702 :
703 29 : if (so->killedItems == NULL)
704 : {
705 : MemoryContext oldCxt =
706 14 : MemoryContextSwitchTo(so->giststate->scanCxt);
707 :
708 14 : so->killedItems =
709 14 : (OffsetNumber *) palloc(MaxIndexTuplesPerPage
710 : * sizeof(OffsetNumber));
711 :
712 14 : MemoryContextSwitchTo(oldCxt);
713 : }
714 29 : if (so->numKilled < MaxIndexTuplesPerPage)
715 29 : so->killedItems[so->numKilled++] =
716 29 : so->pageData[so->curPageData - 1].offnum;
717 : }
718 : /* find and process the next index page */
719 : do
720 : {
721 : GISTSearchItem *item;
722 :
723 12152 : if ((so->curBlkno != InvalidBlockNumber) && (so->numKilled > 0))
724 1 : gistkillitems(scan);
725 :
726 12152 : item = getNextGISTSearchItem(so);
727 :
728 12152 : if (!item)
729 4795 : return false;
730 :
731 7357 : CHECK_FOR_INTERRUPTS();
732 :
733 : /* save current item BlockNumber for next gistkillitems() call */
734 7357 : so->curBlkno = item->blkno;
735 :
736 : /*
737 : * While scanning a leaf page, ItemPointers of matching heap
738 : * tuples are stored in so->pageData. If there are any on
739 : * this page, we fall out of the inner "do" and loop around to
740 : * return them.
741 : */
742 7357 : gistScanPage(scan, item, item->distances, NULL, NULL);
743 :
744 7357 : pfree(item);
745 7357 : } while (so->nPageData == 0);
746 : }
747 : }
748 : }
749 :
750 : /*
751 : * gistgetbitmap() -- Get a bitmap of all heap tuple locations
752 : */
753 : int64
754 1219 : gistgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
755 : {
756 1219 : GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
757 1219 : int64 ntids = 0;
758 : GISTSearchItem fakeItem;
759 :
760 1219 : if (!so->qual_ok)
761 0 : return 0;
762 :
763 1219 : pgstat_count_index_scan(scan->indexRelation);
764 1219 : if (scan->instrument)
765 0 : scan->instrument->nsearches++;
766 :
767 : /* Begin the scan by processing the root page */
768 1219 : so->curPageData = so->nPageData = 0;
769 1219 : scan->xs_hitup = NULL;
770 1219 : if (so->pageDataCxt)
771 0 : MemoryContextReset(so->pageDataCxt);
772 :
773 1219 : fakeItem.blkno = GIST_ROOT_BLKNO;
774 1219 : memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
775 1219 : gistScanPage(scan, &fakeItem, NULL, tbm, &ntids);
776 :
777 : /*
778 : * While scanning a leaf page, ItemPointers of matching heap tuples will
779 : * be stored directly into tbm, so we don't need to deal with them here.
780 : */
781 : for (;;)
782 31799 : {
783 33018 : GISTSearchItem *item = getNextGISTSearchItem(so);
784 :
785 33018 : if (!item)
786 1219 : break;
787 :
788 31799 : CHECK_FOR_INTERRUPTS();
789 :
790 31799 : gistScanPage(scan, item, item->distances, tbm, &ntids);
791 :
792 31799 : pfree(item);
793 : }
794 :
795 1219 : return ntids;
796 : }
797 :
798 : /*
799 : * Can we do index-only scans on the given index column?
800 : *
801 : * Opclasses that implement a fetch function support index-only scans.
802 : * Opclasses without compression functions also support index-only scans.
803 : * Included attributes always can be fetched for index-only scans.
804 : */
805 : bool
806 8921 : gistcanreturn(Relation index, int attno)
807 : {
808 17737 : if (attno > IndexRelationGetNumberOfKeyAttributes(index) ||
809 16913 : OidIsValid(index_getprocid(index, attno, GIST_FETCH_PROC)) ||
810 8097 : !OidIsValid(index_getprocid(index, attno, GIST_COMPRESS_PROC)))
811 6732 : return true;
812 : else
813 2189 : return false;
814 : }
|