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