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