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