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 : {
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 2 : for (i = 0; i < so->numKilled; i++)
80 : {
81 1 : offnum = so->killedItems[i];
82 1 : iid = PageGetItemId(page, offnum);
83 1 : ItemIdMarkDead(iid);
84 1 : killedsomething = true;
85 : }
86 :
87 1 : if (killedsomething)
88 : {
89 1 : GistMarkPageHasGarbage(page);
90 1 : MarkBufferDirtyHint(buffer, true);
91 : }
92 :
93 1 : UnlockReleaseBuffer(buffer);
94 :
95 : /*
96 : * Always reset the scan state, so we don't look for same items on other
97 : * pages.
98 : */
99 1 : 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 1064201 : gistindex_keytest(IndexScanDesc scan,
127 : IndexTuple tuple,
128 : Page page,
129 : OffsetNumber offset,
130 : bool *recheck_p,
131 : bool *recheck_distances_p)
132 : {
133 1064201 : GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
134 1064201 : GISTSTATE *giststate = so->giststate;
135 1064201 : ScanKey key = scan->keyData;
136 1064201 : int keySize = scan->numberOfKeys;
137 : IndexOrderByDistance *distance_p;
138 1064201 : Relation r = scan->indexRelation;
139 :
140 1064201 : *recheck_p = false;
141 1064201 : *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 1064201 : 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 1644482 : while (keySize > 0)
164 : {
165 : Datum datum;
166 : bool isNull;
167 :
168 1021372 : datum = index_getattr(tuple,
169 1021372 : key->sk_attno,
170 : giststate->leafTupdesc,
171 : &isNull);
172 :
173 1021372 : 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 23098 : if (key->sk_flags & SK_SEARCHNULL)
182 : {
183 23065 : if (GistPageIsLeaf(page) && !isNull)
184 441091 : return false;
185 : }
186 : else
187 : {
188 : Assert(key->sk_flags & SK_SEARCHNOTNULL);
189 33 : if (isNull)
190 3 : return false;
191 : }
192 : }
193 998274 : else if (isNull)
194 : {
195 1637 : return false;
196 : }
197 : else
198 : {
199 : Datum test;
200 : bool recheck;
201 : GISTENTRY de;
202 :
203 996637 : 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 996637 : recheck = true;
221 :
222 1993274 : test = FunctionCall5Coll(&key->sk_func,
223 : key->sk_collation,
224 : PointerGetDatum(&de),
225 : key->sk_argument,
226 996637 : UInt16GetDatum(key->sk_strategy),
227 : ObjectIdGetDatum(key->sk_subtype),
228 : PointerGetDatum(&recheck));
229 :
230 996637 : if (!DatumGetBool(test))
231 418244 : return false;
232 578393 : *recheck_p |= recheck;
233 : }
234 :
235 580281 : key++;
236 580281 : keySize--;
237 : }
238 :
239 : /* OK, it passes --- now let's compute the distances */
240 623110 : key = scan->orderByData;
241 623110 : distance_p = so->distances;
242 623110 : keySize = scan->numberOfOrderBys;
243 638878 : while (keySize > 0)
244 : {
245 : Datum datum;
246 : bool isNull;
247 :
248 15768 : datum = index_getattr(tuple,
249 15768 : key->sk_attno,
250 : giststate->leafTupdesc,
251 : &isNull);
252 :
253 15768 : if ((key->sk_flags & SK_ISNULL) || isNull)
254 : {
255 : /* Assume distance computes as null */
256 54 : distance_p->value = 0.0;
257 54 : distance_p->isnull = true;
258 : }
259 : else
260 : {
261 : Datum dist;
262 : bool recheck;
263 : GISTENTRY de;
264 :
265 15714 : 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 15714 : recheck = false;
286 31428 : dist = FunctionCall5Coll(&key->sk_func,
287 : key->sk_collation,
288 : PointerGetDatum(&de),
289 : key->sk_argument,
290 15714 : UInt16GetDatum(key->sk_strategy),
291 : ObjectIdGetDatum(key->sk_subtype),
292 : PointerGetDatum(&recheck));
293 15714 : *recheck_distances_p |= recheck;
294 15714 : distance_p->value = DatumGetFloat8(dist);
295 15714 : distance_p->isnull = false;
296 : }
297 :
298 15768 : key++;
299 15768 : distance_p++;
300 15768 : keySize--;
301 : }
302 :
303 623110 : 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 38813 : gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem,
330 : IndexOrderByDistance *myDistances, TIDBitmap *tbm, int64 *ntids)
331 : {
332 38813 : GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
333 38813 : GISTSTATE *giststate = so->giststate;
334 38813 : 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 38813 : buffer = ReadBuffer(scan->indexRelation, pageItem->blkno);
345 38813 : LockBuffer(buffer, GIST_SHARE);
346 38813 : PredicateLockPage(r, BufferGetBlockNumber(buffer), scan->xs_snapshot);
347 38813 : gistcheckpage(scan->indexRelation, buffer);
348 38813 : page = BufferGetPage(buffer);
349 38813 : 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 38813 : if (XLogRecPtrIsValid(pageItem->data.parentlsn) &&
358 71598 : (GistFollowRight(page) ||
359 35799 : 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 38813 : if (GistPageIsDeleted(page))
393 : {
394 0 : UnlockReleaseBuffer(buffer);
395 0 : return;
396 : }
397 :
398 38813 : so->nPageData = so->curPageData = 0;
399 38813 : scan->xs_hitup = NULL; /* might point into pageDataCxt */
400 38813 : if (so->pageDataCxt)
401 3042 : MemoryContextReset(so->pageDataCxt);
402 :
403 : /*
404 : * We save the LSN of the page as we read it, so that we know whether it
405 : * is safe to apply LP_DEAD hints to the page later. This allows us to
406 : * drop the pin for MVCC scans, which allows vacuum to avoid blocking.
407 : */
408 38813 : so->curPageLSN = BufferGetLSNAtomic(buffer);
409 :
410 : /*
411 : * check all tuples on page
412 : */
413 38813 : maxoff = PageGetMaxOffsetNumber(page);
414 1103015 : for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
415 : {
416 1064202 : 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 1064202 : if (scan->ignore_killed_tuples && ItemIdIsDead(iid))
427 441092 : continue;
428 :
429 1064201 : it = (IndexTuple) PageGetItem(page, iid);
430 :
431 : /*
432 : * Must call gistindex_keytest in tempCxt, and clean up any leftover
433 : * junk afterward.
434 : */
435 1064201 : oldcxt = MemoryContextSwitchTo(so->giststate->tempCxt);
436 :
437 1064201 : match = gistindex_keytest(scan, it, page, i,
438 : &recheck, &recheck_distances);
439 :
440 1064201 : MemoryContextSwitchTo(oldcxt);
441 1064201 : MemoryContextReset(so->giststate->tempCxt);
442 :
443 : /* Ignore tuple if it doesn't match */
444 1064201 : if (!match)
445 441091 : continue;
446 :
447 623110 : 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 198079 : tbm_add_tuples(tbm, &it->t_tid, 1, recheck);
454 198079 : (*ntids)++;
455 : }
456 425031 : else if (scan->numberOfOrderBys == 0 && GistPageIsLeaf(page))
457 : {
458 : /*
459 : * Non-ordered scan, so report tuples in so->pageData[]
460 : */
461 373630 : so->pageData[so->nPageData].heapPtr = it->t_tid;
462 373630 : so->pageData[so->nPageData].recheck = recheck;
463 373630 : 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 373630 : if (scan->xs_want_itup)
470 : {
471 258739 : oldcxt = MemoryContextSwitchTo(so->pageDataCxt);
472 517478 : so->pageData[so->nPageData].recontup =
473 258739 : gistFetchTuple(giststate, r, it);
474 258739 : MemoryContextSwitchTo(oldcxt);
475 : }
476 373630 : 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 51401 : int nOrderBys = scan->numberOfOrderBys;
487 :
488 51401 : oldcxt = MemoryContextSwitchTo(so->queueCxt);
489 :
490 : /* Create new GISTSearchItem for this item */
491 51401 : item = palloc(SizeOfGISTSearchItem(scan->numberOfOrderBys));
492 :
493 51401 : if (GistPageIsLeaf(page))
494 : {
495 : /* Creating heap-tuple GISTSearchItem */
496 14031 : item->blkno = InvalidBlockNumber;
497 14031 : item->data.heap.heapPtr = it->t_tid;
498 14031 : item->data.heap.recheck = recheck;
499 14031 : item->data.heap.recheckDistances = recheck_distances;
500 :
501 : /*
502 : * In an index-only scan, also fetch the data from the tuple.
503 : */
504 14031 : if (scan->xs_want_itup)
505 7110 : item->data.heap.recontup = gistFetchTuple(giststate, r, it);
506 : }
507 : else
508 : {
509 : /* Creating index-page GISTSearchItem */
510 37370 : 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 37370 : item->data.parentlsn = BufferGetLSNAtomic(buffer);
518 : }
519 :
520 : /* Insert it into the queue using new distance data */
521 51401 : memcpy(item->distances, so->distances,
522 : sizeof(item->distances[0]) * nOrderBys);
523 :
524 51401 : pairingheap_add(so->queue, &item->phNode);
525 :
526 51401 : MemoryContextSwitchTo(oldcxt);
527 : }
528 : }
529 :
530 38813 : 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 39243 : getNextGISTSearchItem(GISTScanOpaque so)
540 : {
541 : GISTSearchItem *item;
542 :
543 39243 : if (!pairingheap_is_empty(so->queue))
544 : {
545 36422 : item = (GISTSearchItem *) pairingheap_remove_first(so->queue);
546 : }
547 : else
548 : {
549 : /* Done when both heaps are empty */
550 2821 : item = NULL;
551 : }
552 :
553 : /* Return item; caller is responsible to pfree it */
554 39243 : return item;
555 : }
556 :
557 : /*
558 : * Fetch next heap tuple in an ordered search
559 : */
560 : static bool
561 644 : getNextNearest(IndexScanDesc scan)
562 : {
563 644 : GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
564 644 : bool res = false;
565 :
566 644 : if (scan->xs_hitup)
567 : {
568 : /* free previously returned tuple */
569 425 : pfree(scan->xs_hitup);
570 425 : scan->xs_hitup = NULL;
571 : }
572 :
573 : do
574 : {
575 810 : GISTSearchItem *item = getNextGISTSearchItem(so);
576 :
577 810 : if (!item)
578 21 : break;
579 :
580 789 : if (GISTSearchItemIsHeap(*item))
581 : {
582 : /* found a heap item at currently minimal distance */
583 623 : scan->xs_heaptid = item->data.heap.heapPtr;
584 623 : scan->xs_recheck = item->data.heap.recheck;
585 :
586 623 : index_store_float8_orderby_distances(scan, so->orderByTypes,
587 623 : item->distances,
588 623 : item->data.heap.recheckDistances);
589 :
590 : /* in an index-only scan, also return the reconstructed tuple. */
591 623 : if (scan->xs_want_itup)
592 459 : scan->xs_hitup = item->data.heap.recontup;
593 623 : res = true;
594 : }
595 : else
596 : {
597 : /* visit an index page, extract its items into queue */
598 166 : CHECK_FOR_INTERRUPTS();
599 :
600 166 : gistScanPage(scan, item, item->distances, NULL, NULL);
601 : }
602 :
603 789 : pfree(item);
604 789 : } while (!res);
605 :
606 644 : return res;
607 : }
608 :
609 : /*
610 : * gistgettuple() -- Get the next tuple in the scan
611 : */
612 : bool
613 376174 : gistgettuple(IndexScanDesc scan, ScanDirection dir)
614 : {
615 376174 : GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
616 :
617 376174 : if (dir != ForwardScanDirection)
618 0 : elog(ERROR, "GiST only supports forward scan direction");
619 :
620 376174 : if (!so->qual_ok)
621 0 : return false;
622 :
623 376174 : if (so->firstCall)
624 : {
625 : /* Begin the scan by processing the root page */
626 : GISTSearchItem fakeItem;
627 :
628 2193 : pgstat_count_index_scan(scan->indexRelation);
629 2193 : if (scan->instrument)
630 1053 : scan->instrument->nsearches++;
631 :
632 2193 : so->firstCall = false;
633 2193 : so->curPageData = so->nPageData = 0;
634 2193 : scan->xs_hitup = NULL;
635 2193 : if (so->pageDataCxt)
636 340 : MemoryContextReset(so->pageDataCxt);
637 :
638 2193 : fakeItem.blkno = GIST_ROOT_BLKNO;
639 2193 : memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
640 2193 : gistScanPage(scan, &fakeItem, NULL, NULL, NULL);
641 : }
642 :
643 376174 : if (scan->numberOfOrderBys > 0)
644 : {
645 : /* Must fetch tuples in strict distance order */
646 644 : return getNextNearest(scan);
647 : }
648 : else
649 : {
650 : /* Fetch tuples index-page-at-a-time */
651 : for (;;)
652 : {
653 379561 : if (so->curPageData < so->nPageData)
654 : {
655 373551 : if (scan->kill_prior_tuple && so->curPageData > 0)
656 : {
657 :
658 282 : if (so->killedItems == NULL)
659 : {
660 : MemoryContext oldCxt =
661 224 : MemoryContextSwitchTo(so->giststate->scanCxt);
662 :
663 224 : so->killedItems =
664 224 : (OffsetNumber *) palloc(MaxIndexTuplesPerPage
665 : * sizeof(OffsetNumber));
666 :
667 224 : MemoryContextSwitchTo(oldCxt);
668 : }
669 282 : if (so->numKilled < MaxIndexTuplesPerPage)
670 282 : so->killedItems[so->numKilled++] =
671 282 : so->pageData[so->curPageData - 1].offnum;
672 : }
673 : /* continuing to return tuples from a leaf page */
674 373551 : scan->xs_heaptid = so->pageData[so->curPageData].heapPtr;
675 373551 : scan->xs_recheck = so->pageData[so->curPageData].recheck;
676 :
677 : /* in an index-only scan, also return the reconstructed tuple */
678 373551 : if (scan->xs_want_itup)
679 258739 : scan->xs_hitup = so->pageData[so->curPageData].recontup;
680 :
681 373551 : so->curPageData++;
682 :
683 373551 : return true;
684 : }
685 :
686 : /*
687 : * Check the last returned tuple and add it to killedItems if
688 : * necessary
689 : */
690 6010 : if (scan->kill_prior_tuple
691 5 : && so->curPageData > 0
692 5 : && so->curPageData == so->nPageData)
693 : {
694 :
695 5 : if (so->killedItems == NULL)
696 : {
697 : MemoryContext oldCxt =
698 5 : MemoryContextSwitchTo(so->giststate->scanCxt);
699 :
700 5 : so->killedItems =
701 5 : (OffsetNumber *) palloc(MaxIndexTuplesPerPage
702 : * sizeof(OffsetNumber));
703 :
704 5 : MemoryContextSwitchTo(oldCxt);
705 : }
706 5 : if (so->numKilled < MaxIndexTuplesPerPage)
707 5 : so->killedItems[so->numKilled++] =
708 5 : so->pageData[so->curPageData - 1].offnum;
709 : }
710 : /* find and process the next index page */
711 : do
712 : {
713 : GISTSearchItem *item;
714 :
715 7579 : if ((so->curBlkno != InvalidBlockNumber) && (so->numKilled > 0))
716 1 : gistkillitems(scan);
717 :
718 7579 : item = getNextGISTSearchItem(so);
719 :
720 7579 : if (!item)
721 1979 : return false;
722 :
723 5600 : CHECK_FOR_INTERRUPTS();
724 :
725 : /* save current item BlockNumber for next gistkillitems() call */
726 5600 : 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 5600 : gistScanPage(scan, item, item->distances, NULL, NULL);
735 :
736 5600 : pfree(item);
737 5600 : } while (so->nPageData == 0);
738 : }
739 : }
740 : }
741 :
742 : /*
743 : * gistgetbitmap() -- Get a bitmap of all heap tuple locations
744 : */
745 : int64
746 821 : gistgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
747 : {
748 821 : GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
749 821 : int64 ntids = 0;
750 : GISTSearchItem fakeItem;
751 :
752 821 : if (!so->qual_ok)
753 0 : return 0;
754 :
755 821 : pgstat_count_index_scan(scan->indexRelation);
756 821 : if (scan->instrument)
757 821 : scan->instrument->nsearches++;
758 :
759 : /* Begin the scan by processing the root page */
760 821 : so->curPageData = so->nPageData = 0;
761 821 : scan->xs_hitup = NULL;
762 821 : if (so->pageDataCxt)
763 0 : MemoryContextReset(so->pageDataCxt);
764 :
765 821 : fakeItem.blkno = GIST_ROOT_BLKNO;
766 821 : memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
767 821 : 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 30033 : {
775 30854 : GISTSearchItem *item = getNextGISTSearchItem(so);
776 :
777 30854 : if (!item)
778 821 : break;
779 :
780 30033 : CHECK_FOR_INTERRUPTS();
781 :
782 30033 : gistScanPage(scan, item, item->distances, tbm, &ntids);
783 :
784 30033 : pfree(item);
785 : }
786 :
787 821 : 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 3927 : gistcanreturn(Relation index, int attno)
799 : {
800 7791 : if (attno > IndexRelationGetNumberOfKeyAttributes(index) ||
801 7177 : OidIsValid(index_getprocid(index, attno, GIST_FETCH_PROC)) ||
802 3313 : !OidIsValid(index_getprocid(index, attno, GIST_COMPRESS_PROC)))
803 2606 : return true;
804 : else
805 1321 : return false;
806 : }
|