LCOV - code coverage report
Current view: top level - src/backend/access/spgist - spgscan.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 93.6 % 425 398
Test Date: 2026-02-17 16:40:31 Functions: 100.0 % 23 23
Legend: Lines:     hit not hit

            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
      42      2197594 : pairingheap_SpGistSearchItem_cmp(const pairingheap_node *a,
      43              :                                  const pairingheap_node *b, void *arg)
      44              : {
      45      2197594 :     const SpGistSearchItem *sa = (const SpGistSearchItem *) a;
      46      2197594 :     const SpGistSearchItem *sb = (const SpGistSearchItem *) b;
      47      2197594 :     SpGistScanOpaque so = (SpGistScanOpaque) arg;
      48              :     int         i;
      49              : 
      50      2197594 :     if (sa->isNull)
      51              :     {
      52          228 :         if (!sb->isNull)
      53          207 :             return -1;
      54              :     }
      55      2197366 :     else if (sb->isNull)
      56              :     {
      57          204 :         return 1;
      58              :     }
      59              :     else
      60              :     {
      61              :         /* Order according to distance comparison */
      62      2318096 :         for (i = 0; i < so->numberOfNonNullOrderBys; i++)
      63              :         {
      64      2017254 :             if (isnan(sa->distances[i]) && isnan(sb->distances[i]))
      65            0 :                 continue;       /* NaN == NaN */
      66      2017254 :             if (isnan(sa->distances[i]))
      67            0 :                 return -1;      /* NaN > number */
      68      2017254 :             if (isnan(sb->distances[i]))
      69            0 :                 return 1;       /* number < NaN */
      70      2017254 :             if (sa->distances[i] != sb->distances[i])
      71      1896320 :                 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       300863 :     if (sa->isLeaf && !sb->isLeaf)
      77         2208 :         return 1;
      78       298655 :     if (!sa->isLeaf && sb->isLeaf)
      79         2423 :         return -1;
      80              : 
      81       296232 :     return 0;
      82              : }
      83              : 
      84              : static void
      85       229202 : 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) */
      89       229202 :     if (!(item->isLeaf ? so->state.attType.attbyval :
      90       458404 :           so->state.attLeafType.attbyval) &&
      91       229202 :         DatumGetPointer(item->value) != NULL)
      92       144389 :         pfree(DatumGetPointer(item->value));
      93              : 
      94       229202 :     if (item->leafTuple)
      95           30 :         pfree(item->leafTuple);
      96              : 
      97       229202 :     if (item->traversalValue)
      98        22212 :         pfree(item->traversalValue);
      99              : 
     100       229202 :     pfree(item);
     101       229202 : }
     102              : 
     103              : /*
     104              :  * Add SpGistSearchItem to queue
     105              :  *
     106              :  * Called in queue context
     107              :  */
     108              : static void
     109       230575 : spgAddSearchItemToQueue(SpGistScanOpaque so, SpGistSearchItem *item)
     110              : {
     111       230575 :     pairingheap_add(so->scanQueue, &item->phNode);
     112       230575 : }
     113              : 
     114              : static SpGistSearchItem *
     115       230575 : spgAllocSearchItem(SpGistScanOpaque so, bool isnull, double *distances)
     116              : {
     117              :     /* allocate distance array only for non-NULL items */
     118              :     SpGistSearchItem *item =
     119       230575 :         palloc(SizeOfSpGistSearchItem(isnull ? 0 : so->numberOfNonNullOrderBys));
     120              : 
     121       230575 :     item->isNull = isnull;
     122              : 
     123       230575 :     if (!isnull && so->numberOfNonNullOrderBys > 0)
     124       188984 :         memcpy(item->distances, distances,
     125       188984 :                sizeof(item->distances[0]) * so->numberOfNonNullOrderBys);
     126              : 
     127       230575 :     return item;
     128              : }
     129              : 
     130              : static void
     131          491 : spgAddStartItem(SpGistScanOpaque so, bool isnull)
     132              : {
     133              :     SpGistSearchItem *startEntry =
     134          491 :         spgAllocSearchItem(so, isnull, so->zeroDistances);
     135              : 
     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;
     142          491 :     startEntry->leafTuple = NULL;
     143          491 :     startEntry->traversalValue = NULL;
     144          491 :     startEntry->recheck = false;
     145          491 :     startEntry->recheckDistances = false;
     146              : 
     147          491 :     spgAddSearchItemToQueue(so, startEntry);
     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              : 
     159          464 :     MemoryContextReset(so->traversalCxt);
     160              : 
     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              : 
     166          464 :     if (so->searchNulls)
     167              :         /* Add a work item to scan the null index entries */
     168           33 :         spgAddStartItem(so, true);
     169              : 
     170          464 :     if (so->searchNonNulls)
     171              :         /* Add a work item to scan the non-null index entries */
     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              : 
     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++)
     192           33 :             pfree(so->reconTups[i]);
     193              :     }
     194          464 :     so->iPtr = so->nPtrs = 0;
     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
     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              : 
     218          464 :     so->numberOfOrderBys = scan->numberOfOrderBys;
     219          464 :     so->orderByData = scan->orderByData;
     220              : 
     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              : 
     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 */
     273            0 :             qual_ok = false;
     274            0 :             break;
     275              :         }
     276              :         else
     277              :         {
     278              :             /* ordinary qual, propagate into so->keyData */
     279          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)
     287            0 :         qual_ok = false;
     288              : 
     289              :     /* Emit results */
     290          437 :     if (qual_ok)
     291              :     {
     292          437 :         so->searchNulls = haveIsNull;
     293          437 :         so->searchNonNulls = haveNotNull;
     294          437 :         so->numberOfKeys = nkeys;
     295              :     }
     296              :     else
     297              :     {
     298            0 :         so->searchNulls = false;
     299            0 :         so->searchNonNulls = false;
     300            0 :         so->numberOfKeys = 0;
     301              :     }
     302              : }
     303              : 
     304              : IndexScanDesc
     305          452 : spgbeginscan(Relation rel, int keysz, int orderbysz)
     306              : {
     307              :     IndexScanDesc scan;
     308              :     SpGistScanOpaque so;
     309              :     int         i;
     310              : 
     311          452 :     scan = RelationGetIndexScan(rel, keysz, orderbysz);
     312              : 
     313          452 :     so = palloc0_object(SpGistScanOpaqueData);
     314          452 :     if (keysz > 0)
     315          431 :         so->keyData = palloc_array(ScanKeyData, keysz);
     316              :     else
     317           21 :         so->keyData = NULL;
     318          452 :     initSpGistState(&so->state, scan->indexRelation);
     319              : 
     320          452 :     so->tempCxt = AllocSetContextCreate(CurrentMemoryContext,
     321              :                                         "SP-GiST search temporary context",
     322              :                                         ALLOCSET_DEFAULT_SIZES);
     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              :      */
     333          452 :     so->reconTupDesc = scan->xs_hitupdesc =
     334          452 :         getSpGistTupleDesc(rel, &so->state.attType);
     335              : 
     336              :     /* Allocate various arrays needed for order-by scans */
     337          452 :     if (scan->numberOfOrderBys > 0)
     338              :     {
     339              :         /* This will be filled in spgrescan, but allocate the space here */
     340           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              : 
     347           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              : 
     353           33 :         scan->xs_orderbyvals = palloc0_array(Datum, scan->numberOfOrderBys);
     354           33 :         scan->xs_orderbynulls = palloc_array(bool, scan->numberOfOrderBys);
     355           33 :         memset(scan->xs_orderbynulls, true,
     356           33 :                sizeof(bool) * scan->numberOfOrderBys);
     357              :     }
     358              : 
     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              : 
     369          452 :     scan->opaque = so;
     370              : 
     371          452 :     return scan;
     372              : }
     373              : 
     374              : void
     375          464 : spgrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
     376              :           ScanKey orderbys, int norderbys)
     377              : {
     378          464 :     SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque;
     379              : 
     380              :     /* copy scankeys into local storage */
     381          464 :     if (scankey && scan->numberOfKeys > 0)
     382          437 :         memcpy(scan->keyData, scankey, scan->numberOfKeys * sizeof(ScanKeyData));
     383              : 
     384              :     /* initialize order-by data if needed */
     385          464 :     if (orderbys && scan->numberOfOrderBys > 0)
     386              :     {
     387              :         int         i;
     388              : 
     389           39 :         memcpy(scan->orderByData, orderbys, scan->numberOfOrderBys * sizeof(ScanKeyData));
     390              : 
     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 */
     412          464 :     spgPrepareScanKeys(scan);
     413              : 
     414              :     /* set up starting queue entries */
     415          464 :     resetSpGistScanOpaque(so);
     416              : 
     417              :     /* count an indexscan for stats */
     418          464 :     pgstat_count_index_scan(scan->indexRelation);
     419          464 :     if (scan->instrument)
     420          464 :         scan->instrument->nsearches++;
     421          464 : }
     422              : 
     423              : void
     424          452 : spgendscan(IndexScanDesc scan)
     425              : {
     426          452 :     SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque;
     427              : 
     428          452 :     MemoryContextDelete(so->tempCxt);
     429          452 :     MemoryContextDelete(so->traversalCxt);
     430              : 
     431          452 :     if (so->keyData)
     432          431 :         pfree(so->keyData);
     433              : 
     434          452 :     if (so->state.leafTupDesc &&
     435          452 :         so->state.leafTupDesc != RelationGetDescr(so->state.index))
     436            4 :         FreeTupleDesc(so->state.leafTupDesc);
     437              : 
     438          452 :     if (so->state.deadTupleStorage)
     439          452 :         pfree(so->state.deadTupleStorage);
     440              : 
     441          452 :     if (scan->numberOfOrderBys > 0)
     442              :     {
     443           33 :         pfree(so->orderByTypes);
     444           33 :         pfree(so->nonNullOrderByOffsets);
     445           33 :         pfree(so->zeroDistances);
     446           33 :         pfree(so->infDistances);
     447           33 :         pfree(scan->xs_orderbyvals);
     448           33 :         pfree(scan->xs_orderbynulls);
     449              :     }
     450              : 
     451          452 :     pfree(so);
     452          452 : }
     453              : 
     454              : /*
     455              :  * Leaf SpGistSearchItem constructor, called in queue context
     456              :  */
     457              : static SpGistSearchItem *
     458       182945 : spgNewHeapItem(SpGistScanOpaque so, int level, SpGistLeafTuple leafTuple,
     459              :                Datum leafValue, bool recheck, bool recheckDistances,
     460              :                bool isnull, double *distances)
     461              : {
     462       182945 :     SpGistSearchItem *item = spgAllocSearchItem(so, isnull, distances);
     463              : 
     464       182945 :     item->level = level;
     465       182945 :     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              :      */
     473       182945 :     if (so->want_itup)
     474              :     {
     475       139667 :         item->value = isnull ? (Datum) 0 :
     476       139649 :             datumCopy(leafValue, so->state.attType.attbyval,
     477       139649 :                       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              :          */
     483       139667 :         if (so->state.leafTupDesc->natts > 1)
     484              :         {
     485           92 :             item->leafTuple = palloc(leafTuple->size);
     486           92 :             memcpy(item->leafTuple, leafTuple, leafTuple->size);
     487              :         }
     488              :         else
     489       139575 :             item->leafTuple = NULL;
     490              :     }
     491              :     else
     492              :     {
     493        43278 :         item->value = (Datum) 0;
     494        43278 :         item->leafTuple = NULL;
     495              :     }
     496       182945 :     item->traversalValue = NULL;
     497       182945 :     item->isLeaf = true;
     498       182945 :     item->recheck = recheck;
     499       182945 :     item->recheckDistances = recheckDistances;
     500              : 
     501       182945 :     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
     511      1272478 : 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              : 
     521      1272478 :     if (isnull)
     522              :     {
     523              :         /* Should not have arrived on a nulls page unless nulls are wanted */
     524              :         Assert(so->searchNulls);
     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      1272418 :         MemoryContext oldCxt = MemoryContextSwitchTo(so->tempCxt);
     538              : 
     539      1272418 :         in.scankeys = so->keyData;
     540      1272418 :         in.nkeys = so->numberOfKeys;
     541      1272418 :         in.orderbys = so->orderByData;
     542      1272418 :         in.norderbys = so->numberOfNonNullOrderBys;
     543              :         Assert(!item->isLeaf);   /* else reconstructedValue would be wrong type */
     544      1272418 :         in.reconstructedValue = item->value;
     545      1272418 :         in.traversalValue = item->traversalValue;
     546      1272418 :         in.level = item->level;
     547      1272418 :         in.returnData = so->want_itup;
     548      1272418 :         in.leafDatum = SGLTDATUM(leafTuple, &so->state);
     549              : 
     550      1272418 :         out.leafValue = (Datum) 0;
     551      1272418 :         out.recheck = false;
     552      1272418 :         out.distances = NULL;
     553      1272418 :         out.recheckDistances = false;
     554              : 
     555      1272418 :         result = DatumGetBool(FunctionCall2Coll(&so->leafConsistentFn,
     556              :                                                 so->indexCollation,
     557              :                                                 PointerGetDatum(&in),
     558              :                                                 PointerGetDatum(&out)));
     559      1272418 :         recheck = out.recheck;
     560      1272418 :         recheckDistances = out.recheckDistances;
     561      1272418 :         leafValue = out.leafValue;
     562      1272418 :         distances = out.distances;
     563              : 
     564      1272418 :         MemoryContextSwitchTo(oldCxt);
     565              :     }
     566              : 
     567      1272478 :     if (result)
     568              :     {
     569              :         /* item passes the scankeys */
     570      1030612 :         if (so->numberOfNonNullOrderBys > 0)
     571              :         {
     572              :             /* the scan is ordered -> add the item to the queue */
     573       182945 :             MemoryContext oldCxt = MemoryContextSwitchTo(so->traversalCxt);
     574       182945 :             SpGistSearchItem *heapItem = spgNewHeapItem(so, item->level,
     575              :                                                         leafTuple,
     576              :                                                         leafValue,
     577              :                                                         recheck,
     578              :                                                         recheckDistances,
     579              :                                                         isnull,
     580              :                                                         distances);
     581              : 
     582       182945 :             spgAddSearchItemToQueue(so, heapItem);
     583              : 
     584       182945 :             MemoryContextSwitchTo(oldCxt);
     585              :         }
     586              :         else
     587              :         {
     588              :             /* non-ordered scan, so report the item right away */
     589              :             Assert(!recheckDistances);
     590       847667 :             storeRes(so, &leafTuple->heapPtr, leafValue, isnull,
     591              :                      leafTuple, recheck, false, NULL);
     592       847667 :             *reportedSome = true;
     593              :         }
     594              :     }
     595              : 
     596      1272478 :     return result;
     597              : }
     598              : 
     599              : /* A bundle initializer for inner_consistent methods */
     600              : static void
     601        12244 : spgInitInnerConsistentIn(spgInnerConsistentIn *in,
     602              :                          SpGistScanOpaque so,
     603              :                          SpGistSearchItem *item,
     604              :                          SpGistInnerTuple innerTuple)
     605              : {
     606        12244 :     in->scankeys = so->keyData;
     607        12244 :     in->orderbys = so->orderByData;
     608        12244 :     in->nkeys = so->numberOfKeys;
     609        12244 :     in->norderbys = so->numberOfNonNullOrderBys;
     610              :     Assert(!item->isLeaf);       /* else reconstructedValue would be wrong type */
     611        12244 :     in->reconstructedValue = item->value;
     612        12244 :     in->traversalMemoryContext = so->traversalCxt;
     613        12244 :     in->traversalValue = item->traversalValue;
     614        12244 :     in->level = item->level;
     615        12244 :     in->returnData = so->want_itup;
     616        12244 :     in->allTheSame = innerTuple->allTheSame;
     617        12244 :     in->hasPrefix = (innerTuple->prefixSize > 0);
     618        12244 :     in->prefixDatum = SGITDATUM(innerTuple, &so->state);
     619        12244 :     in->nNodes = innerTuple->nNodes;
     620        12244 :     in->nodeLabels = spgExtractNodeLabels(&so->state, innerTuple);
     621        12244 : }
     622              : 
     623              : static SpGistSearchItem *
     624        47139 : spgMakeInnerItem(SpGistScanOpaque so,
     625              :                  SpGistSearchItem *parentItem,
     626              :                  SpGistNodeTuple tuple,
     627              :                  spgInnerConsistentOut *out, int i, bool isnull,
     628              :                  double *distances)
     629              : {
     630        47139 :     SpGistSearchItem *item = spgAllocSearchItem(so, isnull, distances);
     631              : 
     632        47139 :     item->heapPtr = tuple->t_tid;
     633       115100 :     item->level = out->levelAdds ? parentItem->level + out->levelAdds[i]
     634        47139 :         : parentItem->level;
     635              : 
     636              :     /* Must copy value out of temp context */
     637              :     /* (recall that reconstructed values are of type leafType) */
     638        94278 :     item->value = out->reconstructedValues
     639         6008 :         ? datumCopy(out->reconstructedValues[i],
     640         6008 :                     so->state.attLeafType.attbyval,
     641         6008 :                     so->state.attLeafType.attlen)
     642        47139 :         : (Datum) 0;
     643              : 
     644        47139 :     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              :      */
     651        47139 :     item->traversalValue =
     652        47139 :         out->traversalValues ? out->traversalValues[i] : NULL;
     653              : 
     654        47139 :     item->isLeaf = false;
     655        47139 :     item->recheck = false;
     656        47139 :     item->recheckDistances = false;
     657              : 
     658        47139 :     return item;
     659              : }
     660              : 
     661              : static void
     662        12244 : spgInnerTest(SpGistScanOpaque so, SpGistSearchItem *item,
     663              :              SpGistInnerTuple innerTuple, bool isnull)
     664              : {
     665        12244 :     MemoryContext oldCxt = MemoryContextSwitchTo(so->tempCxt);
     666              :     spgInnerConsistentOut out;
     667        12244 :     int         nNodes = innerTuple->nNodes;
     668              :     int         i;
     669              : 
     670        12244 :     memset(&out, 0, sizeof(out));
     671              : 
     672        12244 :     if (!isnull)
     673              :     {
     674              :         spgInnerConsistentIn in;
     675              : 
     676        12244 :         spgInitInnerConsistentIn(&in, so, item, innerTuple);
     677              : 
     678              :         /* use user-defined inner consistent method */
     679        12244 :         FunctionCall2Coll(&so->innerConsistentFn,
     680              :                           so->indexCollation,
     681              :                           PointerGetDatum(&in),
     682              :                           PointerGetDatum(&out));
     683              :     }
     684              :     else
     685              :     {
     686              :         /* force all children to be visited */
     687            0 :         out.nNodes = nNodes;
     688            0 :         out.nodeNumbers = palloc_array(int, nNodes);
     689            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 */
     694        12244 :     if (innerTuple->allTheSame && out.nNodes != 0 && out.nNodes != nNodes)
     695            0 :         elog(ERROR, "inconsistent inner_consistent results for allTheSame inner tuple");
     696              : 
     697        12244 :     if (out.nNodes)
     698              :     {
     699              :         /* collect node pointers */
     700              :         SpGistNodeTuple node;
     701        12244 :         SpGistNodeTuple *nodes = palloc_array(SpGistNodeTuple, nNodes);
     702              : 
     703       113277 :         SGITITERATE(innerTuple, i, node)
     704              :         {
     705       101033 :             nodes[i] = node;
     706              :         }
     707              : 
     708        12244 :         MemoryContextSwitchTo(so->traversalCxt);
     709              : 
     710        92914 :         for (i = 0; i < out.nNodes; i++)
     711              :         {
     712        80670 :             int         nodeN = out.nodeNumbers[i];
     713              :             SpGistSearchItem *innerItem;
     714              :             double     *distances;
     715              : 
     716              :             Assert(nodeN >= 0 && nodeN < nNodes);
     717              : 
     718        80670 :             node = nodes[nodeN];
     719              : 
     720        80670 :             if (!ItemPointerIsValid(&node->t_tid))
     721        33531 :                 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        47139 :             distances = out.distances ? out.distances[i] : so->infDistances;
     728              : 
     729        47139 :             innerItem = spgMakeInnerItem(so, item, node, &out, i, isnull,
     730              :                                          distances);
     731              : 
     732        47139 :             spgAddSearchItemToQueue(so, innerItem);
     733              :         }
     734              :     }
     735              : 
     736        12244 :     MemoryContextSwitchTo(oldCxt);
     737        12244 : }
     738              : 
     739              : /* Returns a next item in an (ordered) scan or null if the index is exhausted */
     740              : static SpGistSearchItem *
     741       229645 : spgGetNextQueueItem(SpGistScanOpaque so)
     742              : {
     743       229645 :     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       229202 :     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      1272478 : 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)
     766      1272478 :         PageGetItem(page, PageGetItemId(page, offset));
     767              : 
     768      1272478 :     if (leafTuple->tupstate != SPGIST_LIVE)
     769              :     {
     770            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              :                 Assert(offset == ItemPointerGetOffsetNumber(&item->heapPtr));
     776              :                 /* transfer attention to redirect point */
     777            0 :                 item->heapPtr = ((SpGistDeadTuple) leafTuple)->pointer;
     778              :                 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              :                 Assert(offset == ItemPointerGetOffsetNumber(&item->heapPtr));
     786              :                 /* No live entries on this page */
     787              :                 Assert(SGLT_GET_NEXTOFFSET(leafTuple) == InvalidOffsetNumber);
     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              : 
     797              :     Assert(ItemPointerIsValid(&leafTuple->heapPtr));
     798              : 
     799      1272478 :     spgLeafTest(so, item, leafTuple, isnull, reportedSome, storeRes);
     800              : 
     801      1272478 :     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
     812       188662 : spgWalk(Relation index, SpGistScanOpaque so, bool scanWholeIndex,
     813              :         storeRes_func storeRes)
     814              : {
     815       188662 :     Buffer      buffer = InvalidBuffer;
     816       188662 :     bool        reportedSome = false;
     817              : 
     818       417864 :     while (scanWholeIndex || !reportedSome)
     819              :     {
     820       229645 :         SpGistSearchItem *item = spgGetNextQueueItem(so);
     821              : 
     822       229645 :         if (item == NULL)
     823          443 :             break;              /* No more items in queue -> done */
     824              : 
     825       229202 : redirect:
     826              :         /* Check for interrupts, just in case of infinite loop */
     827       229202 :         CHECK_FOR_INTERRUPTS();
     828              : 
     829       229202 :         if (item->isLeaf)
     830              :         {
     831              :             /* We store heap items in the queue only in case of ordered search */
     832              :             Assert(so->numberOfNonNullOrderBys > 0);
     833       181677 :             storeRes(so, &item->heapPtr, item->value, item->isNull,
     834       181677 :                      item->leafTuple, item->recheck,
     835       181677 :                      item->recheckDistances, item->distances);
     836       181677 :             reportedSome = true;
     837              :         }
     838              :         else
     839              :         {
     840        47525 :             BlockNumber blkno = ItemPointerGetBlockNumber(&item->heapPtr);
     841        47525 :             OffsetNumber offset = ItemPointerGetOffsetNumber(&item->heapPtr);
     842              :             Page        page;
     843              :             bool        isnull;
     844              : 
     845        47525 :             if (buffer == InvalidBuffer)
     846              :             {
     847         9940 :                 buffer = ReadBuffer(index, blkno);
     848         9940 :                 LockBuffer(buffer, BUFFER_LOCK_SHARE);
     849              :             }
     850        37585 :             else if (blkno != BufferGetBlockNumber(buffer))
     851              :             {
     852        26072 :                 UnlockReleaseBuffer(buffer);
     853        26072 :                 buffer = ReadBuffer(index, blkno);
     854        26072 :                 LockBuffer(buffer, BUFFER_LOCK_SHARE);
     855              :             }
     856              : 
     857              :             /* else new pointer points to the same page, no work needed */
     858              : 
     859        47525 :             page = BufferGetPage(buffer);
     860              : 
     861        47525 :             isnull = SpGistPageStoresNulls(page) ? true : false;
     862              : 
     863        47525 :             if (SpGistPageIsLeaf(page))
     864              :             {
     865              :                 /* Page is a leaf - that is, all its tuples are heap items */
     866        35281 :                 OffsetNumber max = PageGetMaxOffsetNumber(page);
     867              : 
     868        35281 :                 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      1304696 :                     while (offset != InvalidOffsetNumber)
     880              :                     {
     881              :                         Assert(offset >= FirstOffsetNumber && offset <= max);
     882      1269514 :                         offset = spgTestLeafTuple(so, item, page, offset,
     883              :                                                   isnull, false,
     884              :                                                   &reportedSome, storeRes);
     885      1269514 :                         if (offset == SpGistRedirectOffsetNumber)
     886            0 :                             goto redirect;
     887              :                     }
     888              :                 }
     889              :             }
     890              :             else                /* page is inner */
     891              :             {
     892              :                 SpGistInnerTuple innerTuple = (SpGistInnerTuple)
     893        12244 :                     PageGetItem(page, PageGetItemId(page, offset));
     894              : 
     895        12244 :                 if (innerTuple->tupstate != SPGIST_LIVE)
     896              :                 {
     897            0 :                     if (innerTuple->tupstate == SPGIST_REDIRECT)
     898              :                     {
     899              :                         /* transfer attention to redirect point */
     900            0 :                         item->heapPtr = ((SpGistDeadTuple) innerTuple)->pointer;
     901              :                         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              : 
     909        12244 :                 spgInnerTest(so, item, innerTuple, isnull);
     910              :             }
     911              :         }
     912              : 
     913              :         /* done with this scan item */
     914       229202 :         spgFreeSearchItem(so, item);
     915              :         /* clear temp context before proceeding to the next one */
     916       229202 :         MemoryContextReset(so->tempCxt);
     917              :     }
     918              : 
     919       188662 :     if (buffer != InvalidBuffer)
     920         9940 :         UnlockReleaseBuffer(buffer);
     921       188662 : }
     922              : 
     923              : 
     924              : /* storeRes subroutine for getbitmap case */
     925              : static void
     926       526107 : storeBitmap(SpGistScanOpaque so, ItemPointer heapPtr,
     927              :             Datum leafValue, bool isnull,
     928              :             SpGistLeafTuple leafTuple, bool recheck,
     929              :             bool recheckDistances, double *distances)
     930              : {
     931              :     Assert(!recheckDistances && !distances);
     932       526107 :     tbm_add_tuples(so->tbm, heapPtr, 1, recheck);
     933       526107 :     so->ntids++;
     934       526107 : }
     935              : 
     936              : int64
     937          174 : spggetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
     938              : {
     939          174 :     SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque;
     940              : 
     941              :     /* Copy want_itup to *so so we don't need to pass it around separately */
     942          174 :     so->want_itup = false;
     943              : 
     944          174 :     so->tbm = tbm;
     945          174 :     so->ntids = 0;
     946              : 
     947          174 :     spgWalk(scan->indexRelation, so, true, storeBitmap);
     948              : 
     949          174 :     return so->ntids;
     950              : }
     951              : 
     952              : /* storeRes subroutine for gettuple case */
     953              : static void
     954       503237 : storeGettuple(SpGistScanOpaque so, ItemPointer heapPtr,
     955              :               Datum leafValue, bool isnull,
     956              :               SpGistLeafTuple leafTuple, bool recheck,
     957              :               bool recheckDistances, double *nonNullDistances)
     958              : {
     959              :     Assert(so->nPtrs < MaxIndexTuplesPerPage);
     960       503237 :     so->heapPtrs[so->nPtrs] = *heapPtr;
     961       503237 :     so->recheck[so->nPtrs] = recheck;
     962       503237 :     so->recheckDistances[so->nPtrs] = recheckDistances;
     963              : 
     964       503237 :     if (so->numberOfOrderBys > 0)
     965              :     {
     966       181677 :         if (isnull || so->numberOfNonNullOrderBys <= 0)
     967           24 :             so->distances[so->nPtrs] = NULL;
     968              :         else
     969              :         {
     970       181653 :             IndexOrderByDistance *distances = palloc_array(IndexOrderByDistance,
     971              :                                                            so->numberOfOrderBys);
     972              :             int         i;
     973              : 
     974       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              : 
     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 */
    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              :     }
    1017       503237 :     so->nPtrs++;
    1018       503237 : }
    1019              : 
    1020              : bool
    1021       503458 : spggettuple(IndexScanDesc scan, ScanDirection dir)
    1022              : {
    1023       503458 :     SpGistScanOpaque so = (SpGistScanOpaque) scan->opaque;
    1024              : 
    1025       503458 :     if (dir != ForwardScanDirection)
    1026            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 */
    1029       503458 :     so->want_itup = scan->xs_want_itup;
    1030              : 
    1031              :     for (;;)
    1032              :     {
    1033       691677 :         if (so->iPtr < so->nPtrs)
    1034              :         {
    1035              :             /* continuing to return reported tuples */
    1036       503189 :             scan->xs_heaptid = so->heapPtrs[so->iPtr];
    1037       503189 :             scan->xs_recheck = so->recheck[so->iPtr];
    1038       503189 :             scan->xs_hitup = so->reconTups[so->iPtr];
    1039              : 
    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]);
    1044       503189 :             so->iPtr++;
    1045       503189 :             return true;
    1046              :         }
    1047              : 
    1048       188488 :         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              : 
    1058       188488 :         if (so->want_itup)
    1059              :         {
    1060              :             /* Must pfree reconstructed tuples to avoid memory leak */
    1061              :             int         i;
    1062              : 
    1063       604782 :             for (i = 0; i < so->nPtrs; i++)
    1064       459650 :                 pfree(so->reconTups[i]);
    1065              :         }
    1066       188488 :         so->iPtr = so->nPtrs = 0;
    1067              : 
    1068       188488 :         spgWalk(scan->indexRelation, so, false, storeGettuple);
    1069              : 
    1070       188488 :         if (so->nPtrs == 0)
    1071          269 :             break;              /* must have completed scan */
    1072              :     }
    1073              : 
    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 */
    1083          927 :     if (attno > 1)
    1084           14 :         return true;
    1085              : 
    1086              :     /* We can do it if the opclass config function says so */
    1087          913 :     cache = spgGetCache(index);
    1088              : 
    1089          913 :     return cache->config.canReturnData;
    1090              : }
        

Generated by: LCOV version 2.0-1