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