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