1 /*-------------------------------------------------------------------------
4 * fetch tuples from a GiST scan.
7 * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
8 * Portions Copyright (c) 1994, Regents of the University of California
13 *-------------------------------------------------------------------------
17 #include "access/gist_private.h"
18 #include "access/relscan.h"
19 #include "executor/execdebug.h"
20 #include "miscadmin.h"
22 #include "storage/bufmgr.h"
23 #include "utils/memutils.h"
26 static OffsetNumber
gistfindnext(IndexScanDesc scan
, OffsetNumber n
);
27 static int64
gistnext(IndexScanDesc scan
, TIDBitmap
*tbm
);
28 static bool gistindex_keytest(IndexTuple tuple
, IndexScanDesc scan
,
32 killtuple(Relation r
, GISTScanOpaque so
, ItemPointer iptr
)
37 LockBuffer(so
->curbuf
, GIST_SHARE
);
38 gistcheckpage(r
, so
->curbuf
);
39 p
= (Page
) BufferGetPage(so
->curbuf
);
41 if (XLByteEQ(so
->stack
->lsn
, PageGetLSN(p
)))
43 /* page unchanged, so all is simple */
44 offset
= ItemPointerGetOffsetNumber(iptr
);
45 ItemIdMarkDead(PageGetItemId(p
, offset
));
46 SetBufferCommitInfoNeedsSave(so
->curbuf
);
50 OffsetNumber maxoff
= PageGetMaxOffsetNumber(p
);
52 for (offset
= FirstOffsetNumber
; offset
<= maxoff
; offset
= OffsetNumberNext(offset
))
54 IndexTuple ituple
= (IndexTuple
) PageGetItem(p
, PageGetItemId(p
, offset
));
56 if (ItemPointerEquals(&(ituple
->t_tid
), iptr
))
59 ItemIdMarkDead(PageGetItemId(p
, offset
));
60 SetBufferCommitInfoNeedsSave(so
->curbuf
);
66 LockBuffer(so
->curbuf
, GIST_UNLOCK
);
70 * gistgettuple() -- Get the next tuple in the scan
73 gistgettuple(PG_FUNCTION_ARGS
)
75 IndexScanDesc scan
= (IndexScanDesc
) PG_GETARG_POINTER(0);
76 ScanDirection dir
= (ScanDirection
) PG_GETARG_INT32(1);
80 so
= (GISTScanOpaque
) scan
->opaque
;
82 if (dir
!= ForwardScanDirection
)
83 elog(ERROR
, "GiST doesn't support other scan directions than forward");
86 * If we have produced an index tuple in the past and the executor has
87 * informed us we need to mark it as "killed", do so now.
89 if (scan
->kill_prior_tuple
&& ItemPointerIsValid(&(so
->curpos
)))
90 killtuple(scan
->indexRelation
, so
, &(so
->curpos
));
93 * Get the next tuple that matches the search key.
95 res
= (gistnext(scan
, NULL
) > 0);
101 gistgetbitmap(PG_FUNCTION_ARGS
)
103 IndexScanDesc scan
= (IndexScanDesc
) PG_GETARG_POINTER(0);
104 TIDBitmap
*tbm
= (TIDBitmap
*) PG_GETARG_POINTER(1);
107 ntids
= gistnext(scan
, tbm
);
109 PG_RETURN_INT64(ntids
);
113 * Fetch tuple(s) that match the search key; this can be invoked
114 * either to fetch the first such tuple or subsequent matching tuples.
116 * This function is used by both gistgettuple and gistgetbitmap. When
117 * invoked from gistgettuple, tbm is null and the next matching tuple
118 * is returned in scan->xs_ctup.t_self. When invoked from getbitmap,
119 * tbm is non-null and all matching tuples are added to tbm before
120 * returning. In both cases, the function result is the number of
123 * If scan specifies to skip killed tuples, continue looping until we find a
124 * non-killed tuple that matches the search key.
127 gistnext(IndexScanDesc scan
, TIDBitmap
*tbm
)
132 GISTSearchStack
*stk
;
134 GISTPageOpaque opaque
;
137 so
= (GISTScanOpaque
) scan
->opaque
;
139 if (so
->qual_ok
== false)
142 if (so
->curbuf
== InvalidBuffer
)
144 if (ItemPointerIsValid(&so
->curpos
) == false)
146 /* Being asked to fetch the first entry, so start at the root */
147 Assert(so
->curbuf
== InvalidBuffer
);
148 Assert(so
->stack
== NULL
);
150 so
->curbuf
= ReadBuffer(scan
->indexRelation
, GIST_ROOT_BLKNO
);
152 stk
= so
->stack
= (GISTSearchStack
*) palloc0(sizeof(GISTSearchStack
));
155 stk
->block
= GIST_ROOT_BLKNO
;
157 pgstat_count_index_scan(scan
->indexRelation
);
161 /* scan is finished */
167 * check stored pointers from last visit
169 if (so
->nPageData
> 0)
172 * gistgetmulti never should go here
176 if (so
->curPageData
< so
->nPageData
)
178 scan
->xs_ctup
.t_self
= so
->pageData
[so
->curPageData
].heapPtr
;
179 scan
->xs_recheck
= so
->pageData
[so
->curPageData
].recheck
;
181 ItemPointerSet(&so
->curpos
,
182 BufferGetBlockNumber(so
->curbuf
),
183 so
->pageData
[so
->curPageData
].pageOffset
);
192 * Go to the next page
194 stk
= so
->stack
->next
;
198 /* If we're out of stack entries, we're done */
199 if (so
->stack
== NULL
)
201 ReleaseBuffer(so
->curbuf
);
202 so
->curbuf
= InvalidBuffer
;
206 so
->curbuf
= ReleaseAndReadBuffer(so
->curbuf
,
214 CHECK_FOR_INTERRUPTS();
216 /* First of all, we need lock buffer */
217 Assert(so
->curbuf
!= InvalidBuffer
);
218 LockBuffer(so
->curbuf
, GIST_SHARE
);
219 gistcheckpage(scan
->indexRelation
, so
->curbuf
);
220 p
= BufferGetPage(so
->curbuf
);
221 opaque
= GistPageGetOpaque(p
);
223 /* remember lsn to identify page changed for tuple's killing */
224 so
->stack
->lsn
= PageGetLSN(p
);
226 /* check page split, occured since visit to parent */
227 if (!XLogRecPtrIsInvalid(so
->stack
->parentlsn
) &&
228 XLByteLT(so
->stack
->parentlsn
, opaque
->nsn
) &&
229 opaque
->rightlink
!= InvalidBlockNumber
/* sanity check */ &&
230 (so
->stack
->next
== NULL
|| so
->stack
->next
->block
!= opaque
->rightlink
) /* check if already
233 /* detect page split, follow right link to add pages */
235 stk
= (GISTSearchStack
*) palloc(sizeof(GISTSearchStack
));
236 stk
->next
= so
->stack
->next
;
237 stk
->block
= opaque
->rightlink
;
238 stk
->parentlsn
= so
->stack
->parentlsn
;
239 memset(&(stk
->lsn
), 0, sizeof(GistNSN
));
240 so
->stack
->next
= stk
;
243 /* if page is empty, then just skip it */
246 LockBuffer(so
->curbuf
, GIST_UNLOCK
);
247 stk
= so
->stack
->next
;
251 if (so
->stack
== NULL
)
253 ReleaseBuffer(so
->curbuf
);
254 so
->curbuf
= InvalidBuffer
;
258 so
->curbuf
= ReleaseAndReadBuffer(so
->curbuf
, scan
->indexRelation
,
263 n
= FirstOffsetNumber
;
265 /* wonderful, we can look at page */
266 so
->nPageData
= so
->curPageData
= 0;
270 n
= gistfindnext(scan
, n
);
272 if (!OffsetNumberIsValid(n
))
275 * If we was called from gistgettuple and current buffer
276 * contains something matched then make a recursive call - it
277 * will return ItemPointer from so->pageData. But we save
278 * buffer pinned to support tuple's killing
280 if (!tbm
&& so
->nPageData
> 0)
282 LockBuffer(so
->curbuf
, GIST_UNLOCK
);
283 return gistnext(scan
, NULL
);
287 * We ran out of matching index entries on the current page,
288 * so pop the top stack entry and use it to continue the
291 LockBuffer(so
->curbuf
, GIST_UNLOCK
);
292 stk
= so
->stack
->next
;
296 /* If we're out of stack entries, we're done */
298 if (so
->stack
== NULL
)
300 ReleaseBuffer(so
->curbuf
);
301 so
->curbuf
= InvalidBuffer
;
305 so
->curbuf
= ReleaseAndReadBuffer(so
->curbuf
,
312 if (GistPageIsLeaf(p
))
315 * We've found a matching index entry in a leaf page, so
316 * return success. Note that we keep "curbuf" pinned so that
317 * we can efficiently resume the index scan later.
320 if (!(scan
->ignore_killed_tuples
&&
321 ItemIdIsDead(PageGetItemId(p
, n
))))
323 it
= (IndexTuple
) PageGetItem(p
, PageGetItemId(p
, n
));
326 tbm_add_tuples(tbm
, &it
->t_tid
, 1, scan
->xs_recheck
);
329 so
->pageData
[so
->nPageData
].heapPtr
= it
->t_tid
;
330 so
->pageData
[so
->nPageData
].pageOffset
= n
;
331 so
->pageData
[so
->nPageData
].recheck
= scan
->xs_recheck
;
339 * We've found an entry in an internal node whose key is
340 * consistent with the search key, so push it to stack
342 stk
= (GISTSearchStack
*) palloc(sizeof(GISTSearchStack
));
344 it
= (IndexTuple
) PageGetItem(p
, PageGetItemId(p
, n
));
345 stk
->block
= ItemPointerGetBlockNumber(&(it
->t_tid
));
346 memset(&(stk
->lsn
), 0, sizeof(GistNSN
));
347 stk
->parentlsn
= so
->stack
->lsn
;
349 stk
->next
= so
->stack
->next
;
350 so
->stack
->next
= stk
;
353 n
= OffsetNumberNext(n
);
361 * gistindex_keytest() -- does this index tuple satisfy the scan key(s)?
363 * On success return for a leaf tuple, scan->xs_recheck is set to indicate
364 * whether recheck is needed. We recheck if any of the consistent() functions
367 * We must decompress the key in the IndexTuple before passing it to the
368 * sk_func (and we have previously overwritten the sk_func to use the
369 * user-defined Consistent method, so we actually are invoking that).
371 * Note that this function is always invoked in a short-lived memory context,
372 * so we don't need to worry about cleaning up allocated memory, either here
373 * or in the implementation of any Consistent methods.
376 gistindex_keytest(IndexTuple tuple
,
380 int keySize
= scan
->numberOfKeys
;
381 ScanKey key
= scan
->keyData
;
382 Relation r
= scan
->indexRelation
;
385 GISTSTATE
*giststate
;
387 so
= (GISTScanOpaque
) scan
->opaque
;
388 giststate
= so
->giststate
;
389 p
= BufferGetPage(so
->curbuf
);
391 IncrIndexProcessed();
393 scan
->xs_recheck
= false;
396 * Tuple doesn't restore after crash recovery because of incomplete insert
398 if (!GistPageIsLeaf(p
) && GistTupleIsInvalid(tuple
))
409 datum
= index_getattr(tuple
,
414 if (key
->sk_flags
& SK_ISNULL
)
417 * On non-leaf page we can't conclude that child hasn't NULL
418 * values because of assumption in GiST: uinon (VAL, NULL) is VAL
419 * But if on non-leaf page key IS NULL then all childs has NULL.
422 Assert(key
->sk_flags
& SK_SEARCHNULL
);
424 if (GistPageIsLeaf(p
) && !isNull
)
433 gistdentryinit(giststate
, key
->sk_attno
- 1, &de
,
438 * Call the Consistent function to evaluate the test. The
439 * arguments are the index datum (as a GISTENTRY*), the comparison
440 * datum, the comparison operator's strategy number and subtype
441 * from pg_amop, and the recheck flag.
443 * (Presently there's no need to pass the subtype since it'll
444 * always be zero, but might as well pass it for possible future
447 * We initialize the recheck flag to true (the safest assumption)
448 * in case the Consistent function forgets to set it.
452 test
= FunctionCall5(&key
->sk_func
,
453 PointerGetDatum(&de
),
455 Int32GetDatum(key
->sk_strategy
),
456 ObjectIdGetDatum(key
->sk_subtype
),
457 PointerGetDatum(&recheck
));
459 if (!DatumGetBool(test
))
461 scan
->xs_recheck
|= recheck
;
472 * Return the offset of the first index entry that is consistent with
473 * the search key after offset 'n' in the current page. If there are
474 * no more consistent entries, return InvalidOffsetNumber.
475 * On success, scan->xs_recheck is set correctly, too.
476 * Page should be locked....
479 gistfindnext(IndexScanDesc scan
, OffsetNumber n
)
484 MemoryContext oldcxt
;
487 so
= (GISTScanOpaque
) scan
->opaque
;
488 p
= BufferGetPage(so
->curbuf
);
489 maxoff
= PageGetMaxOffsetNumber(p
);
492 * Make sure we're in a short-lived memory context when we invoke a
493 * user-supplied GiST method in gistindex_keytest(), so we don't leak
496 oldcxt
= MemoryContextSwitchTo(so
->tempCxt
);
498 while (n
>= FirstOffsetNumber
&& n
<= maxoff
)
500 it
= (IndexTuple
) PageGetItem(p
, PageGetItemId(p
, n
));
501 if (gistindex_keytest(it
, scan
, n
))
504 n
= OffsetNumberNext(n
);
507 MemoryContextSwitchTo(oldcxt
);
508 MemoryContextReset(so
->tempCxt
);
511 * If we found a matching entry, return its offset; otherwise return
512 * InvalidOffsetNumber to inform the caller to go to the next page.
514 if (n
>= FirstOffsetNumber
&& n
<= maxoff
)
517 return InvalidOffsetNumber
;