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