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 2 : gistkillitems(IndexScanDesc scan)
40 : {
41 2 : GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
42 : Buffer buffer;
43 : Page page;
44 : OffsetNumber offnum;
45 : ItemId iid;
46 : int i;
47 2 : bool killedsomething = false;
48 :
49 : Assert(so->curBlkno != InvalidBlockNumber);
50 : Assert(XLogRecPtrIsValid(so->curPageLSN));
51 : Assert(so->killedItems != NULL);
52 :
53 2 : buffer = ReadBuffer(scan->indexRelation, so->curBlkno);
54 2 : if (!BufferIsValid(buffer))
55 0 : return;
56 :
57 2 : LockBuffer(buffer, GIST_SHARE);
58 2 : gistcheckpage(scan->indexRelation, buffer);
59 2 : 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 2 : if (BufferGetLSNAtomic(buffer) != so->curPageLSN)
67 : {
68 0 : UnlockReleaseBuffer(buffer);
69 0 : so->numKilled = 0; /* reset counter */
70 0 : 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, curPageLSN must have changed.
78 : */
79 4 : for (i = 0; i < so->numKilled; i++)
80 : {
81 2 : offnum = so->killedItems[i];
82 2 : iid = PageGetItemId(page, offnum);
83 2 : ItemIdMarkDead(iid);
84 2 : killedsomething = true;
85 : }
86 :
87 2 : if (killedsomething)
88 : {
89 2 : GistMarkPageHasGarbage(page);
90 2 : MarkBufferDirtyHint(buffer, true);
91 : }
92 :
93 2 : UnlockReleaseBuffer(buffer);
94 :
95 : /*
96 : * Always reset the scan state, so we don't look for same items on other
97 : * pages.
98 : */
99 2 : 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 : */
125 : static bool
126 2148462 : gistindex_keytest(IndexScanDesc scan,
127 : IndexTuple tuple,
128 : Page page,
129 : OffsetNumber offset,
130 : bool *recheck_p,
131 : bool *recheck_distances_p)
132 : {
133 2148462 : GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
134 2148462 : GISTSTATE *giststate = so->giststate;
135 2148462 : ScanKey key = scan->keyData;
136 2148462 : int keySize = scan->numberOfKeys;
137 : IndexOrderByDistance *distance_p;
138 2148462 : Relation r = scan->indexRelation;
139 :
140 2148462 : *recheck_p = false;
141 2148462 : *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 2148462 : if (GistTupleIsInvalid(tuple))
149 : {
150 : int i;
151 :
152 0 : if (GistPageIsLeaf(page)) /* shouldn't happen */
153 0 : elog(ERROR, "invalid GiST tuple found on leaf page");
154 0 : for (i = 0; i < scan->numberOfOrderBys; i++)
155 : {
156 0 : so->distances[i].value = -get_float8_infinity();
157 0 : so->distances[i].isnull = false;
158 : }
159 0 : return true;
160 : }
161 :
162 : /* Check whether it matches according to the Consistent functions */
163 3309822 : while (keySize > 0)
164 : {
165 : Datum datum;
166 : bool isNull;
167 :
168 2062508 : datum = index_getattr(tuple,
169 2062508 : key->sk_attno,
170 : giststate->leafTupdesc,
171 : &isNull);
172 :
173 2062508 : 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 46196 : if (key->sk_flags & SK_SEARCHNULL)
182 : {
183 46130 : if (GistPageIsLeaf(page) && !isNull)
184 901148 : return false;
185 : }
186 : else
187 : {
188 : Assert(key->sk_flags & SK_SEARCHNOTNULL);
189 66 : if (isNull)
190 6 : return false;
191 : }
192 : }
193 2016312 : else if (isNull)
194 : {
195 3364 : return false;
196 : }
197 : else
198 : {
199 : Datum test;
200 : bool recheck;
201 : GISTENTRY de;
202 :
203 2012948 : 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 2012948 : recheck = true;
221 :
222 4025896 : test = FunctionCall5Coll(&key->sk_func,
223 : key->sk_collation,
224 : PointerGetDatum(&de),
225 : key->sk_argument,
226 2012948 : UInt16GetDatum(key->sk_strategy),
227 : ObjectIdGetDatum(key->sk_subtype),
228 : PointerGetDatum(&recheck));
229 :
230 2012948 : if (!DatumGetBool(test))
231 855364 : return false;
232 1157584 : *recheck_p |= recheck;
233 : }
234 :
235 1161360 : key++;
236 1161360 : keySize--;
237 : }
238 :
239 : /* OK, it passes --- now let's compute the distances */
240 1247314 : key = scan->orderByData;
241 1247314 : distance_p = so->distances;
242 1247314 : keySize = scan->numberOfOrderBys;
243 1279146 : while (keySize > 0)
244 : {
245 : Datum datum;
246 : bool isNull;
247 :
248 31832 : datum = index_getattr(tuple,
249 31832 : key->sk_attno,
250 : giststate->leafTupdesc,
251 : &isNull);
252 :
253 31832 : if ((key->sk_flags & SK_ISNULL) || isNull)
254 : {
255 : /* Assume distance computes as null */
256 108 : distance_p->value = 0.0;
257 108 : distance_p->isnull = true;
258 : }
259 : else
260 : {
261 : Datum dist;
262 : bool recheck;
263 : GISTENTRY de;
264 :
265 31724 : 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 31724 : recheck = false;
286 63448 : dist = FunctionCall5Coll(&key->sk_func,
287 : key->sk_collation,
288 : PointerGetDatum(&de),
289 : key->sk_argument,
290 31724 : UInt16GetDatum(key->sk_strategy),
291 : ObjectIdGetDatum(key->sk_subtype),
292 : PointerGetDatum(&recheck));
293 31724 : *recheck_distances_p |= recheck;
294 31724 : distance_p->value = DatumGetFloat8(dist);
295 31724 : distance_p->isnull = false;
296 : }
297 :
298 31832 : key++;
299 31832 : distance_p++;
300 31832 : keySize--;
301 : }
302 :
303 1247314 : 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 : */
328 : static void
329 78422 : gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem,
330 : IndexOrderByDistance *myDistances, TIDBitmap *tbm, int64 *ntids)
331 : {
332 78422 : GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
333 78422 : GISTSTATE *giststate = so->giststate;
334 78422 : 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 78422 : buffer = ReadBuffer(scan->indexRelation, pageItem->blkno);
345 78422 : LockBuffer(buffer, GIST_SHARE);
346 78422 : PredicateLockPage(r, BufferGetBlockNumber(buffer), scan->xs_snapshot);
347 78422 : gistcheckpage(scan->indexRelation, buffer);
348 78422 : page = BufferGetPage(buffer);
349 78422 : opaque = GistPageGetOpaque(page);
350 :
351 : /*
352 : * Check if we need to follow the rightlink. We need to follow it if the
353 : * page was concurrently split since we visited the parent (in which case
354 : * parentlsn < nsn), or if the system crashed after a page split but
355 : * before the downlink was inserted into the parent.
356 : */
357 78422 : if (XLogRecPtrIsValid(pageItem->data.parentlsn) &&
358 144804 : (GistFollowRight(page) ||
359 72402 : pageItem->data.parentlsn < GistPageGetNSN(page)) &&
360 0 : opaque->rightlink != InvalidBlockNumber /* sanity check */ )
361 : {
362 : /* There was a page split, follow right link to add pages */
363 : GISTSearchItem *item;
364 :
365 : /* This can't happen when starting at the root */
366 : Assert(myDistances != NULL);
367 :
368 0 : oldcxt = MemoryContextSwitchTo(so->queueCxt);
369 :
370 : /* Create new GISTSearchItem for the right sibling index page */
371 0 : item = palloc(SizeOfGISTSearchItem(scan->numberOfOrderBys));
372 0 : item->blkno = opaque->rightlink;
373 0 : item->data.parentlsn = pageItem->data.parentlsn;
374 :
375 : /* Insert it into the queue using same distances as for this page */
376 0 : memcpy(item->distances, myDistances,
377 0 : sizeof(item->distances[0]) * scan->numberOfOrderBys);
378 :
379 0 : pairingheap_add(so->queue, &item->phNode);
380 :
381 0 : MemoryContextSwitchTo(oldcxt);
382 : }
383 :
384 : /*
385 : * Check if the page was deleted after we saw the downlink. There's
386 : * nothing of interest on a deleted page. Note that we must do this after
387 : * checking the NSN for concurrent splits! It's possible that the page
388 : * originally contained some tuples that are visible to us, but was split
389 : * so that all the visible tuples were moved to another page, and then
390 : * this page was deleted.
391 : */
392 78422 : if (GistPageIsDeleted(page))
393 : {
394 0 : UnlockReleaseBuffer(buffer);
395 0 : return;
396 : }
397 :
398 78422 : so->nPageData = so->curPageData = 0;
399 78422 : scan->xs_hitup = NULL; /* might point into pageDataCxt */
400 78422 : if (so->pageDataCxt)
401 6072 : MemoryContextReset(so->pageDataCxt);
402 :
403 : /*
404 : * We save the LSN of the page as we read it, so that we know whether it
405 : * safe to apply LP_DEAD hints to the page later. This allows us to drop
406 : * the pin for MVCC scans, which allows vacuum to avoid blocking.
407 : */
408 78422 : so->curPageLSN = BufferGetLSNAtomic(buffer);
409 :
410 : /*
411 : * check all tuples on page
412 : */
413 78422 : maxoff = PageGetMaxOffsetNumber(page);
414 2226886 : for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
415 : {
416 2148464 : ItemId iid = PageGetItemId(page, i);
417 : IndexTuple it;
418 : bool match;
419 : bool recheck;
420 : bool recheck_distances;
421 :
422 : /*
423 : * If the scan specifies not to return killed tuples, then we treat a
424 : * killed tuple as not passing the qual.
425 : */
426 2148464 : if (scan->ignore_killed_tuples && ItemIdIsDead(iid))
427 901150 : continue;
428 :
429 2148462 : it = (IndexTuple) PageGetItem(page, iid);
430 :
431 : /*
432 : * Must call gistindex_keytest in tempCxt, and clean up any leftover
433 : * junk afterward.
434 : */
435 2148462 : oldcxt = MemoryContextSwitchTo(so->giststate->tempCxt);
436 :
437 2148462 : match = gistindex_keytest(scan, it, page, i,
438 : &recheck, &recheck_distances);
439 :
440 2148462 : MemoryContextSwitchTo(oldcxt);
441 2148462 : MemoryContextReset(so->giststate->tempCxt);
442 :
443 : /* Ignore tuple if it doesn't match */
444 2148462 : if (!match)
445 901148 : continue;
446 :
447 1247314 : if (tbm && GistPageIsLeaf(page))
448 : {
449 : /*
450 : * getbitmap scan, so just push heap tuple TIDs into the bitmap
451 : * without worrying about ordering
452 : */
453 396158 : tbm_add_tuples(tbm, &it->t_tid, 1, recheck);
454 396158 : (*ntids)++;
455 : }
456 851156 : else if (scan->numberOfOrderBys == 0 && GistPageIsLeaf(page))
457 : {
458 : /*
459 : * Non-ordered scan, so report tuples in so->pageData[]
460 : */
461 747254 : so->pageData[so->nPageData].heapPtr = it->t_tid;
462 747254 : so->pageData[so->nPageData].recheck = recheck;
463 747254 : so->pageData[so->nPageData].offnum = i;
464 :
465 : /*
466 : * In an index-only scan, also fetch the data from the tuple. The
467 : * reconstructed tuples are stored in pageDataCxt.
468 : */
469 747254 : if (scan->xs_want_itup)
470 : {
471 517478 : oldcxt = MemoryContextSwitchTo(so->pageDataCxt);
472 1034956 : so->pageData[so->nPageData].recontup =
473 517478 : gistFetchTuple(giststate, r, it);
474 517478 : MemoryContextSwitchTo(oldcxt);
475 : }
476 747254 : so->nPageData++;
477 : }
478 : else
479 : {
480 : /*
481 : * Must push item into search queue. We get here for any lower
482 : * index page, and also for heap tuples if doing an ordered
483 : * search.
484 : */
485 : GISTSearchItem *item;
486 103902 : int nOrderBys = scan->numberOfOrderBys;
487 :
488 103902 : oldcxt = MemoryContextSwitchTo(so->queueCxt);
489 :
490 : /* Create new GISTSearchItem for this item */
491 103902 : item = palloc(SizeOfGISTSearchItem(scan->numberOfOrderBys));
492 :
493 103902 : if (GistPageIsLeaf(page))
494 : {
495 : /* Creating heap-tuple GISTSearchItem */
496 28362 : item->blkno = InvalidBlockNumber;
497 28362 : item->data.heap.heapPtr = it->t_tid;
498 28362 : item->data.heap.recheck = recheck;
499 28362 : item->data.heap.recheckDistances = recheck_distances;
500 :
501 : /*
502 : * In an index-only scan, also fetch the data from the tuple.
503 : */
504 28362 : if (scan->xs_want_itup)
505 14248 : item->data.heap.recontup = gistFetchTuple(giststate, r, it);
506 : }
507 : else
508 : {
509 : /* Creating index-page GISTSearchItem */
510 75540 : item->blkno = ItemPointerGetBlockNumber(&it->t_tid);
511 :
512 : /*
513 : * LSN of current page is lsn of parent page for child. We
514 : * only have a shared lock, so we need to get the LSN
515 : * atomically.
516 : */
517 75540 : item->data.parentlsn = BufferGetLSNAtomic(buffer);
518 : }
519 :
520 : /* Insert it into the queue using new distance data */
521 103902 : memcpy(item->distances, so->distances,
522 : sizeof(item->distances[0]) * nOrderBys);
523 :
524 103902 : pairingheap_add(so->queue, &item->phNode);
525 :
526 103902 : MemoryContextSwitchTo(oldcxt);
527 : }
528 : }
529 :
530 78422 : UnlockReleaseBuffer(buffer);
531 : }
532 :
533 : /*
534 : * Extract next item (in order) from search queue
535 : *
536 : * Returns a GISTSearchItem or NULL. Caller must pfree item when done with it.
537 : */
538 : static GISTSearchItem *
539 79282 : getNextGISTSearchItem(GISTScanOpaque so)
540 : {
541 : GISTSearchItem *item;
542 :
543 79282 : if (!pairingheap_is_empty(so->queue))
544 : {
545 73648 : item = (GISTSearchItem *) pairingheap_remove_first(so->queue);
546 : }
547 : else
548 : {
549 : /* Done when both heaps are empty */
550 5634 : item = NULL;
551 : }
552 :
553 : /* Return item; caller is responsible to pfree it */
554 79282 : return item;
555 : }
556 :
557 : /*
558 : * Fetch next heap tuple in an ordered search
559 : */
560 : static bool
561 1288 : getNextNearest(IndexScanDesc scan)
562 : {
563 1288 : GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
564 1288 : bool res = false;
565 :
566 1288 : if (scan->xs_hitup)
567 : {
568 : /* free previously returned tuple */
569 850 : pfree(scan->xs_hitup);
570 850 : scan->xs_hitup = NULL;
571 : }
572 :
573 : do
574 : {
575 1620 : GISTSearchItem *item = getNextGISTSearchItem(so);
576 :
577 1620 : if (!item)
578 42 : break;
579 :
580 1578 : if (GISTSearchItemIsHeap(*item))
581 : {
582 : /* found a heap item at currently minimal distance */
583 1246 : scan->xs_heaptid = item->data.heap.heapPtr;
584 1246 : scan->xs_recheck = item->data.heap.recheck;
585 :
586 1246 : index_store_float8_orderby_distances(scan, so->orderByTypes,
587 1246 : item->distances,
588 1246 : item->data.heap.recheckDistances);
589 :
590 : /* in an index-only scan, also return the reconstructed tuple. */
591 1246 : if (scan->xs_want_itup)
592 918 : scan->xs_hitup = item->data.heap.recontup;
593 1246 : res = true;
594 : }
595 : else
596 : {
597 : /* visit an index page, extract its items into queue */
598 332 : CHECK_FOR_INTERRUPTS();
599 :
600 332 : gistScanPage(scan, item, item->distances, NULL, NULL);
601 : }
602 :
603 1578 : pfree(item);
604 1578 : } while (!res);
605 :
606 1288 : return res;
607 : }
608 :
609 : /*
610 : * gistgettuple() -- Get the next tuple in the scan
611 : */
612 : bool
613 752334 : gistgettuple(IndexScanDesc scan, ScanDirection dir)
614 : {
615 752334 : GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
616 :
617 752334 : if (dir != ForwardScanDirection)
618 0 : elog(ERROR, "GiST only supports forward scan direction");
619 :
620 752334 : if (!so->qual_ok)
621 0 : return false;
622 :
623 752334 : if (so->firstCall)
624 : {
625 : /* Begin the scan by processing the root page */
626 : GISTSearchItem fakeItem;
627 :
628 4378 : pgstat_count_index_scan(scan->indexRelation);
629 4378 : if (scan->instrument)
630 2098 : scan->instrument->nsearches++;
631 :
632 4378 : so->firstCall = false;
633 4378 : so->curPageData = so->nPageData = 0;
634 4378 : scan->xs_hitup = NULL;
635 4378 : if (so->pageDataCxt)
636 680 : MemoryContextReset(so->pageDataCxt);
637 :
638 4378 : fakeItem.blkno = GIST_ROOT_BLKNO;
639 4378 : memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
640 4378 : gistScanPage(scan, &fakeItem, NULL, NULL, NULL);
641 : }
642 :
643 752334 : if (scan->numberOfOrderBys > 0)
644 : {
645 : /* Must fetch tuples in strict distance order */
646 1288 : return getNextNearest(scan);
647 : }
648 : else
649 : {
650 : /* Fetch tuples index-page-at-a-time */
651 : for (;;)
652 : {
653 759214 : if (so->curPageData < so->nPageData)
654 : {
655 747096 : if (scan->kill_prior_tuple && so->curPageData > 0)
656 : {
657 :
658 544 : if (so->killedItems == NULL)
659 : {
660 : MemoryContext oldCxt =
661 418 : MemoryContextSwitchTo(so->giststate->scanCxt);
662 :
663 418 : so->killedItems =
664 418 : (OffsetNumber *) palloc(MaxIndexTuplesPerPage
665 : * sizeof(OffsetNumber));
666 :
667 418 : MemoryContextSwitchTo(oldCxt);
668 : }
669 544 : if (so->numKilled < MaxIndexTuplesPerPage)
670 544 : so->killedItems[so->numKilled++] =
671 544 : so->pageData[so->curPageData - 1].offnum;
672 : }
673 : /* continuing to return tuples from a leaf page */
674 747096 : scan->xs_heaptid = so->pageData[so->curPageData].heapPtr;
675 747096 : scan->xs_recheck = so->pageData[so->curPageData].recheck;
676 :
677 : /* in an index-only scan, also return the reconstructed tuple */
678 747096 : if (scan->xs_want_itup)
679 517478 : scan->xs_hitup = so->pageData[so->curPageData].recontup;
680 :
681 747096 : so->curPageData++;
682 :
683 747096 : return true;
684 : }
685 :
686 : /*
687 : * Check the last returned tuple and add it to killedItems if
688 : * necessary
689 : */
690 12118 : if (scan->kill_prior_tuple
691 10 : && so->curPageData > 0
692 10 : && so->curPageData == so->nPageData)
693 : {
694 :
695 10 : if (so->killedItems == NULL)
696 : {
697 : MemoryContext oldCxt =
698 10 : MemoryContextSwitchTo(so->giststate->scanCxt);
699 :
700 10 : so->killedItems =
701 10 : (OffsetNumber *) palloc(MaxIndexTuplesPerPage
702 : * sizeof(OffsetNumber));
703 :
704 10 : MemoryContextSwitchTo(oldCxt);
705 : }
706 10 : if (so->numKilled < MaxIndexTuplesPerPage)
707 10 : so->killedItems[so->numKilled++] =
708 10 : so->pageData[so->curPageData - 1].offnum;
709 : }
710 : /* find and process the next index page */
711 : do
712 : {
713 : GISTSearchItem *item;
714 :
715 15582 : if ((so->curBlkno != InvalidBlockNumber) && (so->numKilled > 0))
716 2 : gistkillitems(scan);
717 :
718 15582 : item = getNextGISTSearchItem(so);
719 :
720 15582 : if (!item)
721 3950 : return false;
722 :
723 11632 : CHECK_FOR_INTERRUPTS();
724 :
725 : /* save current item BlockNumber for next gistkillitems() call */
726 11632 : so->curBlkno = item->blkno;
727 :
728 : /*
729 : * While scanning a leaf page, ItemPointers of matching heap
730 : * tuples are stored in so->pageData. If there are any on
731 : * this page, we fall out of the inner "do" and loop around to
732 : * return them.
733 : */
734 11632 : gistScanPage(scan, item, item->distances, NULL, NULL);
735 :
736 11632 : pfree(item);
737 11632 : } while (so->nPageData == 0);
738 : }
739 : }
740 : }
741 :
742 : /*
743 : * gistgetbitmap() -- Get a bitmap of all heap tuple locations
744 : */
745 : int64
746 1642 : gistgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
747 : {
748 1642 : GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
749 1642 : int64 ntids = 0;
750 : GISTSearchItem fakeItem;
751 :
752 1642 : if (!so->qual_ok)
753 0 : return 0;
754 :
755 1642 : pgstat_count_index_scan(scan->indexRelation);
756 1642 : if (scan->instrument)
757 1642 : scan->instrument->nsearches++;
758 :
759 : /* Begin the scan by processing the root page */
760 1642 : so->curPageData = so->nPageData = 0;
761 1642 : scan->xs_hitup = NULL;
762 1642 : if (so->pageDataCxt)
763 0 : MemoryContextReset(so->pageDataCxt);
764 :
765 1642 : fakeItem.blkno = GIST_ROOT_BLKNO;
766 1642 : memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
767 1642 : gistScanPage(scan, &fakeItem, NULL, tbm, &ntids);
768 :
769 : /*
770 : * While scanning a leaf page, ItemPointers of matching heap tuples will
771 : * be stored directly into tbm, so we don't need to deal with them here.
772 : */
773 : for (;;)
774 60438 : {
775 62080 : GISTSearchItem *item = getNextGISTSearchItem(so);
776 :
777 62080 : if (!item)
778 1642 : break;
779 :
780 60438 : CHECK_FOR_INTERRUPTS();
781 :
782 60438 : gistScanPage(scan, item, item->distances, tbm, &ntids);
783 :
784 60438 : pfree(item);
785 : }
786 :
787 1642 : return ntids;
788 : }
789 :
790 : /*
791 : * Can we do index-only scans on the given index column?
792 : *
793 : * Opclasses that implement a fetch function support index-only scans.
794 : * Opclasses without compression functions also support index-only scans.
795 : * Included attributes always can be fetched for index-only scans.
796 : */
797 : bool
798 7836 : gistcanreturn(Relation index, int attno)
799 : {
800 15546 : if (attno > IndexRelationGetNumberOfKeyAttributes(index) ||
801 14318 : OidIsValid(index_getprocid(index, attno, GIST_FETCH_PROC)) ||
802 6608 : !OidIsValid(index_getprocid(index, attno, GIST_COMPRESS_PROC)))
803 5208 : return true;
804 : else
805 2628 : return false;
806 : }
|