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
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_hitup = 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.  The
451 			 * reconstructed tuples are stored in pageDataCxt.
452 			 */
453 			if (scan->xs_want_itup)
454 			{
455 				oldcxt = MemoryContextSwitchTo(so->pageDataCxt);
456 				so->pageData[so->nPageData].recontup =
457 					gistFetchTuple(giststate, r, it);
458 				MemoryContextSwitchTo(oldcxt);
459 			}
460 			so->nPageData++;
461 		}
462 		else
463 		{
464 			/*
465 			 * Must push item into search queue.  We get here for any lower
466 			 * index page, and also for heap tuples if doing an ordered
467 			 * search.
468 			 */
469 			GISTSearchItem *item;
470 			int			nOrderBys = scan->numberOfOrderBys;
471 
472 			oldcxt = MemoryContextSwitchTo(so->queueCxt);
473 
474 			/* Create new GISTSearchItem for this item */
475 			item = palloc(SizeOfGISTSearchItem(scan->numberOfOrderBys));
476 
477 			if (GistPageIsLeaf(page))
478 			{
479 				/* Creating heap-tuple GISTSearchItem */
480 				item->blkno = InvalidBlockNumber;
481 				item->data.heap.heapPtr = it->t_tid;
482 				item->data.heap.recheck = recheck;
483 				item->data.heap.recheckDistances = recheck_distances;
484 
485 				/*
486 				 * In an index-only scan, also fetch the data from the tuple.
487 				 */
488 				if (scan->xs_want_itup)
489 					item->data.heap.recontup = gistFetchTuple(giststate, r, it);
490 			}
491 			else
492 			{
493 				/* Creating index-page GISTSearchItem */
494 				item->blkno = ItemPointerGetBlockNumber(&it->t_tid);
495 
496 				/*
497 				 * LSN of current page is lsn of parent page for child. We
498 				 * only have a shared lock, so we need to get the LSN
499 				 * atomically.
500 				 */
501 				item->data.parentlsn = BufferGetLSNAtomic(buffer);
502 			}
503 
504 			/* Insert it into the queue using new distance data */
505 			memcpy(item->distances, so->distances,
506 				   sizeof(item->distances[0]) * nOrderBys);
507 
508 			pairingheap_add(so->queue, &item->phNode);
509 
510 			MemoryContextSwitchTo(oldcxt);
511 		}
512 	}
513 
514 	UnlockReleaseBuffer(buffer);
515 }
516 
517 /*
518  * Extract next item (in order) from search queue
519  *
520  * Returns a GISTSearchItem or NULL.  Caller must pfree item when done with it.
521  */
522 static GISTSearchItem *
getNextGISTSearchItem(GISTScanOpaque so)523 getNextGISTSearchItem(GISTScanOpaque so)
524 {
525 	GISTSearchItem *item;
526 
527 	if (!pairingheap_is_empty(so->queue))
528 	{
529 		item = (GISTSearchItem *) pairingheap_remove_first(so->queue);
530 	}
531 	else
532 	{
533 		/* Done when both heaps are empty */
534 		item = NULL;
535 	}
536 
537 	/* Return item; caller is responsible to pfree it */
538 	return item;
539 }
540 
541 /*
542  * Fetch next heap tuple in an ordered search
543  */
544 static bool
getNextNearest(IndexScanDesc scan)545 getNextNearest(IndexScanDesc scan)
546 {
547 	GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
548 	bool		res = false;
549 	int			i;
550 
551 	if (scan->xs_hitup)
552 	{
553 		/* free previously returned tuple */
554 		pfree(scan->xs_hitup);
555 		scan->xs_hitup = NULL;
556 	}
557 
558 	do
559 	{
560 		GISTSearchItem *item = getNextGISTSearchItem(so);
561 
562 		if (!item)
563 			break;
564 
565 		if (GISTSearchItemIsHeap(*item))
566 		{
567 			/* found a heap item at currently minimal distance */
568 			scan->xs_ctup.t_self = item->data.heap.heapPtr;
569 			scan->xs_recheck = item->data.heap.recheck;
570 			scan->xs_recheckorderby = item->data.heap.recheckDistances;
571 			for (i = 0; i < scan->numberOfOrderBys; i++)
572 			{
573 				if (so->orderByTypes[i] == FLOAT8OID)
574 				{
575 #ifndef USE_FLOAT8_BYVAL
576 					/* must free any old value to avoid memory leakage */
577 					if (!scan->xs_orderbynulls[i])
578 						pfree(DatumGetPointer(scan->xs_orderbyvals[i]));
579 #endif
580 					scan->xs_orderbyvals[i] = Float8GetDatum(item->distances[i].value);
581 					scan->xs_orderbynulls[i] = item->distances[i].isnull;
582 				}
583 				else if (so->orderByTypes[i] == FLOAT4OID)
584 				{
585 					/* convert distance function's result to ORDER BY type */
586 #ifndef USE_FLOAT4_BYVAL
587 					/* must free any old value to avoid memory leakage */
588 					if (!scan->xs_orderbynulls[i])
589 						pfree(DatumGetPointer(scan->xs_orderbyvals[i]));
590 #endif
591 					scan->xs_orderbyvals[i] = Float4GetDatum(item->distances[i].value);
592 					scan->xs_orderbynulls[i] = item->distances[i].isnull;
593 				}
594 				else
595 				{
596 					/*
597 					 * If the ordering operator's return value is anything
598 					 * else, we don't know how to convert the float8 bound
599 					 * calculated by the distance function to that.  The
600 					 * executor won't actually need the order by values we
601 					 * return here, if there are no lossy results, so only
602 					 * insist on converting if the *recheck flag is set.
603 					 */
604 					if (scan->xs_recheckorderby)
605 						elog(ERROR, "GiST operator family's FOR ORDER BY operator must return float8 or float4 if the distance function is lossy");
606 					scan->xs_orderbynulls[i] = true;
607 				}
608 			}
609 
610 			/* in an index-only scan, also return the reconstructed tuple. */
611 			if (scan->xs_want_itup)
612 				scan->xs_hitup = item->data.heap.recontup;
613 			res = true;
614 		}
615 		else
616 		{
617 			/* visit an index page, extract its items into queue */
618 			CHECK_FOR_INTERRUPTS();
619 
620 			gistScanPage(scan, item, item->distances, NULL, NULL);
621 		}
622 
623 		pfree(item);
624 	} while (!res);
625 
626 	return res;
627 }
628 
629 /*
630  * gistgettuple() -- Get the next tuple in the scan
631  */
632 bool
gistgettuple(IndexScanDesc scan,ScanDirection dir)633 gistgettuple(IndexScanDesc scan, ScanDirection dir)
634 {
635 	GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
636 
637 	if (dir != ForwardScanDirection)
638 		elog(ERROR, "GiST only supports forward scan direction");
639 
640 	if (!so->qual_ok)
641 		return false;
642 
643 	if (so->firstCall)
644 	{
645 		/* Begin the scan by processing the root page */
646 		GISTSearchItem fakeItem;
647 
648 		pgstat_count_index_scan(scan->indexRelation);
649 
650 		so->firstCall = false;
651 		so->curPageData = so->nPageData = 0;
652 		scan->xs_hitup = NULL;
653 		if (so->pageDataCxt)
654 			MemoryContextReset(so->pageDataCxt);
655 
656 		fakeItem.blkno = GIST_ROOT_BLKNO;
657 		memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
658 		gistScanPage(scan, &fakeItem, NULL, NULL, NULL);
659 	}
660 
661 	if (scan->numberOfOrderBys > 0)
662 	{
663 		/* Must fetch tuples in strict distance order */
664 		return getNextNearest(scan);
665 	}
666 	else
667 	{
668 		/* Fetch tuples index-page-at-a-time */
669 		for (;;)
670 		{
671 			if (so->curPageData < so->nPageData)
672 			{
673 				if (scan->kill_prior_tuple && so->curPageData > 0)
674 				{
675 
676 					if (so->killedItems == NULL)
677 					{
678 						MemoryContext oldCxt =
679 						MemoryContextSwitchTo(so->giststate->scanCxt);
680 
681 						so->killedItems =
682 							(OffsetNumber *) palloc(MaxIndexTuplesPerPage
683 													* sizeof(OffsetNumber));
684 
685 						MemoryContextSwitchTo(oldCxt);
686 					}
687 					if (so->numKilled < MaxIndexTuplesPerPage)
688 						so->killedItems[so->numKilled++] =
689 							so->pageData[so->curPageData - 1].offnum;
690 				}
691 				/* continuing to return tuples from a leaf page */
692 				scan->xs_ctup.t_self = so->pageData[so->curPageData].heapPtr;
693 				scan->xs_recheck = so->pageData[so->curPageData].recheck;
694 
695 				/* in an index-only scan, also return the reconstructed tuple */
696 				if (scan->xs_want_itup)
697 					scan->xs_hitup = so->pageData[so->curPageData].recontup;
698 
699 				so->curPageData++;
700 
701 				return true;
702 			}
703 
704 			/*
705 			 * Check the last returned tuple and add it to killitems if
706 			 * necessary
707 			 */
708 			if (scan->kill_prior_tuple
709 				&& so->curPageData > 0
710 				&& so->curPageData == so->nPageData)
711 			{
712 
713 				if (so->killedItems == NULL)
714 				{
715 					MemoryContext oldCxt =
716 					MemoryContextSwitchTo(so->giststate->scanCxt);
717 
718 					so->killedItems =
719 						(OffsetNumber *) palloc(MaxIndexTuplesPerPage
720 												* sizeof(OffsetNumber));
721 
722 					MemoryContextSwitchTo(oldCxt);
723 				}
724 				if (so->numKilled < MaxIndexTuplesPerPage)
725 					so->killedItems[so->numKilled++] =
726 						so->pageData[so->curPageData - 1].offnum;
727 			}
728 			/* find and process the next index page */
729 			do
730 			{
731 				GISTSearchItem *item;
732 
733 				if ((so->curBlkno != InvalidBlockNumber) && (so->numKilled > 0))
734 					gistkillitems(scan);
735 
736 				item = getNextGISTSearchItem(so);
737 
738 				if (!item)
739 					return false;
740 
741 				CHECK_FOR_INTERRUPTS();
742 
743 				/* save current item BlockNumber for next gistkillitems() call */
744 				so->curBlkno = item->blkno;
745 
746 				/*
747 				 * While scanning a leaf page, ItemPointers of matching heap
748 				 * tuples are stored in so->pageData.  If there are any on
749 				 * this page, we fall out of the inner "do" and loop around to
750 				 * return them.
751 				 */
752 				gistScanPage(scan, item, item->distances, NULL, NULL);
753 
754 				pfree(item);
755 			} while (so->nPageData == 0);
756 		}
757 	}
758 }
759 
760 /*
761  * gistgetbitmap() -- Get a bitmap of all heap tuple locations
762  */
763 int64
gistgetbitmap(IndexScanDesc scan,TIDBitmap * tbm)764 gistgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
765 {
766 	GISTScanOpaque so = (GISTScanOpaque) scan->opaque;
767 	int64		ntids = 0;
768 	GISTSearchItem fakeItem;
769 
770 	if (!so->qual_ok)
771 		return 0;
772 
773 	pgstat_count_index_scan(scan->indexRelation);
774 
775 	/* Begin the scan by processing the root page */
776 	so->curPageData = so->nPageData = 0;
777 	scan->xs_hitup = NULL;
778 	if (so->pageDataCxt)
779 		MemoryContextReset(so->pageDataCxt);
780 
781 	fakeItem.blkno = GIST_ROOT_BLKNO;
782 	memset(&fakeItem.data.parentlsn, 0, sizeof(GistNSN));
783 	gistScanPage(scan, &fakeItem, NULL, tbm, &ntids);
784 
785 	/*
786 	 * While scanning a leaf page, ItemPointers of matching heap tuples will
787 	 * be stored directly into tbm, so we don't need to deal with them here.
788 	 */
789 	for (;;)
790 	{
791 		GISTSearchItem *item = getNextGISTSearchItem(so);
792 
793 		if (!item)
794 			break;
795 
796 		CHECK_FOR_INTERRUPTS();
797 
798 		gistScanPage(scan, item, item->distances, tbm, &ntids);
799 
800 		pfree(item);
801 	}
802 
803 	return ntids;
804 }
805 
806 /*
807  * Can we do index-only scans on the given index column?
808  *
809  * Opclasses that implement a fetch function support index-only scans.
810  */
811 bool
gistcanreturn(Relation index,int attno)812 gistcanreturn(Relation index, int attno)
813 {
814 	if (OidIsValid(index_getprocid(index, attno, GIST_FETCH_PROC)))
815 		return true;
816 	else
817 		return false;
818 }
819