Coverage Report

Created: 2026-09-28 06:55

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