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