Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * spgscan.c
4 : * routines for scanning SP-GiST indexes
5 : *
6 : *
7 : * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
8 : * Portions Copyright (c) 1994, Regents of the University of California
9 : *
10 : * IDENTIFICATION
11 : * src/backend/access/spgist/spgscan.c
12 : *
13 : *-------------------------------------------------------------------------
14 : */
15 :
16 : #include "postgres.h"
17 :
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"
25 :
26 :
27 : typedef void (*storeRes_func) (SpGistScanOpaque so, ItemPointer heapPtr,
28 : Datum leafValue, bool isnull, bool recheck);
29 :
30 : typedef struct ScanStackEntry
31 : {
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 */
36 : } ScanStackEntry;
37 :
38 :
39 : /* Free a ScanStackEntry */
40 : static void
41 10014 : freeScanStackEntry(SpGistScanOpaque so, ScanStackEntry *stackEntry)
42 : {
43 20028 : if (!so->state.attType.attbyval &&
44 10014 : DatumGetPointer(stackEntry->reconstructedValue) != NULL)
45 2304 : pfree(DatumGetPointer(stackEntry->reconstructedValue));
46 10014 : if (stackEntry->traversalValue)
47 2118 : pfree(stackEntry->traversalValue);
48 :
49 10014 : pfree(stackEntry);
50 10014 : }
51 :
52 : /* Free the entire stack */
53 : static void
54 120 : freeScanStack(SpGistScanOpaque so)
55 : {
56 : ListCell *lc;
57 :
58 120 : foreach(lc, so->scanStack)
59 : {
60 0 : freeScanStackEntry(so, (ScanStackEntry *) lfirst(lc));
61 : }
62 120 : list_free(so->scanStack);
63 120 : so->scanStack = NIL;
64 120 : }
65 :
66 : /*
67 : * Initialize scanStack to search the root page, resetting
68 : * any previously active scan
69 : */
70 : static void
71 120 : resetSpGistScanOpaque(SpGistScanOpaque so)
72 : {
73 : ScanStackEntry *startEntry;
74 :
75 120 : freeScanStack(so);
76 :
77 120 : if (so->searchNulls)
78 : {
79 : /* Stack a work item to scan the null index entries */
80 4 : startEntry = (ScanStackEntry *) palloc0(sizeof(ScanStackEntry));
81 4 : ItemPointerSet(&startEntry->ptr, SPGIST_NULL_BLKNO, FirstOffsetNumber);
82 4 : so->scanStack = lappend(so->scanStack, startEntry);
83 : }
84 :
85 120 : if (so->searchNonNulls)
86 : {
87 : /* Stack a work item to scan the non-null index entries */
88 118 : startEntry = (ScanStackEntry *) palloc0(sizeof(ScanStackEntry));
89 118 : ItemPointerSet(&startEntry->ptr, SPGIST_ROOT_BLKNO, FirstOffsetNumber);
90 118 : so->scanStack = lappend(so->scanStack, startEntry);
91 : }
92 :
93 120 : if (so->want_itup)
94 : {
95 : /* Must pfree reconstructed tuples to avoid memory leak */
96 : int i;
97 :
98 0 : for (i = 0; i < so->nPtrs; i++)
99 0 : pfree(so->reconTups[i]);
100 : }
101 120 : so->iPtr = so->nPtrs = 0;
102 120 : }
103 :
104 : /*
105 : * Prepare scan keys in SpGistScanOpaque from caller-given scan keys
106 : *
107 : * Sets searchNulls, searchNonNulls, numberOfKeys, keyData fields of *so.
108 : *
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.
114 : */
115 : static void
116 120 : spgPrepareScanKeys(IndexScanDesc scan)
117 : {
118 120 : SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque;
119 : bool qual_ok;
120 : bool haveIsNull;
121 : bool haveNotNull;
122 : int nkeys;
123 : int i;
124 :
125 120 : if (scan->numberOfKeys <= 0)
126 : {
127 : /* If no quals, whole-index scan is required */
128 2 : so->searchNulls = true;
129 2 : so->searchNonNulls = true;
130 2 : so->numberOfKeys = 0;
131 122 : return;
132 : }
133 :
134 : /* Examine the given quals */
135 118 : qual_ok = true;
136 118 : haveIsNull = haveNotNull = false;
137 118 : nkeys = 0;
138 236 : for (i = 0; i < scan->numberOfKeys; i++)
139 : {
140 118 : ScanKey skey = &scan->keyData[i];
141 :
142 118 : if (skey->sk_flags & SK_SEARCHNULL)
143 2 : haveIsNull = true;
144 116 : else if (skey->sk_flags & SK_SEARCHNOTNULL)
145 2 : haveNotNull = true;
146 114 : else if (skey->sk_flags & SK_ISNULL)
147 : {
148 : /* ordinary qual with null argument - unsatisfiable */
149 0 : qual_ok = false;
150 0 : break;
151 : }
152 : else
153 : {
154 : /* ordinary qual, propagate into so->keyData */
155 114 : so->keyData[nkeys++] = *skey;
156 : /* this effectively creates a not-null requirement */
157 114 : haveNotNull = true;
158 : }
159 : }
160 :
161 : /* IS NULL in combination with something else is unsatisfiable */
162 118 : if (haveIsNull && haveNotNull)
163 0 : qual_ok = false;
164 :
165 : /* Emit results */
166 118 : if (qual_ok)
167 : {
168 118 : so->searchNulls = haveIsNull;
169 118 : so->searchNonNulls = haveNotNull;
170 118 : so->numberOfKeys = nkeys;
171 : }
172 : else
173 : {
174 0 : so->searchNulls = false;
175 0 : so->searchNonNulls = false;
176 0 : so->numberOfKeys = 0;
177 : }
178 : }
179 :
180 : IndexScanDesc
181 120 : spgbeginscan(Relation rel, int keysz, int orderbysz)
182 : {
183 : IndexScanDesc scan;
184 : SpGistScanOpaque so;
185 :
186 120 : scan = RelationGetIndexScan(rel, keysz, 0);
187 :
188 120 : so = (SpGistScanOpaque) palloc0(sizeof(SpGistScanOpaqueData));
189 120 : if (keysz > 0)
190 118 : so->keyData = (ScanKey) palloc(sizeof(ScanKeyData) * keysz);
191 : else
192 2 : so->keyData = NULL;
193 120 : initSpGistState(&so->state, scan->indexRelation);
194 120 : so->tempCxt = AllocSetContextCreate(CurrentMemoryContext,
195 : "SP-GiST search temporary context",
196 : ALLOCSET_DEFAULT_SIZES);
197 :
198 : /* Set up indexTupDesc and xs_hitupdesc in case it's an index-only scan */
199 120 : so->indexTupDesc = scan->xs_hitupdesc = RelationGetDescr(rel);
200 :
201 120 : scan->opaque = so;
202 :
203 120 : return scan;
204 : }
205 :
206 : void
207 120 : spgrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
208 : ScanKey orderbys, int norderbys)
209 : {
210 120 : SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque;
211 :
212 : /* copy scankeys into local storage */
213 120 : if (scankey && scan->numberOfKeys > 0)
214 : {
215 118 : memmove(scan->keyData, scankey,
216 118 : scan->numberOfKeys * sizeof(ScanKeyData));
217 : }
218 :
219 : /* preprocess scankeys, set up the representation in *so */
220 120 : spgPrepareScanKeys(scan);
221 :
222 : /* set up starting stack entries */
223 120 : resetSpGistScanOpaque(so);
224 120 : }
225 :
226 : void
227 120 : spgendscan(IndexScanDesc scan)
228 : {
229 120 : SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque;
230 :
231 120 : MemoryContextDelete(so->tempCxt);
232 120 : }
233 :
234 : /*
235 : * Test whether a leaf tuple satisfies all the scan keys
236 : *
237 : * *leafValue is set to the reconstructed datum, if provided
238 : * *recheck is set true if any of the operators are lossy
239 : */
240 : static bool
241 313577 : spgLeafTest(Relation index, SpGistScanOpaque so,
242 : SpGistLeafTuple leafTuple, bool isnull,
243 : int level, Datum reconstructedValue,
244 : void *traversalValue,
245 : Datum *leafValue, bool *recheck)
246 : {
247 : bool result;
248 : Datum leafDatum;
249 : spgLeafConsistentIn in;
250 : spgLeafConsistentOut out;
251 : FmgrInfo *procinfo;
252 : MemoryContext oldCtx;
253 :
254 313577 : if (isnull)
255 : {
256 : /* Should not have arrived on a nulls page unless nulls are wanted */
257 12 : Assert(so->searchNulls);
258 12 : *leafValue = (Datum) 0;
259 12 : *recheck = false;
260 12 : return true;
261 : }
262 :
263 313565 : leafDatum = SGLTDATUM(leafTuple, &so->state);
264 :
265 : /* use temp context for calling leaf_consistent */
266 313565 : oldCtx = MemoryContextSwitchTo(so->tempCxt);
267 :
268 313565 : in.scankeys = so->keyData;
269 313565 : in.nkeys = so->numberOfKeys;
270 313565 : in.reconstructedValue = reconstructedValue;
271 313565 : in.traversalValue = traversalValue;
272 313565 : in.level = level;
273 313565 : in.returnData = so->want_itup;
274 313565 : in.leafDatum = leafDatum;
275 :
276 313565 : out.leafValue = (Datum) 0;
277 313565 : out.recheck = false;
278 :
279 313565 : procinfo = index_getprocinfo(index, 1, SPGIST_LEAF_CONSISTENT_PROC);
280 313565 : result = DatumGetBool(FunctionCall2Coll(procinfo,
281 : index->rd_indcollation[0],
282 : PointerGetDatum(&in),
283 : PointerGetDatum(&out)));
284 :
285 313565 : *leafValue = out.leafValue;
286 313565 : *recheck = out.recheck;
287 :
288 313565 : MemoryContextSwitchTo(oldCtx);
289 :
290 313565 : return result;
291 : }
292 :
293 : /*
294 : * Walk the tree and report all tuples passing the scan quals to the storeRes
295 : * subroutine.
296 : *
297 : * If scanWholeIndex is true, we'll do just that. If not, we'll stop at the
298 : * next page boundary once we have reported at least one tuple.
299 : */
300 : static void
301 2198 : spgWalk(Relation index, SpGistScanOpaque so, bool scanWholeIndex,
302 : storeRes_func storeRes, Snapshot snapshot)
303 : {
304 2198 : Buffer buffer = InvalidBuffer;
305 2198 : bool reportedSome = false;
306 :
307 14410 : while (scanWholeIndex || !reportedSome)
308 : {
309 : ScanStackEntry *stackEntry;
310 : BlockNumber blkno;
311 : OffsetNumber offset;
312 : Page page;
313 : bool isnull;
314 :
315 : /* Pull next to-do item from the list */
316 10134 : if (so->scanStack == NIL)
317 120 : break; /* there are no more pages to scan */
318 :
319 10014 : stackEntry = (ScanStackEntry *) linitial(so->scanStack);
320 10014 : so->scanStack = list_delete_first(so->scanStack);
321 :
322 : redirect:
323 : /* Check for interrupts, just in case of infinite loop */
324 10014 : CHECK_FOR_INTERRUPTS();
325 :
326 10014 : blkno = ItemPointerGetBlockNumber(&stackEntry->ptr);
327 10014 : offset = ItemPointerGetOffsetNumber(&stackEntry->ptr);
328 :
329 10014 : if (buffer == InvalidBuffer)
330 : {
331 2130 : buffer = ReadBuffer(index, blkno);
332 2130 : LockBuffer(buffer, BUFFER_LOCK_SHARE);
333 : }
334 7884 : else if (blkno != BufferGetBlockNumber(buffer))
335 : {
336 4053 : UnlockReleaseBuffer(buffer);
337 4053 : buffer = ReadBuffer(index, blkno);
338 4053 : LockBuffer(buffer, BUFFER_LOCK_SHARE);
339 : }
340 : /* else new pointer points to the same page, no work needed */
341 :
342 10014 : page = BufferGetPage(buffer);
343 10014 : TestForOldSnapshot(snapshot, index, page);
344 :
345 10014 : isnull = SpGistPageStoresNulls(page) ? true : false;
346 :
347 10014 : if (SpGistPageIsLeaf(page))
348 : {
349 : SpGistLeafTuple leafTuple;
350 7306 : OffsetNumber max = PageGetMaxOffsetNumber(page);
351 7306 : Datum leafValue = (Datum) 0;
352 7306 : bool recheck = false;
353 :
354 7334 : if (SpGistBlockIsRoot(blkno))
355 : {
356 : /* When root is a leaf, examine all its tuples */
357 904 : for (offset = FirstOffsetNumber; offset <= max; offset++)
358 : {
359 876 : leafTuple = (SpGistLeafTuple)
360 876 : PageGetItem(page, PageGetItemId(page, offset));
361 876 : if (leafTuple->tupstate != SPGIST_LIVE)
362 : {
363 : /* all tuples on root should be live */
364 0 : elog(ERROR, "unexpected SPGiST tuple state: %d",
365 : leafTuple->tupstate);
366 : }
367 :
368 876 : Assert(ItemPointerIsValid(&leafTuple->heapPtr));
369 876 : if (spgLeafTest(index, so,
370 : leafTuple, isnull,
371 : stackEntry->level,
372 : stackEntry->reconstructedValue,
373 : stackEntry->traversalValue,
374 : &leafValue,
375 : &recheck))
376 : {
377 162 : storeRes(so, &leafTuple->heapPtr,
378 : leafValue, isnull, recheck);
379 162 : reportedSome = true;
380 : }
381 : }
382 : }
383 : else
384 : {
385 : /* Normal case: just examine the chain we arrived at */
386 327257 : while (offset != InvalidOffsetNumber)
387 : {
388 312701 : Assert(offset >= FirstOffsetNumber && offset <= max);
389 312701 : leafTuple = (SpGistLeafTuple)
390 312701 : PageGetItem(page, PageGetItemId(page, offset));
391 312701 : if (leafTuple->tupstate != SPGIST_LIVE)
392 : {
393 0 : if (leafTuple->tupstate == SPGIST_REDIRECT)
394 : {
395 : /* redirection tuple should be first in chain */
396 0 : Assert(offset == ItemPointerGetOffsetNumber(&stackEntry->ptr));
397 : /* transfer attention to redirect point */
398 0 : stackEntry->ptr = ((SpGistDeadTuple) leafTuple)->pointer;
399 0 : Assert(ItemPointerGetBlockNumber(&stackEntry->ptr) != SPGIST_METAPAGE_BLKNO);
400 0 : goto redirect;
401 : }
402 0 : if (leafTuple->tupstate == SPGIST_DEAD)
403 : {
404 : /* dead tuple should be first in chain */
405 0 : Assert(offset == ItemPointerGetOffsetNumber(&stackEntry->ptr));
406 : /* No live entries on this page */
407 0 : Assert(leafTuple->nextOffset == InvalidOffsetNumber);
408 0 : break;
409 : }
410 : /* We should not arrive at a placeholder */
411 0 : elog(ERROR, "unexpected SPGiST tuple state: %d",
412 : leafTuple->tupstate);
413 : }
414 :
415 312701 : Assert(ItemPointerIsValid(&leafTuple->heapPtr));
416 312701 : if (spgLeafTest(index, so,
417 : leafTuple, isnull,
418 : stackEntry->level,
419 : stackEntry->reconstructedValue,
420 : stackEntry->traversalValue,
421 : &leafValue,
422 : &recheck))
423 : {
424 234565 : storeRes(so, &leafTuple->heapPtr,
425 : leafValue, isnull, recheck);
426 234565 : reportedSome = true;
427 : }
428 :
429 312701 : offset = leafTuple->nextOffset;
430 : }
431 : }
432 : }
433 : else /* page is inner */
434 : {
435 : SpGistInnerTuple innerTuple;
436 : spgInnerConsistentIn in;
437 : spgInnerConsistentOut out;
438 : FmgrInfo *procinfo;
439 : SpGistNodeTuple *nodes;
440 : SpGistNodeTuple node;
441 : int i;
442 : MemoryContext oldCtx;
443 :
444 2708 : innerTuple = (SpGistInnerTuple) PageGetItem(page,
445 : PageGetItemId(page, offset));
446 :
447 2708 : if (innerTuple->tupstate != SPGIST_LIVE)
448 : {
449 0 : if (innerTuple->tupstate == SPGIST_REDIRECT)
450 : {
451 : /* transfer attention to redirect point */
452 0 : stackEntry->ptr = ((SpGistDeadTuple) innerTuple)->pointer;
453 0 : Assert(ItemPointerGetBlockNumber(&stackEntry->ptr) != SPGIST_METAPAGE_BLKNO);
454 0 : goto redirect;
455 : }
456 0 : elog(ERROR, "unexpected SPGiST tuple state: %d",
457 : innerTuple->tupstate);
458 : }
459 :
460 : /* use temp context for calling inner_consistent */
461 2708 : oldCtx = MemoryContextSwitchTo(so->tempCxt);
462 :
463 2708 : in.scankeys = so->keyData;
464 2708 : in.nkeys = so->numberOfKeys;
465 2708 : in.reconstructedValue = stackEntry->reconstructedValue;
466 2708 : in.traversalMemoryContext = oldCtx;
467 2708 : in.traversalValue = stackEntry->traversalValue;
468 2708 : in.level = stackEntry->level;
469 2708 : in.returnData = so->want_itup;
470 2708 : in.allTheSame = innerTuple->allTheSame;
471 2708 : in.hasPrefix = (innerTuple->prefixSize > 0);
472 2708 : in.prefixDatum = SGITDATUM(innerTuple, &so->state);
473 2708 : in.nNodes = innerTuple->nNodes;
474 2708 : in.nodeLabels = spgExtractNodeLabels(&so->state, innerTuple);
475 :
476 : /* collect node pointers */
477 2708 : nodes = (SpGistNodeTuple *) palloc(sizeof(SpGistNodeTuple) * in.nNodes);
478 22161 : SGITITERATE(innerTuple, i, node)
479 : {
480 19453 : nodes[i] = node;
481 : }
482 :
483 2708 : memset(&out, 0, sizeof(out));
484 :
485 2708 : if (!isnull)
486 : {
487 : /* use user-defined inner consistent method */
488 2708 : procinfo = index_getprocinfo(index, 1, SPGIST_INNER_CONSISTENT_PROC);
489 5416 : FunctionCall2Coll(procinfo,
490 2708 : index->rd_indcollation[0],
491 : PointerGetDatum(&in),
492 : PointerGetDatum(&out));
493 : }
494 : else
495 : {
496 : /* force all children to be visited */
497 0 : out.nNodes = in.nNodes;
498 0 : out.nodeNumbers = (int *) palloc(sizeof(int) * in.nNodes);
499 0 : for (i = 0; i < in.nNodes; i++)
500 0 : out.nodeNumbers[i] = i;
501 : }
502 :
503 2708 : MemoryContextSwitchTo(oldCtx);
504 :
505 : /* If allTheSame, they should all or none of 'em match */
506 2708 : if (innerTuple->allTheSame)
507 208 : if (out.nNodes != 0 && out.nNodes != in.nNodes)
508 0 : elog(ERROR, "inconsistent inner_consistent results for allTheSame inner tuple");
509 :
510 18759 : for (i = 0; i < out.nNodes; i++)
511 : {
512 16051 : int nodeN = out.nodeNumbers[i];
513 :
514 16051 : Assert(nodeN >= 0 && nodeN < in.nNodes);
515 16051 : if (ItemPointerIsValid(&nodes[nodeN]->t_tid))
516 : {
517 : ScanStackEntry *newEntry;
518 :
519 : /* Create new work item for this node */
520 9892 : newEntry = palloc(sizeof(ScanStackEntry));
521 9892 : newEntry->ptr = nodes[nodeN]->t_tid;
522 9892 : if (out.levelAdds)
523 3436 : newEntry->level = stackEntry->level + out.levelAdds[i];
524 : else
525 6456 : newEntry->level = stackEntry->level;
526 : /* Must copy value out of temp context */
527 9892 : if (out.reconstructedValues)
528 2304 : newEntry->reconstructedValue =
529 4608 : datumCopy(out.reconstructedValues[i],
530 2304 : so->state.attType.attbyval,
531 2304 : so->state.attType.attlen);
532 : else
533 7588 : newEntry->reconstructedValue = (Datum) 0;
534 :
535 : /*
536 : * Elements of out.traversalValues should be allocated in
537 : * in.traversalMemoryContext, which is actually a long
538 : * lived context of index scan.
539 : */
540 19784 : newEntry->traversalValue = (out.traversalValues) ?
541 9892 : out.traversalValues[i] : NULL;
542 :
543 9892 : so->scanStack = lcons(newEntry, so->scanStack);
544 : }
545 : }
546 : }
547 :
548 : /* done with this scan stack entry */
549 10014 : freeScanStackEntry(so, stackEntry);
550 : /* clear temp context before proceeding to the next one */
551 10014 : MemoryContextReset(so->tempCxt);
552 : }
553 :
554 2198 : if (buffer != InvalidBuffer)
555 2130 : UnlockReleaseBuffer(buffer);
556 2198 : }
557 :
558 : /* storeRes subroutine for getbitmap case */
559 : static void
560 127583 : storeBitmap(SpGistScanOpaque so, ItemPointer heapPtr,
561 : Datum leafValue, bool isnull, bool recheck)
562 : {
563 127583 : tbm_add_tuples(so->tbm, heapPtr, 1, recheck);
564 127583 : so->ntids++;
565 127583 : }
566 :
567 : int64
568 43 : spggetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
569 : {
570 43 : SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque;
571 :
572 : /* Copy want_itup to *so so we don't need to pass it around separately */
573 43 : so->want_itup = false;
574 :
575 43 : so->tbm = tbm;
576 43 : so->ntids = 0;
577 :
578 43 : spgWalk(scan->indexRelation, so, true, storeBitmap, scan->xs_snapshot);
579 :
580 43 : return so->ntids;
581 : }
582 :
583 : /* storeRes subroutine for gettuple case */
584 : static void
585 107144 : storeGettuple(SpGistScanOpaque so, ItemPointer heapPtr,
586 : Datum leafValue, bool isnull, bool recheck)
587 : {
588 107144 : Assert(so->nPtrs < MaxIndexTuplesPerPage);
589 107144 : so->heapPtrs[so->nPtrs] = *heapPtr;
590 107144 : so->recheck[so->nPtrs] = recheck;
591 107144 : if (so->want_itup)
592 : {
593 : /*
594 : * Reconstruct index data. We have to copy the datum out of the temp
595 : * context anyway, so we may as well create the tuple here.
596 : */
597 107075 : so->reconTups[so->nPtrs] = heap_form_tuple(so->indexTupDesc,
598 : &leafValue,
599 : &isnull);
600 : }
601 107144 : so->nPtrs++;
602 107144 : }
603 :
604 : bool
605 107221 : spggettuple(IndexScanDesc scan, ScanDirection dir)
606 : {
607 107221 : SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque;
608 :
609 107221 : if (dir != ForwardScanDirection)
610 0 : elog(ERROR, "SP-GiST only supports forward scan direction");
611 :
612 : /* Copy want_itup to *so so we don't need to pass it around separately */
613 107221 : so->want_itup = scan->xs_want_itup;
614 :
615 : for (;;)
616 : {
617 109299 : if (so->iPtr < so->nPtrs)
618 : {
619 : /* continuing to return tuples from a leaf page */
620 107144 : scan->xs_ctup.t_self = so->heapPtrs[so->iPtr];
621 107144 : scan->xs_recheck = so->recheck[so->iPtr];
622 107144 : scan->xs_hitup = so->reconTups[so->iPtr];
623 107144 : so->iPtr++;
624 107144 : return true;
625 : }
626 :
627 2155 : if (so->want_itup)
628 : {
629 : /* Must pfree reconstructed tuples to avoid memory leak */
630 : int i;
631 :
632 109209 : for (i = 0; i < so->nPtrs; i++)
633 107075 : pfree(so->reconTups[i]);
634 : }
635 2155 : so->iPtr = so->nPtrs = 0;
636 :
637 2155 : spgWalk(scan->indexRelation, so, false, storeGettuple,
638 : scan->xs_snapshot);
639 :
640 2155 : if (so->nPtrs == 0)
641 77 : break; /* must have completed scan */
642 2078 : }
643 :
644 77 : return false;
645 : }
646 :
647 : bool
648 233 : spgcanreturn(Relation index, int attno)
649 : {
650 : SpGistCache *cache;
651 :
652 : /* We can do it if the opclass config function says so */
653 233 : cache = spgGetCache(index);
654 :
655 233 : return cache->config.canReturnData;
656 : }
|