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