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