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