1 /*-------------------------------------------------------------------------
4 * routines for scanning SP-GiST indexes
7 * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
8 * Portions Copyright (c) 1994, Regents of the University of California
11 * src/backend/access/spgist/spgscan.c
13 *-------------------------------------------------------------------------
18 #include "access/relscan.h"
19 #include "access/spgist_private.h"
20 #include "miscadmin.h"
21 #include "storage/bufmgr.h"
22 #include "utils/datum.h"
23 #include "utils/memutils.h"
24 #include "utils/rel.h"
27 typedef void (*storeRes_func) (SpGistScanOpaque so, ItemPointer heapPtr,
28 Datum leafValue, bool isnull, bool recheck);
30 typedef struct ScanStackEntry
32 Datum reconstructedValue; /* value reconstructed from parent */
33 void *traversalValue; /* opclass-specific traverse value */
34 int level; /* level of items on this page */
35 ItemPointerData ptr; /* block and offset to scan from */
39 /* Free a ScanStackEntry */
41 freeScanStackEntry(SpGistScanOpaque so, ScanStackEntry *stackEntry)
43 if (!so->state.attType.attbyval &&
44 DatumGetPointer(stackEntry->reconstructedValue) != NULL)
45 pfree(DatumGetPointer(stackEntry->reconstructedValue));
46 if (stackEntry->traversalValue)
47 pfree(stackEntry->traversalValue);
52 /* Free the entire stack */
54 freeScanStack(SpGistScanOpaque so)
58 foreach(lc, so->scanStack)
60 freeScanStackEntry(so, (ScanStackEntry *) lfirst(lc));
62 list_free(so->scanStack);
67 * Initialize scanStack to search the root page, resetting
68 * any previously active scan
71 resetSpGistScanOpaque(SpGistScanOpaque so)
73 ScanStackEntry *startEntry;
79 /* Stack a work item to scan the null index entries */
80 startEntry = (ScanStackEntry *) palloc0(sizeof(ScanStackEntry));
81 ItemPointerSet(&startEntry->ptr, SPGIST_NULL_BLKNO, FirstOffsetNumber);
82 so->scanStack = lappend(so->scanStack, startEntry);
85 if (so->searchNonNulls)
87 /* Stack a work item to scan the non-null index entries */
88 startEntry = (ScanStackEntry *) palloc0(sizeof(ScanStackEntry));
89 ItemPointerSet(&startEntry->ptr, SPGIST_ROOT_BLKNO, FirstOffsetNumber);
90 so->scanStack = lappend(so->scanStack, startEntry);
95 /* Must pfree IndexTuples to avoid memory leak */
98 for (i = 0; i < so->nPtrs; i++)
99 pfree(so->indexTups[i]);
101 so->iPtr = so->nPtrs = 0;
105 * Prepare scan keys in SpGistScanOpaque from caller-given scan keys
107 * Sets searchNulls, searchNonNulls, numberOfKeys, keyData fields of *so.
109 * The point here is to eliminate null-related considerations from what the
110 * opclass consistent functions need to deal with. We assume all SPGiST-
111 * indexable operators are strict, so any null RHS value makes the scan
112 * condition unsatisfiable. We also pull out any IS NULL/IS NOT NULL
113 * conditions; their effect is reflected into searchNulls/searchNonNulls.
116 spgPrepareScanKeys(IndexScanDesc scan)
118 SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque;
125 if (scan->numberOfKeys <= 0)
127 /* If no quals, whole-index scan is required */
128 so->searchNulls = true;
129 so->searchNonNulls = true;
130 so->numberOfKeys = 0;
134 /* Examine the given quals */
136 haveIsNull = haveNotNull = false;
138 for (i = 0; i < scan->numberOfKeys; i++)
140 ScanKey skey = &scan->keyData[i];
142 if (skey->sk_flags & SK_SEARCHNULL)
144 else if (skey->sk_flags & SK_SEARCHNOTNULL)
146 else if (skey->sk_flags & SK_ISNULL)
148 /* ordinary qual with null argument - unsatisfiable */
154 /* ordinary qual, propagate into so->keyData */
155 so->keyData[nkeys++] = *skey;
156 /* this effectively creates a not-null requirement */
161 /* IS NULL in combination with something else is unsatisfiable */
162 if (haveIsNull && haveNotNull)
168 so->searchNulls = haveIsNull;
169 so->searchNonNulls = haveNotNull;
170 so->numberOfKeys = nkeys;
174 so->searchNulls = false;
175 so->searchNonNulls = false;
176 so->numberOfKeys = 0;
181 spgbeginscan(Relation rel, int keysz, int orderbysz)
186 scan = RelationGetIndexScan(rel, keysz, 0);
188 so = (SpGistScanOpaque) palloc0(sizeof(SpGistScanOpaqueData));
190 so->keyData = (ScanKey) palloc(sizeof(ScanKeyData) * keysz);
193 initSpGistState(&so->state, scan->indexRelation);
194 so->tempCxt = AllocSetContextCreate(CurrentMemoryContext,
195 "SP-GiST search temporary context",
196 ALLOCSET_DEFAULT_MINSIZE,
197 ALLOCSET_DEFAULT_INITSIZE,
198 ALLOCSET_DEFAULT_MAXSIZE);
200 /* Set up indexTupDesc and xs_itupdesc in case it's an index-only scan */
201 so->indexTupDesc = scan->xs_itupdesc = RelationGetDescr(rel);
209 spgrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
210 ScanKey orderbys, int norderbys)
212 SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque;
214 /* copy scankeys into local storage */
215 if (scankey && scan->numberOfKeys > 0)
217 memmove(scan->keyData, scankey,
218 scan->numberOfKeys * sizeof(ScanKeyData));
221 /* preprocess scankeys, set up the representation in *so */
222 spgPrepareScanKeys(scan);
224 /* set up starting stack entries */
225 resetSpGistScanOpaque(so);
229 spgendscan(IndexScanDesc scan)
231 SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque;
233 MemoryContextDelete(so->tempCxt);
237 * Test whether a leaf tuple satisfies all the scan keys
239 * *leafValue is set to the reconstructed datum, if provided
240 * *recheck is set true if any of the operators are lossy
243 spgLeafTest(Relation index, SpGistScanOpaque so,
244 SpGistLeafTuple leafTuple, bool isnull,
245 int level, Datum reconstructedValue,
246 void *traversalValue,
247 Datum *leafValue, bool *recheck)
251 spgLeafConsistentIn in;
252 spgLeafConsistentOut out;
254 MemoryContext oldCtx;
258 /* Should not have arrived on a nulls page unless nulls are wanted */
259 Assert(so->searchNulls);
260 *leafValue = (Datum) 0;
265 leafDatum = SGLTDATUM(leafTuple, &so->state);
267 /* use temp context for calling leaf_consistent */
268 oldCtx = MemoryContextSwitchTo(so->tempCxt);
270 in.scankeys = so->keyData;
271 in.nkeys = so->numberOfKeys;
272 in.reconstructedValue = reconstructedValue;
273 in.traversalValue = traversalValue;
275 in.returnData = so->want_itup;
276 in.leafDatum = leafDatum;
278 out.leafValue = (Datum) 0;
281 procinfo = index_getprocinfo(index, 1, SPGIST_LEAF_CONSISTENT_PROC);
282 result = DatumGetBool(FunctionCall2Coll(procinfo,
283 index->rd_indcollation[0],
284 PointerGetDatum(&in),
285 PointerGetDatum(&out)));
287 *leafValue = out.leafValue;
288 *recheck = out.recheck;
290 MemoryContextSwitchTo(oldCtx);
296 * Walk the tree and report all tuples passing the scan quals to the storeRes
299 * If scanWholeIndex is true, we'll do just that. If not, we'll stop at the
300 * next page boundary once we have reported at least one tuple.
303 spgWalk(Relation index, SpGistScanOpaque so, bool scanWholeIndex,
304 storeRes_func storeRes, Snapshot snapshot)
306 Buffer buffer = InvalidBuffer;
307 bool reportedSome = false;
309 while (scanWholeIndex || !reportedSome)
311 ScanStackEntry *stackEntry;
317 /* Pull next to-do item from the list */
318 if (so->scanStack == NIL)
319 break; /* there are no more pages to scan */
321 stackEntry = (ScanStackEntry *) linitial(so->scanStack);
322 so->scanStack = list_delete_first(so->scanStack);
325 /* Check for interrupts, just in case of infinite loop */
326 CHECK_FOR_INTERRUPTS();
328 blkno = ItemPointerGetBlockNumber(&stackEntry->ptr);
329 offset = ItemPointerGetOffsetNumber(&stackEntry->ptr);
331 if (buffer == InvalidBuffer)
333 buffer = ReadBuffer(index, blkno);
334 LockBuffer(buffer, BUFFER_LOCK_SHARE);
336 else if (blkno != BufferGetBlockNumber(buffer))
338 UnlockReleaseBuffer(buffer);
339 buffer = ReadBuffer(index, blkno);
340 LockBuffer(buffer, BUFFER_LOCK_SHARE);
342 /* else new pointer points to the same page, no work needed */
344 page = BufferGetPage(buffer);
345 TestForOldSnapshot(snapshot, index, page);
347 isnull = SpGistPageStoresNulls(page) ? true : false;
349 if (SpGistPageIsLeaf(page))
351 SpGistLeafTuple leafTuple;
352 OffsetNumber max = PageGetMaxOffsetNumber(page);
353 Datum leafValue = (Datum) 0;
354 bool recheck = false;
356 if (SpGistBlockIsRoot(blkno))
358 /* When root is a leaf, examine all its tuples */
359 for (offset = FirstOffsetNumber; offset <= max; offset++)
361 leafTuple = (SpGistLeafTuple)
362 PageGetItem(page, PageGetItemId(page, offset));
363 if (leafTuple->tupstate != SPGIST_LIVE)
365 /* all tuples on root should be live */
366 elog(ERROR, "unexpected SPGiST tuple state: %d",
367 leafTuple->tupstate);
370 Assert(ItemPointerIsValid(&leafTuple->heapPtr));
371 if (spgLeafTest(index, so,
374 stackEntry->reconstructedValue,
375 stackEntry->traversalValue,
379 storeRes(so, &leafTuple->heapPtr,
380 leafValue, isnull, recheck);
387 /* Normal case: just examine the chain we arrived at */
388 while (offset != InvalidOffsetNumber)
390 Assert(offset >= FirstOffsetNumber && offset <= max);
391 leafTuple = (SpGistLeafTuple)
392 PageGetItem(page, PageGetItemId(page, offset));
393 if (leafTuple->tupstate != SPGIST_LIVE)
395 if (leafTuple->tupstate == SPGIST_REDIRECT)
397 /* redirection tuple should be first in chain */
398 Assert(offset == ItemPointerGetOffsetNumber(&stackEntry->ptr));
399 /* transfer attention to redirect point */
400 stackEntry->ptr = ((SpGistDeadTuple) leafTuple)->pointer;
401 Assert(ItemPointerGetBlockNumber(&stackEntry->ptr) != SPGIST_METAPAGE_BLKNO);
404 if (leafTuple->tupstate == SPGIST_DEAD)
406 /* dead tuple should be first in chain */
407 Assert(offset == ItemPointerGetOffsetNumber(&stackEntry->ptr));
408 /* No live entries on this page */
409 Assert(leafTuple->nextOffset == InvalidOffsetNumber);
412 /* We should not arrive at a placeholder */
413 elog(ERROR, "unexpected SPGiST tuple state: %d",
414 leafTuple->tupstate);
417 Assert(ItemPointerIsValid(&leafTuple->heapPtr));
418 if (spgLeafTest(index, so,
421 stackEntry->reconstructedValue,
422 stackEntry->traversalValue,
426 storeRes(so, &leafTuple->heapPtr,
427 leafValue, isnull, recheck);
431 offset = leafTuple->nextOffset;
435 else /* page is inner */
437 SpGistInnerTuple innerTuple;
438 spgInnerConsistentIn in;
439 spgInnerConsistentOut out;
441 SpGistNodeTuple *nodes;
442 SpGistNodeTuple node;
444 MemoryContext oldCtx;
446 innerTuple = (SpGistInnerTuple) PageGetItem(page,
447 PageGetItemId(page, offset));
449 if (innerTuple->tupstate != SPGIST_LIVE)
451 if (innerTuple->tupstate == SPGIST_REDIRECT)
453 /* transfer attention to redirect point */
454 stackEntry->ptr = ((SpGistDeadTuple) innerTuple)->pointer;
455 Assert(ItemPointerGetBlockNumber(&stackEntry->ptr) != SPGIST_METAPAGE_BLKNO);
458 elog(ERROR, "unexpected SPGiST tuple state: %d",
459 innerTuple->tupstate);
462 /* use temp context for calling inner_consistent */
463 oldCtx = MemoryContextSwitchTo(so->tempCxt);
465 in.scankeys = so->keyData;
466 in.nkeys = so->numberOfKeys;
467 in.reconstructedValue = stackEntry->reconstructedValue;
468 in.traversalMemoryContext = oldCtx;
469 in.traversalValue = stackEntry->traversalValue;
470 in.level = stackEntry->level;
471 in.returnData = so->want_itup;
472 in.allTheSame = innerTuple->allTheSame;
473 in.hasPrefix = (innerTuple->prefixSize > 0);
474 in.prefixDatum = SGITDATUM(innerTuple, &so->state);
475 in.nNodes = innerTuple->nNodes;
476 in.nodeLabels = spgExtractNodeLabels(&so->state, innerTuple);
478 /* collect node pointers */
479 nodes = (SpGistNodeTuple *) palloc(sizeof(SpGistNodeTuple) * in.nNodes);
480 SGITITERATE(innerTuple, i, node)
485 memset(&out, 0, sizeof(out));
489 /* use user-defined inner consistent method */
490 procinfo = index_getprocinfo(index, 1, SPGIST_INNER_CONSISTENT_PROC);
491 FunctionCall2Coll(procinfo,
492 index->rd_indcollation[0],
493 PointerGetDatum(&in),
494 PointerGetDatum(&out));
498 /* force all children to be visited */
499 out.nNodes = in.nNodes;
500 out.nodeNumbers = (int *) palloc(sizeof(int) * in.nNodes);
501 for (i = 0; i < in.nNodes; i++)
502 out.nodeNumbers[i] = i;
505 MemoryContextSwitchTo(oldCtx);
507 /* If allTheSame, they should all or none of 'em match */
508 if (innerTuple->allTheSame)
509 if (out.nNodes != 0 && out.nNodes != in.nNodes)
510 elog(ERROR, "inconsistent inner_consistent results for allTheSame inner tuple");
512 for (i = 0; i < out.nNodes; i++)
514 int nodeN = out.nodeNumbers[i];
516 Assert(nodeN >= 0 && nodeN < in.nNodes);
517 if (ItemPointerIsValid(&nodes[nodeN]->t_tid))
519 ScanStackEntry *newEntry;
521 /* Create new work item for this node */
522 newEntry = palloc(sizeof(ScanStackEntry));
523 newEntry->ptr = nodes[nodeN]->t_tid;
525 newEntry->level = stackEntry->level + out.levelAdds[i];
527 newEntry->level = stackEntry->level;
528 /* Must copy value out of temp context */
529 if (out.reconstructedValues)
530 newEntry->reconstructedValue =
531 datumCopy(out.reconstructedValues[i],
532 so->state.attType.attbyval,
533 so->state.attType.attlen);
535 newEntry->reconstructedValue = (Datum) 0;
538 * Elements of out.traversalValues should be allocated in
539 * in.traversalMemoryContext, which is actually a long
540 * lived context of index scan.
542 newEntry->traversalValue = (out.traversalValues) ?
543 out.traversalValues[i] : NULL;
545 so->scanStack = lcons(newEntry, so->scanStack);
550 /* done with this scan stack entry */
551 freeScanStackEntry(so, stackEntry);
552 /* clear temp context before proceeding to the next one */
553 MemoryContextReset(so->tempCxt);
556 if (buffer != InvalidBuffer)
557 UnlockReleaseBuffer(buffer);
560 /* storeRes subroutine for getbitmap case */
562 storeBitmap(SpGistScanOpaque so, ItemPointer heapPtr,
563 Datum leafValue, bool isnull, bool recheck)
565 tbm_add_tuples(so->tbm, heapPtr, 1, recheck);
570 spggetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
572 SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque;
574 /* Copy want_itup to *so so we don't need to pass it around separately */
575 so->want_itup = false;
580 spgWalk(scan->indexRelation, so, true, storeBitmap, scan->xs_snapshot);
585 /* storeRes subroutine for gettuple case */
587 storeGettuple(SpGistScanOpaque so, ItemPointer heapPtr,
588 Datum leafValue, bool isnull, bool recheck)
590 Assert(so->nPtrs < MaxIndexTuplesPerPage);
591 so->heapPtrs[so->nPtrs] = *heapPtr;
592 so->recheck[so->nPtrs] = recheck;
596 * Reconstruct desired IndexTuple. We have to copy the datum out of
597 * the temp context anyway, so we may as well create the tuple here.
599 so->indexTups[so->nPtrs] = index_form_tuple(so->indexTupDesc,
607 spggettuple(IndexScanDesc scan, ScanDirection dir)
609 SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque;
611 if (dir != ForwardScanDirection)
612 elog(ERROR, "SP-GiST only supports forward scan direction");
614 /* Copy want_itup to *so so we don't need to pass it around separately */
615 so->want_itup = scan->xs_want_itup;
619 if (so->iPtr < so->nPtrs)
621 /* continuing to return tuples from a leaf page */
622 scan->xs_ctup.t_self = so->heapPtrs[so->iPtr];
623 scan->xs_recheck = so->recheck[so->iPtr];
624 scan->xs_itup = so->indexTups[so->iPtr];
631 /* Must pfree IndexTuples to avoid memory leak */
634 for (i = 0; i < so->nPtrs; i++)
635 pfree(so->indexTups[i]);
637 so->iPtr = so->nPtrs = 0;
639 spgWalk(scan->indexRelation, so, false, storeGettuple,
643 break; /* must have completed scan */
650 spgcanreturn(Relation index, int attno)
654 /* We can do it if the opclass config function says so */
655 cache = spgGetCache(index);
657 return cache->config.canReturnData;