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