/src/postgres/src/backend/access/spgist/spgscan.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * spgscan.c |
4 | | * routines for scanning SP-GiST indexes |
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/spgist/spgscan.c |
12 | | * |
13 | | *------------------------------------------------------------------------- |
14 | | */ |
15 | | |
16 | | #include "postgres.h" |
17 | | |
18 | | #include "access/genam.h" |
19 | | #include "access/relscan.h" |
20 | | #include "access/spgist_private.h" |
21 | | #include "executor/instrument_node.h" |
22 | | #include "miscadmin.h" |
23 | | #include "pgstat.h" |
24 | | #include "storage/bufmgr.h" |
25 | | #include "utils/datum.h" |
26 | | #include "utils/float.h" |
27 | | #include "utils/lsyscache.h" |
28 | | #include "utils/memutils.h" |
29 | | #include "utils/rel.h" |
30 | | |
31 | | typedef void (*storeRes_func) (SpGistScanOpaque so, ItemPointer heapPtr, |
32 | | Datum leafValue, bool isNull, |
33 | | SpGistLeafTuple leafTuple, bool recheck, |
34 | | bool recheckDistances, double *distances); |
35 | | |
36 | | /* |
37 | | * Pairing heap comparison function for the SpGistSearchItem queue. |
38 | | * KNN-searches currently only support NULLS LAST. So, preserve this logic |
39 | | * here. |
40 | | */ |
41 | | static int |
42 | | pairingheap_SpGistSearchItem_cmp(const pairingheap_node *a, |
43 | | const pairingheap_node *b, void *arg) |
44 | 0 | { |
45 | 0 | const SpGistSearchItem *sa = (const SpGistSearchItem *) a; |
46 | 0 | const SpGistSearchItem *sb = (const SpGistSearchItem *) b; |
47 | 0 | SpGistScanOpaque so = (SpGistScanOpaque) arg; |
48 | 0 | int i; |
49 | |
|
50 | 0 | if (sa->isNull) |
51 | 0 | { |
52 | 0 | if (!sb->isNull) |
53 | 0 | return -1; |
54 | 0 | } |
55 | 0 | else if (sb->isNull) |
56 | 0 | { |
57 | 0 | return 1; |
58 | 0 | } |
59 | 0 | else |
60 | 0 | { |
61 | | /* Order according to distance comparison */ |
62 | 0 | for (i = 0; i < so->numberOfNonNullOrderBys; i++) |
63 | 0 | { |
64 | 0 | if (isnan(sa->distances[i]) && isnan(sb->distances[i])) |
65 | 0 | continue; /* NaN == NaN */ |
66 | 0 | if (isnan(sa->distances[i])) |
67 | 0 | return -1; /* NaN > number */ |
68 | 0 | if (isnan(sb->distances[i])) |
69 | 0 | return 1; /* number < NaN */ |
70 | 0 | if (sa->distances[i] != sb->distances[i]) |
71 | 0 | return (sa->distances[i] < sb->distances[i]) ? 1 : -1; |
72 | 0 | } |
73 | 0 | } |
74 | | |
75 | | /* Leaf items go before inner pages, to ensure a depth-first search */ |
76 | 0 | if (sa->isLeaf && !sb->isLeaf) |
77 | 0 | return 1; |
78 | 0 | if (!sa->isLeaf && sb->isLeaf) |
79 | 0 | return -1; |
80 | | |
81 | 0 | return 0; |
82 | 0 | } |
83 | | |
84 | | static void |
85 | | spgFreeSearchItem(SpGistScanOpaque so, SpGistSearchItem *item) |
86 | 0 | { |
87 | | /* value is of type attType if isLeaf, else of type attLeafType */ |
88 | | /* (no, that is not backwards; yes, it's confusing) */ |
89 | 0 | if (!(item->isLeaf ? so->state.attType.attbyval : |
90 | 0 | so->state.attLeafType.attbyval) && |
91 | 0 | DatumGetPointer(item->value) != NULL) |
92 | 0 | pfree(DatumGetPointer(item->value)); |
93 | |
|
94 | 0 | if (item->leafTuple) |
95 | 0 | pfree(item->leafTuple); |
96 | |
|
97 | 0 | if (item->traversalValue) |
98 | 0 | pfree(item->traversalValue); |
99 | |
|
100 | 0 | pfree(item); |
101 | 0 | } |
102 | | |
103 | | /* |
104 | | * Add SpGistSearchItem to queue |
105 | | * |
106 | | * Called in queue context |
107 | | */ |
108 | | static void |
109 | | spgAddSearchItemToQueue(SpGistScanOpaque so, SpGistSearchItem *item) |
110 | 0 | { |
111 | 0 | pairingheap_add(so->scanQueue, &item->phNode); |
112 | 0 | } |
113 | | |
114 | | static SpGistSearchItem * |
115 | | spgAllocSearchItem(SpGistScanOpaque so, bool isnull, double *distances) |
116 | 0 | { |
117 | | /* allocate distance array only for non-NULL items */ |
118 | 0 | SpGistSearchItem *item = |
119 | 0 | palloc(SizeOfSpGistSearchItem(isnull ? 0 : so->numberOfNonNullOrderBys)); |
120 | |
|
121 | 0 | item->isNull = isnull; |
122 | |
|
123 | 0 | if (!isnull && so->numberOfNonNullOrderBys > 0) |
124 | 0 | memcpy(item->distances, distances, |
125 | 0 | sizeof(item->distances[0]) * so->numberOfNonNullOrderBys); |
126 | |
|
127 | 0 | return item; |
128 | 0 | } |
129 | | |
130 | | static void |
131 | | spgAddStartItem(SpGistScanOpaque so, bool isnull) |
132 | 0 | { |
133 | 0 | SpGistSearchItem *startEntry = |
134 | 0 | spgAllocSearchItem(so, isnull, so->zeroDistances); |
135 | |
|
136 | 0 | ItemPointerSet(&startEntry->heapPtr, |
137 | 0 | isnull ? SPGIST_NULL_BLKNO : SPGIST_ROOT_BLKNO, |
138 | 0 | FirstOffsetNumber); |
139 | 0 | startEntry->isLeaf = false; |
140 | 0 | startEntry->level = 0; |
141 | 0 | startEntry->value = (Datum) 0; |
142 | 0 | startEntry->leafTuple = NULL; |
143 | 0 | startEntry->traversalValue = NULL; |
144 | 0 | startEntry->recheck = false; |
145 | 0 | startEntry->recheckDistances = false; |
146 | |
|
147 | 0 | spgAddSearchItemToQueue(so, startEntry); |
148 | 0 | } |
149 | | |
150 | | /* |
151 | | * Initialize queue to search the root page, resetting |
152 | | * any previously active scan |
153 | | */ |
154 | | static void |
155 | | resetSpGistScanOpaque(SpGistScanOpaque so) |
156 | 0 | { |
157 | 0 | MemoryContext oldCtx; |
158 | |
|
159 | 0 | MemoryContextReset(so->traversalCxt); |
160 | |
|
161 | 0 | oldCtx = MemoryContextSwitchTo(so->traversalCxt); |
162 | | |
163 | | /* initialize queue only for distance-ordered scans */ |
164 | 0 | so->scanQueue = pairingheap_allocate(pairingheap_SpGistSearchItem_cmp, so); |
165 | |
|
166 | 0 | if (so->searchNulls) |
167 | | /* Add a work item to scan the null index entries */ |
168 | 0 | spgAddStartItem(so, true); |
169 | |
|
170 | 0 | if (so->searchNonNulls) |
171 | | /* Add a work item to scan the non-null index entries */ |
172 | 0 | spgAddStartItem(so, false); |
173 | |
|
174 | 0 | MemoryContextSwitchTo(oldCtx); |
175 | |
|
176 | 0 | if (so->numberOfOrderBys > 0) |
177 | 0 | { |
178 | | /* Must pfree distances to avoid memory leak */ |
179 | 0 | int i; |
180 | |
|
181 | 0 | for (i = 0; i < so->nPtrs; i++) |
182 | 0 | if (so->distances[i]) |
183 | 0 | pfree(so->distances[i]); |
184 | 0 | } |
185 | |
|
186 | 0 | if (so->want_itup) |
187 | 0 | { |
188 | | /* Must pfree reconstructed tuples to avoid memory leak */ |
189 | 0 | int i; |
190 | |
|
191 | 0 | for (i = 0; i < so->nPtrs; i++) |
192 | 0 | pfree(so->reconTups[i]); |
193 | 0 | } |
194 | 0 | so->iPtr = so->nPtrs = 0; |
195 | 0 | } |
196 | | |
197 | | /* |
198 | | * Prepare scan keys in SpGistScanOpaque from caller-given scan keys |
199 | | * |
200 | | * Sets searchNulls, searchNonNulls, numberOfKeys, keyData fields of *so. |
201 | | * |
202 | | * The point here is to eliminate null-related considerations from what the |
203 | | * opclass consistent functions need to deal with. We assume all SPGiST- |
204 | | * indexable operators are strict, so any null RHS value makes the scan |
205 | | * condition unsatisfiable. We also pull out any IS NULL/IS NOT NULL |
206 | | * conditions; their effect is reflected into searchNulls/searchNonNulls. |
207 | | */ |
208 | | static void |
209 | | spgPrepareScanKeys(IndexScanDesc scan) |
210 | 0 | { |
211 | 0 | SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque; |
212 | 0 | bool qual_ok; |
213 | 0 | bool haveIsNull; |
214 | 0 | bool haveNotNull; |
215 | 0 | int nkeys; |
216 | 0 | int i; |
217 | |
|
218 | 0 | so->numberOfOrderBys = scan->numberOfOrderBys; |
219 | 0 | so->orderByData = scan->orderByData; |
220 | |
|
221 | 0 | if (so->numberOfOrderBys <= 0) |
222 | 0 | so->numberOfNonNullOrderBys = 0; |
223 | 0 | else |
224 | 0 | { |
225 | 0 | int j = 0; |
226 | | |
227 | | /* |
228 | | * Remove all NULL keys, but remember their offsets in the original |
229 | | * array. |
230 | | */ |
231 | 0 | for (i = 0; i < scan->numberOfOrderBys; i++) |
232 | 0 | { |
233 | 0 | ScanKey skey = &so->orderByData[i]; |
234 | |
|
235 | 0 | if (skey->sk_flags & SK_ISNULL) |
236 | 0 | so->nonNullOrderByOffsets[i] = -1; |
237 | 0 | else |
238 | 0 | { |
239 | 0 | if (i != j) |
240 | 0 | so->orderByData[j] = *skey; |
241 | |
|
242 | 0 | so->nonNullOrderByOffsets[i] = j++; |
243 | 0 | } |
244 | 0 | } |
245 | |
|
246 | 0 | so->numberOfNonNullOrderBys = j; |
247 | 0 | } |
248 | |
|
249 | 0 | if (scan->numberOfKeys <= 0) |
250 | 0 | { |
251 | | /* If no quals, whole-index scan is required */ |
252 | 0 | so->searchNulls = true; |
253 | 0 | so->searchNonNulls = true; |
254 | 0 | so->numberOfKeys = 0; |
255 | 0 | return; |
256 | 0 | } |
257 | | |
258 | | /* Examine the given quals */ |
259 | 0 | qual_ok = true; |
260 | 0 | haveIsNull = haveNotNull = false; |
261 | 0 | nkeys = 0; |
262 | 0 | for (i = 0; i < scan->numberOfKeys; i++) |
263 | 0 | { |
264 | 0 | ScanKey skey = &scan->keyData[i]; |
265 | |
|
266 | 0 | if (skey->sk_flags & SK_SEARCHNULL) |
267 | 0 | haveIsNull = true; |
268 | 0 | else if (skey->sk_flags & SK_SEARCHNOTNULL) |
269 | 0 | haveNotNull = true; |
270 | 0 | else if (skey->sk_flags & SK_ISNULL) |
271 | 0 | { |
272 | | /* ordinary qual with null argument - unsatisfiable */ |
273 | 0 | qual_ok = false; |
274 | 0 | break; |
275 | 0 | } |
276 | 0 | else |
277 | 0 | { |
278 | | /* ordinary qual, propagate into so->keyData */ |
279 | 0 | so->keyData[nkeys++] = *skey; |
280 | | /* this effectively creates a not-null requirement */ |
281 | 0 | haveNotNull = true; |
282 | 0 | } |
283 | 0 | } |
284 | | |
285 | | /* IS NULL in combination with something else is unsatisfiable */ |
286 | 0 | if (haveIsNull && haveNotNull) |
287 | 0 | qual_ok = false; |
288 | | |
289 | | /* Emit results */ |
290 | 0 | if (qual_ok) |
291 | 0 | { |
292 | 0 | so->searchNulls = haveIsNull; |
293 | 0 | so->searchNonNulls = haveNotNull; |
294 | 0 | so->numberOfKeys = nkeys; |
295 | 0 | } |
296 | 0 | else |
297 | 0 | { |
298 | 0 | so->searchNulls = false; |
299 | 0 | so->searchNonNulls = false; |
300 | 0 | so->numberOfKeys = 0; |
301 | 0 | } |
302 | 0 | } |
303 | | |
304 | | IndexScanDesc |
305 | | spgbeginscan(Relation rel, int keysz, int orderbysz) |
306 | 0 | { |
307 | 0 | IndexScanDesc scan; |
308 | 0 | SpGistScanOpaque so; |
309 | 0 | int i; |
310 | |
|
311 | 0 | scan = RelationGetIndexScan(rel, keysz, orderbysz); |
312 | |
|
313 | 0 | so = palloc0_object(SpGistScanOpaqueData); |
314 | 0 | if (keysz > 0) |
315 | 0 | so->keyData = palloc_array(ScanKeyData, keysz); |
316 | 0 | else |
317 | 0 | so->keyData = NULL; |
318 | 0 | initSpGistState(&so->state, scan->indexRelation); |
319 | |
|
320 | 0 | so->tempCxt = AllocSetContextCreate(CurrentMemoryContext, |
321 | 0 | "SP-GiST search temporary context", |
322 | 0 | ALLOCSET_DEFAULT_SIZES); |
323 | 0 | so->traversalCxt = AllocSetContextCreate(CurrentMemoryContext, |
324 | 0 | "SP-GiST traversal-value context", |
325 | 0 | ALLOCSET_DEFAULT_SIZES); |
326 | | |
327 | | /* |
328 | | * Set up reconTupDesc and xs_hitupdesc in case it's an index-only scan, |
329 | | * making sure that the key column is shown as being of type attType. |
330 | | * (It's rather annoying to do this work when it might be wasted, but for |
331 | | * most opclasses we can re-use the index reldesc instead of making one.) |
332 | | */ |
333 | 0 | so->reconTupDesc = scan->xs_hitupdesc = |
334 | 0 | getSpGistTupleDesc(rel, &so->state.attType); |
335 | | |
336 | | /* Allocate various arrays needed for order-by scans */ |
337 | 0 | if (scan->numberOfOrderBys > 0) |
338 | 0 | { |
339 | | /* This will be filled in spgrescan, but allocate the space here */ |
340 | 0 | so->orderByTypes = palloc_array(Oid, scan->numberOfOrderBys); |
341 | 0 | so->nonNullOrderByOffsets = palloc_array(int, scan->numberOfOrderBys); |
342 | | |
343 | | /* These arrays have constant contents, so we can fill them now */ |
344 | 0 | so->zeroDistances = palloc_array(double, scan->numberOfOrderBys); |
345 | 0 | so->infDistances = palloc_array(double, scan->numberOfOrderBys); |
346 | |
|
347 | 0 | for (i = 0; i < scan->numberOfOrderBys; i++) |
348 | 0 | { |
349 | 0 | so->zeroDistances[i] = 0.0; |
350 | 0 | so->infDistances[i] = get_float8_infinity(); |
351 | 0 | } |
352 | |
|
353 | 0 | scan->xs_orderbyvals = palloc0_array(Datum, scan->numberOfOrderBys); |
354 | 0 | scan->xs_orderbynulls = palloc_array(bool, scan->numberOfOrderBys); |
355 | 0 | memset(scan->xs_orderbynulls, true, |
356 | 0 | sizeof(bool) * scan->numberOfOrderBys); |
357 | 0 | } |
358 | |
|
359 | 0 | fmgr_info_copy(&so->innerConsistentFn, |
360 | 0 | index_getprocinfo(rel, 1, SPGIST_INNER_CONSISTENT_PROC), |
361 | 0 | CurrentMemoryContext); |
362 | |
|
363 | 0 | fmgr_info_copy(&so->leafConsistentFn, |
364 | 0 | index_getprocinfo(rel, 1, SPGIST_LEAF_CONSISTENT_PROC), |
365 | 0 | CurrentMemoryContext); |
366 | |
|
367 | 0 | so->indexCollation = rel->rd_indcollation[0]; |
368 | |
|
369 | 0 | scan->opaque = so; |
370 | |
|
371 | 0 | return scan; |
372 | 0 | } |
373 | | |
374 | | void |
375 | | spgrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys, |
376 | | ScanKey orderbys, int norderbys) |
377 | 0 | { |
378 | 0 | SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque; |
379 | | |
380 | | /* copy scankeys into local storage */ |
381 | 0 | if (scankey && scan->numberOfKeys > 0) |
382 | 0 | memcpy(scan->keyData, scankey, scan->numberOfKeys * sizeof(ScanKeyData)); |
383 | | |
384 | | /* initialize order-by data if needed */ |
385 | 0 | if (orderbys && scan->numberOfOrderBys > 0) |
386 | 0 | { |
387 | 0 | int i; |
388 | |
|
389 | 0 | memcpy(scan->orderByData, orderbys, scan->numberOfOrderBys * sizeof(ScanKeyData)); |
390 | |
|
391 | 0 | for (i = 0; i < scan->numberOfOrderBys; i++) |
392 | 0 | { |
393 | 0 | ScanKey skey = &scan->orderByData[i]; |
394 | | |
395 | | /* |
396 | | * Look up the datatype returned by the original ordering |
397 | | * operator. SP-GiST always uses a float8 for the distance |
398 | | * function, but the ordering operator could be anything else. |
399 | | * |
400 | | * XXX: The distance function is only allowed to be lossy if the |
401 | | * ordering operator's result type is float4 or float8. Otherwise |
402 | | * we don't know how to return the distance to the executor. But |
403 | | * we cannot check that here, as we won't know if the distance |
404 | | * function is lossy until it returns *recheck = true for the |
405 | | * first time. |
406 | | */ |
407 | 0 | so->orderByTypes[i] = get_func_rettype(skey->sk_func.fn_oid); |
408 | 0 | } |
409 | 0 | } |
410 | | |
411 | | /* preprocess scankeys, set up the representation in *so */ |
412 | 0 | spgPrepareScanKeys(scan); |
413 | | |
414 | | /* set up starting queue entries */ |
415 | 0 | resetSpGistScanOpaque(so); |
416 | | |
417 | | /* count an indexscan for stats */ |
418 | 0 | pgstat_count_index_scan(scan->indexRelation); |
419 | 0 | if (scan->instrument) |
420 | 0 | scan->instrument->nsearches++; |
421 | 0 | } |
422 | | |
423 | | void |
424 | | spgendscan(IndexScanDesc scan) |
425 | 0 | { |
426 | 0 | SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque; |
427 | |
|
428 | 0 | MemoryContextDelete(so->tempCxt); |
429 | 0 | MemoryContextDelete(so->traversalCxt); |
430 | |
|
431 | 0 | if (so->keyData) |
432 | 0 | pfree(so->keyData); |
433 | |
|
434 | 0 | if (so->state.leafTupDesc && |
435 | 0 | so->state.leafTupDesc != RelationGetDescr(so->state.index)) |
436 | 0 | FreeTupleDesc(so->state.leafTupDesc); |
437 | |
|
438 | 0 | if (so->state.deadTupleStorage) |
439 | 0 | pfree(so->state.deadTupleStorage); |
440 | |
|
441 | 0 | if (scan->numberOfOrderBys > 0) |
442 | 0 | { |
443 | 0 | pfree(so->orderByTypes); |
444 | 0 | pfree(so->nonNullOrderByOffsets); |
445 | 0 | pfree(so->zeroDistances); |
446 | 0 | pfree(so->infDistances); |
447 | 0 | pfree(scan->xs_orderbyvals); |
448 | 0 | pfree(scan->xs_orderbynulls); |
449 | 0 | } |
450 | |
|
451 | 0 | pfree(so); |
452 | 0 | } |
453 | | |
454 | | /* |
455 | | * Leaf SpGistSearchItem constructor, called in queue context |
456 | | */ |
457 | | static SpGistSearchItem * |
458 | | spgNewHeapItem(SpGistScanOpaque so, int level, SpGistLeafTuple leafTuple, |
459 | | Datum leafValue, bool recheck, bool recheckDistances, |
460 | | bool isnull, double *distances) |
461 | 0 | { |
462 | 0 | SpGistSearchItem *item = spgAllocSearchItem(so, isnull, distances); |
463 | |
|
464 | 0 | item->level = level; |
465 | 0 | item->heapPtr = leafTuple->heapPtr; |
466 | | |
467 | | /* |
468 | | * If we need the reconstructed value, copy it to queue cxt out of tmp |
469 | | * cxt. Caution: the leaf_consistent method may not have supplied a value |
470 | | * if we didn't ask it to, and mildly-broken methods might supply one of |
471 | | * the wrong type. The correct leafValue type is attType not leafType. |
472 | | */ |
473 | 0 | if (so->want_itup) |
474 | 0 | { |
475 | 0 | item->value = isnull ? (Datum) 0 : |
476 | 0 | datumCopy(leafValue, so->state.attType.attbyval, |
477 | 0 | so->state.attType.attlen); |
478 | | |
479 | | /* |
480 | | * If we're going to need to reconstruct INCLUDE attributes, store the |
481 | | * whole leaf tuple so we can get the INCLUDE attributes out of it. |
482 | | */ |
483 | 0 | if (so->state.leafTupDesc->natts > 1) |
484 | 0 | { |
485 | 0 | item->leafTuple = palloc(leafTuple->size); |
486 | 0 | memcpy(item->leafTuple, leafTuple, leafTuple->size); |
487 | 0 | } |
488 | 0 | else |
489 | 0 | item->leafTuple = NULL; |
490 | 0 | } |
491 | 0 | else |
492 | 0 | { |
493 | 0 | item->value = (Datum) 0; |
494 | 0 | item->leafTuple = NULL; |
495 | 0 | } |
496 | 0 | item->traversalValue = NULL; |
497 | 0 | item->isLeaf = true; |
498 | 0 | item->recheck = recheck; |
499 | 0 | item->recheckDistances = recheckDistances; |
500 | |
|
501 | 0 | return item; |
502 | 0 | } |
503 | | |
504 | | /* |
505 | | * Test whether a leaf tuple satisfies all the scan keys |
506 | | * |
507 | | * *reportedSome is set to true if: |
508 | | * the scan is not ordered AND the item satisfies the scankeys |
509 | | */ |
510 | | static bool |
511 | | spgLeafTest(SpGistScanOpaque so, SpGistSearchItem *item, |
512 | | SpGistLeafTuple leafTuple, bool isnull, |
513 | | bool *reportedSome, storeRes_func storeRes) |
514 | 0 | { |
515 | 0 | Datum leafValue; |
516 | 0 | double *distances; |
517 | 0 | bool result; |
518 | 0 | bool recheck; |
519 | 0 | bool recheckDistances; |
520 | |
|
521 | 0 | if (isnull) |
522 | 0 | { |
523 | | /* Should not have arrived on a nulls page unless nulls are wanted */ |
524 | 0 | Assert(so->searchNulls); |
525 | 0 | leafValue = (Datum) 0; |
526 | 0 | distances = NULL; |
527 | 0 | recheck = false; |
528 | 0 | recheckDistances = false; |
529 | 0 | result = true; |
530 | 0 | } |
531 | 0 | else |
532 | 0 | { |
533 | 0 | spgLeafConsistentIn in; |
534 | 0 | spgLeafConsistentOut out; |
535 | | |
536 | | /* use temp context for calling leaf_consistent */ |
537 | 0 | MemoryContext oldCxt = MemoryContextSwitchTo(so->tempCxt); |
538 | |
|
539 | 0 | in.scankeys = so->keyData; |
540 | 0 | in.nkeys = so->numberOfKeys; |
541 | 0 | in.orderbys = so->orderByData; |
542 | 0 | in.norderbys = so->numberOfNonNullOrderBys; |
543 | 0 | Assert(!item->isLeaf); /* else reconstructedValue would be wrong type */ |
544 | 0 | in.reconstructedValue = item->value; |
545 | 0 | in.traversalValue = item->traversalValue; |
546 | 0 | in.level = item->level; |
547 | 0 | in.returnData = so->want_itup; |
548 | 0 | in.leafDatum = SGLTDATUM(leafTuple, &so->state); |
549 | |
|
550 | 0 | out.leafValue = (Datum) 0; |
551 | 0 | out.recheck = false; |
552 | 0 | out.distances = NULL; |
553 | 0 | out.recheckDistances = false; |
554 | |
|
555 | 0 | result = DatumGetBool(FunctionCall2Coll(&so->leafConsistentFn, |
556 | 0 | so->indexCollation, |
557 | 0 | PointerGetDatum(&in), |
558 | 0 | PointerGetDatum(&out))); |
559 | 0 | recheck = out.recheck; |
560 | 0 | recheckDistances = out.recheckDistances; |
561 | 0 | leafValue = out.leafValue; |
562 | 0 | distances = out.distances; |
563 | |
|
564 | 0 | MemoryContextSwitchTo(oldCxt); |
565 | 0 | } |
566 | |
|
567 | 0 | if (result) |
568 | 0 | { |
569 | | /* item passes the scankeys */ |
570 | 0 | if (so->numberOfNonNullOrderBys > 0) |
571 | 0 | { |
572 | | /* the scan is ordered -> add the item to the queue */ |
573 | 0 | MemoryContext oldCxt = MemoryContextSwitchTo(so->traversalCxt); |
574 | 0 | SpGistSearchItem *heapItem = spgNewHeapItem(so, item->level, |
575 | 0 | leafTuple, |
576 | 0 | leafValue, |
577 | 0 | recheck, |
578 | 0 | recheckDistances, |
579 | 0 | isnull, |
580 | 0 | distances); |
581 | |
|
582 | 0 | spgAddSearchItemToQueue(so, heapItem); |
583 | |
|
584 | 0 | MemoryContextSwitchTo(oldCxt); |
585 | 0 | } |
586 | 0 | else |
587 | 0 | { |
588 | | /* non-ordered scan, so report the item right away */ |
589 | 0 | Assert(!recheckDistances); |
590 | 0 | storeRes(so, &leafTuple->heapPtr, leafValue, isnull, |
591 | 0 | leafTuple, recheck, false, NULL); |
592 | 0 | *reportedSome = true; |
593 | 0 | } |
594 | 0 | } |
595 | |
|
596 | 0 | return result; |
597 | 0 | } |
598 | | |
599 | | /* A bundle initializer for inner_consistent methods */ |
600 | | static void |
601 | | spgInitInnerConsistentIn(spgInnerConsistentIn *in, |
602 | | SpGistScanOpaque so, |
603 | | SpGistSearchItem *item, |
604 | | SpGistInnerTuple innerTuple) |
605 | 0 | { |
606 | 0 | in->scankeys = so->keyData; |
607 | 0 | in->orderbys = so->orderByData; |
608 | 0 | in->nkeys = so->numberOfKeys; |
609 | 0 | in->norderbys = so->numberOfNonNullOrderBys; |
610 | 0 | Assert(!item->isLeaf); /* else reconstructedValue would be wrong type */ |
611 | 0 | in->reconstructedValue = item->value; |
612 | 0 | in->traversalMemoryContext = so->traversalCxt; |
613 | 0 | in->traversalValue = item->traversalValue; |
614 | 0 | in->level = item->level; |
615 | 0 | in->returnData = so->want_itup; |
616 | 0 | in->allTheSame = innerTuple->allTheSame; |
617 | 0 | in->hasPrefix = (innerTuple->prefixSize > 0); |
618 | 0 | in->prefixDatum = SGITDATUM(innerTuple, &so->state); |
619 | 0 | in->nNodes = innerTuple->nNodes; |
620 | 0 | in->nodeLabels = spgExtractNodeLabels(&so->state, innerTuple); |
621 | 0 | } |
622 | | |
623 | | static SpGistSearchItem * |
624 | | spgMakeInnerItem(SpGistScanOpaque so, |
625 | | SpGistSearchItem *parentItem, |
626 | | SpGistNodeTuple tuple, |
627 | | spgInnerConsistentOut *out, int i, bool isnull, |
628 | | double *distances) |
629 | 0 | { |
630 | 0 | SpGistSearchItem *item = spgAllocSearchItem(so, isnull, distances); |
631 | |
|
632 | 0 | item->heapPtr = tuple->t_tid; |
633 | 0 | item->level = out->levelAdds ? parentItem->level + out->levelAdds[i] |
634 | 0 | : parentItem->level; |
635 | | |
636 | | /* Must copy value out of temp context */ |
637 | | /* (recall that reconstructed values are of type leafType) */ |
638 | 0 | item->value = out->reconstructedValues |
639 | 0 | ? datumCopy(out->reconstructedValues[i], |
640 | 0 | so->state.attLeafType.attbyval, |
641 | 0 | so->state.attLeafType.attlen) |
642 | 0 | : (Datum) 0; |
643 | |
|
644 | 0 | item->leafTuple = NULL; |
645 | | |
646 | | /* |
647 | | * Elements of out.traversalValues should be allocated in |
648 | | * in.traversalMemoryContext, which is actually a long lived context of |
649 | | * index scan. |
650 | | */ |
651 | 0 | item->traversalValue = |
652 | 0 | out->traversalValues ? out->traversalValues[i] : NULL; |
653 | |
|
654 | 0 | item->isLeaf = false; |
655 | 0 | item->recheck = false; |
656 | 0 | item->recheckDistances = false; |
657 | |
|
658 | 0 | return item; |
659 | 0 | } |
660 | | |
661 | | static void |
662 | | spgInnerTest(SpGistScanOpaque so, SpGistSearchItem *item, |
663 | | SpGistInnerTuple innerTuple, bool isnull) |
664 | 0 | { |
665 | 0 | MemoryContext oldCxt = MemoryContextSwitchTo(so->tempCxt); |
666 | 0 | spgInnerConsistentOut out; |
667 | 0 | int nNodes = innerTuple->nNodes; |
668 | 0 | int i; |
669 | |
|
670 | 0 | memset(&out, 0, sizeof(out)); |
671 | |
|
672 | 0 | if (!isnull) |
673 | 0 | { |
674 | 0 | spgInnerConsistentIn in; |
675 | |
|
676 | 0 | spgInitInnerConsistentIn(&in, so, item, innerTuple); |
677 | | |
678 | | /* use user-defined inner consistent method */ |
679 | 0 | FunctionCall2Coll(&so->innerConsistentFn, |
680 | 0 | so->indexCollation, |
681 | 0 | PointerGetDatum(&in), |
682 | 0 | PointerGetDatum(&out)); |
683 | 0 | } |
684 | 0 | else |
685 | 0 | { |
686 | | /* force all children to be visited */ |
687 | 0 | out.nNodes = nNodes; |
688 | 0 | out.nodeNumbers = palloc_array(int, nNodes); |
689 | 0 | for (i = 0; i < nNodes; i++) |
690 | 0 | out.nodeNumbers[i] = i; |
691 | 0 | } |
692 | | |
693 | | /* If allTheSame, they should all or none of them match */ |
694 | 0 | if (innerTuple->allTheSame && out.nNodes != 0 && out.nNodes != nNodes) |
695 | 0 | elog(ERROR, "inconsistent inner_consistent results for allTheSame inner tuple"); |
696 | | |
697 | 0 | if (out.nNodes) |
698 | 0 | { |
699 | | /* collect node pointers */ |
700 | 0 | SpGistNodeTuple node; |
701 | 0 | SpGistNodeTuple *nodes = palloc_array(SpGistNodeTuple, nNodes); |
702 | |
|
703 | 0 | SGITITERATE(innerTuple, i, node) |
704 | 0 | { |
705 | 0 | nodes[i] = node; |
706 | 0 | } |
707 | |
|
708 | 0 | MemoryContextSwitchTo(so->traversalCxt); |
709 | |
|
710 | 0 | for (i = 0; i < out.nNodes; i++) |
711 | 0 | { |
712 | 0 | int nodeN = out.nodeNumbers[i]; |
713 | 0 | SpGistSearchItem *innerItem; |
714 | 0 | double *distances; |
715 | |
|
716 | 0 | Assert(nodeN >= 0 && nodeN < nNodes); |
717 | |
|
718 | 0 | node = nodes[nodeN]; |
719 | |
|
720 | 0 | if (!ItemPointerIsValid(&node->t_tid)) |
721 | 0 | continue; |
722 | | |
723 | | /* |
724 | | * Use infinity distances if innerConsistentFn() failed to return |
725 | | * them or if is a NULL item (their distances are really unused). |
726 | | */ |
727 | 0 | distances = out.distances ? out.distances[i] : so->infDistances; |
728 | |
|
729 | 0 | innerItem = spgMakeInnerItem(so, item, node, &out, i, isnull, |
730 | 0 | distances); |
731 | |
|
732 | 0 | spgAddSearchItemToQueue(so, innerItem); |
733 | 0 | } |
734 | 0 | } |
735 | |
|
736 | 0 | MemoryContextSwitchTo(oldCxt); |
737 | 0 | } |
738 | | |
739 | | /* Returns a next item in an (ordered) scan or null if the index is exhausted */ |
740 | | static SpGistSearchItem * |
741 | | spgGetNextQueueItem(SpGistScanOpaque so) |
742 | 0 | { |
743 | 0 | if (pairingheap_is_empty(so->scanQueue)) |
744 | 0 | return NULL; /* Done when both heaps are empty */ |
745 | | |
746 | | /* Return item; caller is responsible to pfree it */ |
747 | 0 | return (SpGistSearchItem *) pairingheap_remove_first(so->scanQueue); |
748 | 0 | } |
749 | | |
750 | | enum SpGistSpecialOffsetNumbers |
751 | | { |
752 | | SpGistBreakOffsetNumber = InvalidOffsetNumber, |
753 | | SpGistRedirectOffsetNumber = MaxOffsetNumber + 1, |
754 | | SpGistErrorOffsetNumber = MaxOffsetNumber + 2, |
755 | | }; |
756 | | |
757 | | static OffsetNumber |
758 | | spgTestLeafTuple(SpGistScanOpaque so, |
759 | | SpGistSearchItem *item, |
760 | | Page page, OffsetNumber offset, |
761 | | bool isnull, bool isroot, |
762 | | bool *reportedSome, |
763 | | storeRes_func storeRes) |
764 | 0 | { |
765 | 0 | SpGistLeafTuple leafTuple = (SpGistLeafTuple) |
766 | 0 | PageGetItem(page, PageGetItemId(page, offset)); |
767 | |
|
768 | 0 | if (leafTuple->tupstate != SPGIST_LIVE) |
769 | 0 | { |
770 | 0 | if (!isroot) /* all tuples on root should be live */ |
771 | 0 | { |
772 | 0 | if (leafTuple->tupstate == SPGIST_REDIRECT) |
773 | 0 | { |
774 | | /* redirection tuple should be first in chain */ |
775 | 0 | Assert(offset == ItemPointerGetOffsetNumber(&item->heapPtr)); |
776 | | /* transfer attention to redirect point */ |
777 | 0 | item->heapPtr = ((SpGistDeadTuple) leafTuple)->pointer; |
778 | 0 | Assert(ItemPointerGetBlockNumber(&item->heapPtr) != SPGIST_METAPAGE_BLKNO); |
779 | 0 | return SpGistRedirectOffsetNumber; |
780 | 0 | } |
781 | | |
782 | 0 | if (leafTuple->tupstate == SPGIST_DEAD) |
783 | 0 | { |
784 | | /* dead tuple should be first in chain */ |
785 | 0 | Assert(offset == ItemPointerGetOffsetNumber(&item->heapPtr)); |
786 | | /* No live entries on this page */ |
787 | 0 | Assert(SGLT_GET_NEXTOFFSET(leafTuple) == InvalidOffsetNumber); |
788 | 0 | return SpGistBreakOffsetNumber; |
789 | 0 | } |
790 | 0 | } |
791 | | |
792 | | /* We should not arrive at a placeholder */ |
793 | 0 | elog(ERROR, "unexpected SPGiST tuple state: %d", leafTuple->tupstate); |
794 | 0 | return SpGistErrorOffsetNumber; |
795 | 0 | } |
796 | | |
797 | 0 | Assert(ItemPointerIsValid(&leafTuple->heapPtr)); |
798 | |
|
799 | 0 | spgLeafTest(so, item, leafTuple, isnull, reportedSome, storeRes); |
800 | |
|
801 | 0 | return SGLT_GET_NEXTOFFSET(leafTuple); |
802 | 0 | } |
803 | | |
804 | | /* |
805 | | * Walk the tree and report all tuples passing the scan quals to the storeRes |
806 | | * subroutine. |
807 | | * |
808 | | * If scanWholeIndex is true, we'll do just that. If not, we'll stop at the |
809 | | * next page boundary once we have reported at least one tuple. |
810 | | */ |
811 | | static void |
812 | | spgWalk(Relation index, SpGistScanOpaque so, bool scanWholeIndex, |
813 | | storeRes_func storeRes) |
814 | 0 | { |
815 | 0 | Buffer buffer = InvalidBuffer; |
816 | 0 | bool reportedSome = false; |
817 | |
|
818 | 0 | while (scanWholeIndex || !reportedSome) |
819 | 0 | { |
820 | 0 | SpGistSearchItem *item = spgGetNextQueueItem(so); |
821 | |
|
822 | 0 | if (item == NULL) |
823 | 0 | break; /* No more items in queue -> done */ |
824 | | |
825 | 0 | redirect: |
826 | | /* Check for interrupts, just in case of infinite loop */ |
827 | 0 | CHECK_FOR_INTERRUPTS(); |
828 | |
|
829 | 0 | if (item->isLeaf) |
830 | 0 | { |
831 | | /* We store heap items in the queue only in case of ordered search */ |
832 | 0 | Assert(so->numberOfNonNullOrderBys > 0); |
833 | 0 | storeRes(so, &item->heapPtr, item->value, item->isNull, |
834 | 0 | item->leafTuple, item->recheck, |
835 | 0 | item->recheckDistances, item->distances); |
836 | 0 | reportedSome = true; |
837 | 0 | } |
838 | 0 | else |
839 | 0 | { |
840 | 0 | BlockNumber blkno = ItemPointerGetBlockNumber(&item->heapPtr); |
841 | 0 | OffsetNumber offset = ItemPointerGetOffsetNumber(&item->heapPtr); |
842 | 0 | Page page; |
843 | 0 | bool isnull; |
844 | |
|
845 | 0 | if (buffer == InvalidBuffer) |
846 | 0 | { |
847 | 0 | buffer = ReadBuffer(index, blkno); |
848 | 0 | LockBuffer(buffer, BUFFER_LOCK_SHARE); |
849 | 0 | } |
850 | 0 | else if (blkno != BufferGetBlockNumber(buffer)) |
851 | 0 | { |
852 | 0 | UnlockReleaseBuffer(buffer); |
853 | 0 | buffer = ReadBuffer(index, blkno); |
854 | 0 | LockBuffer(buffer, BUFFER_LOCK_SHARE); |
855 | 0 | } |
856 | | |
857 | | /* else new pointer points to the same page, no work needed */ |
858 | |
|
859 | 0 | page = BufferGetPage(buffer); |
860 | |
|
861 | 0 | isnull = SpGistPageStoresNulls(page) ? true : false; |
862 | |
|
863 | 0 | if (SpGistPageIsLeaf(page)) |
864 | 0 | { |
865 | | /* Page is a leaf - that is, all its tuples are heap items */ |
866 | 0 | OffsetNumber max = PageGetMaxOffsetNumber(page); |
867 | |
|
868 | 0 | if (SpGistBlockIsRoot(blkno)) |
869 | 0 | { |
870 | | /* When root is a leaf, examine all its tuples */ |
871 | 0 | for (offset = FirstOffsetNumber; offset <= max; offset++) |
872 | 0 | (void) spgTestLeafTuple(so, item, page, offset, |
873 | 0 | isnull, true, |
874 | 0 | &reportedSome, storeRes); |
875 | 0 | } |
876 | 0 | else |
877 | 0 | { |
878 | | /* Normal case: just examine the chain we arrived at */ |
879 | 0 | while (offset != InvalidOffsetNumber) |
880 | 0 | { |
881 | 0 | Assert(offset >= FirstOffsetNumber && offset <= max); |
882 | 0 | offset = spgTestLeafTuple(so, item, page, offset, |
883 | 0 | isnull, false, |
884 | 0 | &reportedSome, storeRes); |
885 | 0 | if (offset == SpGistRedirectOffsetNumber) |
886 | 0 | goto redirect; |
887 | 0 | } |
888 | 0 | } |
889 | 0 | } |
890 | 0 | else /* page is inner */ |
891 | 0 | { |
892 | 0 | SpGistInnerTuple innerTuple = (SpGistInnerTuple) |
893 | 0 | PageGetItem(page, PageGetItemId(page, offset)); |
894 | |
|
895 | 0 | if (innerTuple->tupstate != SPGIST_LIVE) |
896 | 0 | { |
897 | 0 | if (innerTuple->tupstate == SPGIST_REDIRECT) |
898 | 0 | { |
899 | | /* transfer attention to redirect point */ |
900 | 0 | item->heapPtr = ((SpGistDeadTuple) innerTuple)->pointer; |
901 | 0 | Assert(ItemPointerGetBlockNumber(&item->heapPtr) != |
902 | 0 | SPGIST_METAPAGE_BLKNO); |
903 | 0 | goto redirect; |
904 | 0 | } |
905 | 0 | elog(ERROR, "unexpected SPGiST tuple state: %d", |
906 | 0 | innerTuple->tupstate); |
907 | 0 | } |
908 | | |
909 | 0 | spgInnerTest(so, item, innerTuple, isnull); |
910 | 0 | } |
911 | 0 | } |
912 | | |
913 | | /* done with this scan item */ |
914 | 0 | spgFreeSearchItem(so, item); |
915 | | /* clear temp context before proceeding to the next one */ |
916 | 0 | MemoryContextReset(so->tempCxt); |
917 | 0 | } |
918 | | |
919 | 0 | if (buffer != InvalidBuffer) |
920 | 0 | UnlockReleaseBuffer(buffer); |
921 | 0 | } |
922 | | |
923 | | |
924 | | /* storeRes subroutine for getbitmap case */ |
925 | | static void |
926 | | storeBitmap(SpGistScanOpaque so, ItemPointer heapPtr, |
927 | | Datum leafValue, bool isnull, |
928 | | SpGistLeafTuple leafTuple, bool recheck, |
929 | | bool recheckDistances, double *distances) |
930 | 0 | { |
931 | 0 | Assert(!recheckDistances && !distances); |
932 | 0 | tbm_add_tuples(so->tbm, heapPtr, 1, recheck); |
933 | 0 | so->ntids++; |
934 | 0 | } |
935 | | |
936 | | int64 |
937 | | spggetbitmap(IndexScanDesc scan, TIDBitmap *tbm) |
938 | 0 | { |
939 | 0 | SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque; |
940 | | |
941 | | /* Copy want_itup to *so so we don't need to pass it around separately */ |
942 | 0 | so->want_itup = false; |
943 | |
|
944 | 0 | so->tbm = tbm; |
945 | 0 | so->ntids = 0; |
946 | |
|
947 | 0 | spgWalk(scan->indexRelation, so, true, storeBitmap); |
948 | |
|
949 | 0 | return so->ntids; |
950 | 0 | } |
951 | | |
952 | | /* storeRes subroutine for gettuple case */ |
953 | | static void |
954 | | storeGettuple(SpGistScanOpaque so, ItemPointer heapPtr, |
955 | | Datum leafValue, bool isnull, |
956 | | SpGistLeafTuple leafTuple, bool recheck, |
957 | | bool recheckDistances, double *nonNullDistances) |
958 | 0 | { |
959 | 0 | Assert(so->nPtrs < MaxIndexTuplesPerPage); |
960 | 0 | so->heapPtrs[so->nPtrs] = *heapPtr; |
961 | 0 | so->recheck[so->nPtrs] = recheck; |
962 | 0 | so->recheckDistances[so->nPtrs] = recheckDistances; |
963 | |
|
964 | 0 | if (so->numberOfOrderBys > 0) |
965 | 0 | { |
966 | 0 | if (isnull || so->numberOfNonNullOrderBys <= 0) |
967 | 0 | so->distances[so->nPtrs] = NULL; |
968 | 0 | else |
969 | 0 | { |
970 | 0 | IndexOrderByDistance *distances = palloc_array(IndexOrderByDistance, |
971 | 0 | so->numberOfOrderBys); |
972 | 0 | int i; |
973 | |
|
974 | 0 | for (i = 0; i < so->numberOfOrderBys; i++) |
975 | 0 | { |
976 | 0 | int offset = so->nonNullOrderByOffsets[i]; |
977 | |
|
978 | 0 | if (offset >= 0) |
979 | 0 | { |
980 | | /* Copy non-NULL distance value */ |
981 | 0 | distances[i].value = nonNullDistances[offset]; |
982 | 0 | distances[i].isnull = false; |
983 | 0 | } |
984 | 0 | else |
985 | 0 | { |
986 | | /* Set distance's NULL flag. */ |
987 | 0 | distances[i].value = 0.0; |
988 | 0 | distances[i].isnull = true; |
989 | 0 | } |
990 | 0 | } |
991 | |
|
992 | 0 | so->distances[so->nPtrs] = distances; |
993 | 0 | } |
994 | 0 | } |
995 | |
|
996 | 0 | if (so->want_itup) |
997 | 0 | { |
998 | | /* |
999 | | * Reconstruct index data. We have to copy the datum out of the temp |
1000 | | * context anyway, so we may as well create the tuple here. |
1001 | | */ |
1002 | 0 | Datum leafDatums[INDEX_MAX_KEYS]; |
1003 | 0 | bool leafIsnulls[INDEX_MAX_KEYS]; |
1004 | | |
1005 | | /* We only need to deform the old tuple if it has INCLUDE attributes */ |
1006 | 0 | if (so->state.leafTupDesc->natts > 1) |
1007 | 0 | spgDeformLeafTuple(leafTuple, so->state.leafTupDesc, |
1008 | 0 | leafDatums, leafIsnulls, isnull); |
1009 | |
|
1010 | 0 | leafDatums[spgKeyColumn] = leafValue; |
1011 | 0 | leafIsnulls[spgKeyColumn] = isnull; |
1012 | |
|
1013 | 0 | so->reconTups[so->nPtrs] = heap_form_tuple(so->reconTupDesc, |
1014 | 0 | leafDatums, |
1015 | 0 | leafIsnulls); |
1016 | 0 | } |
1017 | 0 | so->nPtrs++; |
1018 | 0 | } |
1019 | | |
1020 | | bool |
1021 | | spggettuple(IndexScanDesc scan, ScanDirection dir) |
1022 | 0 | { |
1023 | 0 | SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque; |
1024 | |
|
1025 | 0 | if (dir != ForwardScanDirection) |
1026 | 0 | elog(ERROR, "SP-GiST only supports forward scan direction"); |
1027 | | |
1028 | | /* Copy want_itup to *so so we don't need to pass it around separately */ |
1029 | 0 | so->want_itup = scan->xs_want_itup; |
1030 | |
|
1031 | 0 | for (;;) |
1032 | 0 | { |
1033 | 0 | if (so->iPtr < so->nPtrs) |
1034 | 0 | { |
1035 | | /* continuing to return reported tuples */ |
1036 | 0 | scan->xs_heaptid = so->heapPtrs[so->iPtr]; |
1037 | 0 | scan->xs_recheck = so->recheck[so->iPtr]; |
1038 | 0 | scan->xs_hitup = so->reconTups[so->iPtr]; |
1039 | |
|
1040 | 0 | if (so->numberOfOrderBys > 0) |
1041 | 0 | index_store_float8_orderby_distances(scan, so->orderByTypes, |
1042 | 0 | so->distances[so->iPtr], |
1043 | 0 | so->recheckDistances[so->iPtr]); |
1044 | 0 | so->iPtr++; |
1045 | 0 | return true; |
1046 | 0 | } |
1047 | | |
1048 | 0 | if (so->numberOfOrderBys > 0) |
1049 | 0 | { |
1050 | | /* Must pfree distances to avoid memory leak */ |
1051 | 0 | int i; |
1052 | |
|
1053 | 0 | for (i = 0; i < so->nPtrs; i++) |
1054 | 0 | if (so->distances[i]) |
1055 | 0 | pfree(so->distances[i]); |
1056 | 0 | } |
1057 | |
|
1058 | 0 | if (so->want_itup) |
1059 | 0 | { |
1060 | | /* Must pfree reconstructed tuples to avoid memory leak */ |
1061 | 0 | int i; |
1062 | |
|
1063 | 0 | for (i = 0; i < so->nPtrs; i++) |
1064 | 0 | pfree(so->reconTups[i]); |
1065 | 0 | } |
1066 | 0 | so->iPtr = so->nPtrs = 0; |
1067 | |
|
1068 | 0 | spgWalk(scan->indexRelation, so, false, storeGettuple); |
1069 | |
|
1070 | 0 | if (so->nPtrs == 0) |
1071 | 0 | break; /* must have completed scan */ |
1072 | 0 | } |
1073 | | |
1074 | 0 | return false; |
1075 | 0 | } |
1076 | | |
1077 | | bool |
1078 | | spgcanreturn(Relation index, int attno) |
1079 | 0 | { |
1080 | 0 | SpGistCache *cache; |
1081 | | |
1082 | | /* INCLUDE attributes can always be fetched for index-only scans */ |
1083 | 0 | if (attno > 1) |
1084 | 0 | return true; |
1085 | | |
1086 | | /* We can do it if the opclass config function says so */ |
1087 | 0 | cache = spgGetCache(index); |
1088 | |
|
1089 | 0 | return cache->config.canReturnData; |
1090 | 0 | } |