Coverage Report

Created: 2026-08-14 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/access/spgist/spgutils.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * spgutils.c
4
 *    various support functions for SP-GiST
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/spgutils.c
12
 *
13
 *-------------------------------------------------------------------------
14
 */
15
16
#include "postgres.h"
17
18
#include "access/amvalidate.h"
19
#include "access/htup_details.h"
20
#include "access/reloptions.h"
21
#include "access/spgist_private.h"
22
#include "access/toast_compression.h"
23
#include "access/transam.h"
24
#include "access/xact.h"
25
#include "catalog/pg_amop.h"
26
#include "commands/vacuum.h"
27
#include "nodes/nodeFuncs.h"
28
#include "parser/parse_coerce.h"
29
#include "storage/bufmgr.h"
30
#include "storage/indexfsm.h"
31
#include "utils/catcache.h"
32
#include "utils/fmgrprotos.h"
33
#include "utils/index_selfuncs.h"
34
#include "utils/lsyscache.h"
35
#include "utils/rel.h"
36
#include "utils/syscache.h"
37
38
39
/*
40
 * SP-GiST handler function: return IndexAmRoutine with access method parameters
41
 * and callbacks.
42
 */
43
Datum
44
spghandler(PG_FUNCTION_ARGS)
45
0
{
46
0
  static const IndexAmRoutine amroutine = {
47
0
    .type = T_IndexAmRoutine,
48
0
    .amstrategies = 0,
49
0
    .amsupport = SPGISTNProc,
50
0
    .amoptsprocnum = SPGIST_OPTIONS_PROC,
51
0
    .amcanorder = false,
52
0
    .amcanorderbyop = true,
53
0
    .amcanhash = false,
54
0
    .amconsistentequality = false,
55
0
    .amconsistentordering = false,
56
0
    .amcanbackward = false,
57
0
    .amcanunique = false,
58
0
    .amcanmulticol = false,
59
0
    .amoptionalkey = true,
60
0
    .amsearcharray = false,
61
0
    .amsearchnulls = true,
62
0
    .amstorage = true,
63
0
    .amclusterable = false,
64
0
    .ampredlocks = false,
65
0
    .amcanparallel = false,
66
0
    .amcanbuildparallel = false,
67
0
    .amcaninclude = true,
68
0
    .amusemaintenanceworkmem = false,
69
0
    .amsummarizing = false,
70
0
    .amparallelvacuumoptions =
71
0
    VACUUM_OPTION_PARALLEL_BULKDEL | VACUUM_OPTION_PARALLEL_COND_CLEANUP,
72
0
    .amkeytype = InvalidOid,
73
74
0
    .ambuild = spgbuild,
75
0
    .ambuildempty = spgbuildempty,
76
0
    .aminsert = spginsert,
77
0
    .aminsertcleanup = NULL,
78
0
    .ambulkdelete = spgbulkdelete,
79
0
    .amvacuumcleanup = spgvacuumcleanup,
80
0
    .amcanreturn = spgcanreturn,
81
0
    .amcostestimate = spgcostestimate,
82
0
    .amgettreeheight = NULL,
83
0
    .amoptions = spgoptions,
84
0
    .amproperty = spgproperty,
85
0
    .ambuildphasename = NULL,
86
0
    .amvalidate = spgvalidate,
87
0
    .amadjustmembers = spgadjustmembers,
88
0
    .ambeginscan = spgbeginscan,
89
0
    .amrescan = spgrescan,
90
0
    .amgettuple = spggettuple,
91
0
    .amgetbitmap = spggetbitmap,
92
0
    .amendscan = spgendscan,
93
0
    .ammarkpos = NULL,
94
0
    .amrestrpos = NULL,
95
0
    .amestimateparallelscan = NULL,
96
0
    .aminitparallelscan = NULL,
97
0
    .amparallelrescan = NULL,
98
0
    .amtranslatestrategy = NULL,
99
0
    .amtranslatecmptype = NULL,
100
0
  };
101
102
0
  PG_RETURN_POINTER(&amroutine);
103
0
}
104
105
/*
106
 * GetIndexInputType
107
 *    Determine the nominal input data type for an index column
108
 *
109
 * We define the "nominal" input type as the associated opclass's opcintype,
110
 * or if that is a polymorphic type, the base type of the heap column or
111
 * expression that is the index's input.  The reason for preferring the
112
 * opcintype is that non-polymorphic opclasses probably don't want to hear
113
 * about binary-compatible input types.  For instance, if a text opclass
114
 * is being used with a varchar heap column, we want to report "text" not
115
 * "varchar".  Likewise, opclasses don't want to hear about domain types,
116
 * so if we do consult the actual input type, we make sure to flatten domains.
117
 *
118
 * At some point maybe this should go somewhere else, but it's not clear
119
 * if any other index AMs have a use for it.
120
 */
121
static Oid
122
GetIndexInputType(Relation index, AttrNumber indexcol)
123
0
{
124
0
  Oid     opcintype;
125
0
  AttrNumber  heapcol;
126
0
  List     *indexprs;
127
0
  ListCell   *indexpr_item;
128
129
0
  Assert(index->rd_index != NULL);
130
0
  Assert(indexcol > 0 && indexcol <= index->rd_index->indnkeyatts);
131
0
  opcintype = index->rd_opcintype[indexcol - 1];
132
0
  if (!IsPolymorphicType(opcintype))
133
0
    return opcintype;
134
0
  heapcol = index->rd_index->indkey.values[indexcol - 1];
135
0
  if (heapcol != 0)     /* Simple index column? */
136
0
    return getBaseType(get_atttype(index->rd_index->indrelid, heapcol));
137
138
  /*
139
   * If the index expressions are already cached, skip calling
140
   * RelationGetIndexExpressions, as it will make a copy which is overkill.
141
   * We're not going to modify the trees, and we're not going to do anything
142
   * that would invalidate the relcache entry before we're done.
143
   */
144
0
  if (index->rd_indexprs)
145
0
    indexprs = index->rd_indexprs;
146
0
  else
147
0
    indexprs = RelationGetIndexExpressions(index);
148
0
  indexpr_item = list_head(indexprs);
149
0
  for (int i = 1; i <= index->rd_index->indnkeyatts; i++)
150
0
  {
151
0
    if (index->rd_index->indkey.values[i - 1] == 0)
152
0
    {
153
      /* expression column */
154
0
      if (indexpr_item == NULL)
155
0
        elog(ERROR, "wrong number of index expressions");
156
0
      if (i == indexcol)
157
0
        return getBaseType(exprType((Node *) lfirst(indexpr_item)));
158
0
      indexpr_item = lnext(indexprs, indexpr_item);
159
0
    }
160
0
  }
161
0
  elog(ERROR, "wrong number of index expressions");
162
0
  return InvalidOid;     /* keep compiler quiet */
163
0
}
164
165
/* Fill in a SpGistTypeDesc struct with info about the specified data type */
166
static void
167
fillTypeDesc(SpGistTypeDesc *desc, Oid type)
168
0
{
169
0
  HeapTuple tp;
170
0
  Form_pg_type typtup;
171
172
0
  desc->type = type;
173
0
  tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type));
174
0
  if (!HeapTupleIsValid(tp))
175
0
    elog(ERROR, "cache lookup failed for type %u", type);
176
0
  typtup = (Form_pg_type) GETSTRUCT(tp);
177
0
  desc->attlen = typtup->typlen;
178
0
  desc->attbyval = typtup->typbyval;
179
0
  desc->attalign = typtup->typalign;
180
0
  desc->attstorage = typtup->typstorage;
181
0
  ReleaseSysCache(tp);
182
0
}
183
184
/*
185
 * Fetch local cache of AM-specific info about the index, initializing it
186
 * if necessary
187
 */
188
SpGistCache *
189
spgGetCache(Relation index)
190
0
{
191
0
  SpGistCache *cache;
192
193
0
  if (index->rd_amcache == NULL)
194
0
  {
195
0
    Oid     atttype;
196
0
    spgConfigIn in;
197
0
    FmgrInfo   *procinfo;
198
199
0
    cache = MemoryContextAllocZero(index->rd_indexcxt,
200
0
                     sizeof(SpGistCache));
201
202
    /* SPGiST must have one key column and can also have INCLUDE columns */
203
0
    Assert(IndexRelationGetNumberOfKeyAttributes(index) == 1);
204
0
    Assert(IndexRelationGetNumberOfAttributes(index) <= INDEX_MAX_KEYS);
205
206
    /*
207
     * Get the actual (well, nominal) data type of the key column.  We
208
     * pass this to the opclass config function so that polymorphic
209
     * opclasses are possible.
210
     */
211
0
    atttype = GetIndexInputType(index, spgKeyColumn + 1);
212
213
    /* Call the config function to get config info for the opclass */
214
0
    in.attType = atttype;
215
216
0
    procinfo = index_getprocinfo(index, 1, SPGIST_CONFIG_PROC);
217
0
    FunctionCall2Coll(procinfo,
218
0
              index->rd_indcollation[spgKeyColumn],
219
0
              PointerGetDatum(&in),
220
0
              PointerGetDatum(&cache->config));
221
222
    /*
223
     * If leafType isn't specified, use the declared index column type,
224
     * which index.c will have derived from the opclass's opcintype.
225
     * (Although we now make spgvalidate.c warn if these aren't the same,
226
     * old user-defined opclasses may not set the STORAGE parameter
227
     * correctly, so believe leafType if it's given.)
228
     */
229
0
    if (!OidIsValid(cache->config.leafType))
230
0
    {
231
0
      cache->config.leafType =
232
0
        TupleDescAttr(RelationGetDescr(index), spgKeyColumn)->atttypid;
233
234
      /*
235
       * If index column type is binary-coercible to atttype (for
236
       * example, it's a domain over atttype), treat it as plain atttype
237
       * to avoid thinking we need to compress.
238
       */
239
0
      if (cache->config.leafType != atttype &&
240
0
        IsBinaryCoercible(cache->config.leafType, atttype))
241
0
        cache->config.leafType = atttype;
242
0
    }
243
244
    /* Get the information we need about each relevant datatype */
245
0
    fillTypeDesc(&cache->attType, atttype);
246
247
0
    if (cache->config.leafType != atttype)
248
0
    {
249
0
      if (!OidIsValid(index_getprocid(index, 1, SPGIST_COMPRESS_PROC)))
250
0
        ereport(ERROR,
251
0
            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
252
0
             errmsg("compress method must be defined when leaf type is different from input type")));
253
254
0
      fillTypeDesc(&cache->attLeafType, cache->config.leafType);
255
0
    }
256
0
    else
257
0
    {
258
      /* Save lookups in this common case */
259
0
      cache->attLeafType = cache->attType;
260
0
    }
261
262
0
    fillTypeDesc(&cache->attPrefixType, cache->config.prefixType);
263
0
    fillTypeDesc(&cache->attLabelType, cache->config.labelType);
264
265
    /*
266
     * Finally, if it's a real index (not a partitioned one), get the
267
     * lastUsedPages data from the metapage
268
     */
269
0
    if (index->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
270
0
    {
271
0
      Buffer    metabuffer;
272
0
      SpGistMetaPageData *metadata;
273
274
0
      metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO);
275
0
      LockBuffer(metabuffer, BUFFER_LOCK_SHARE);
276
277
0
      metadata = SpGistPageGetMeta(BufferGetPage(metabuffer));
278
279
0
      if (metadata->magicNumber != SPGIST_MAGIC_NUMBER)
280
0
        elog(ERROR, "index \"%s\" is not an SP-GiST index",
281
0
           RelationGetRelationName(index));
282
283
0
      cache->lastUsedPages = metadata->lastUsedPages;
284
285
0
      UnlockReleaseBuffer(metabuffer);
286
0
    }
287
288
0
    index->rd_amcache = cache;
289
0
  }
290
0
  else
291
0
  {
292
    /* assume it's up to date */
293
0
    cache = (SpGistCache *) index->rd_amcache;
294
0
  }
295
296
0
  return cache;
297
0
}
298
299
/*
300
 * Compute a tuple descriptor for leaf tuples or index-only-scan result tuples.
301
 *
302
 * We can use the relcache's tupdesc as-is in many cases, and it's always
303
 * OK so far as any INCLUDE columns are concerned.  However, the entry for
304
 * the key column has to match leafType in the first case or attType in the
305
 * second case.  While the relcache's tupdesc *should* show leafType, this
306
 * might not hold for legacy user-defined opclasses, since before v14 they
307
 * were not allowed to declare their true storage type in CREATE OPCLASS.
308
 * Also, attType can be different from what is in the relcache.
309
 *
310
 * This function gives back either a pointer to the relcache's tupdesc
311
 * if that is suitable, or a palloc'd copy that's been adjusted to match
312
 * the specified key column type.  We can avoid doing any catalog lookups
313
 * here by insisting that the caller pass an SpGistTypeDesc not just an OID.
314
 */
315
TupleDesc
316
getSpGistTupleDesc(Relation index, SpGistTypeDesc *keyType)
317
0
{
318
0
  TupleDesc outTupDesc;
319
0
  Form_pg_attribute att;
320
321
0
  if (keyType->type ==
322
0
    TupleDescAttr(RelationGetDescr(index), spgKeyColumn)->atttypid)
323
0
    outTupDesc = RelationGetDescr(index);
324
0
  else
325
0
  {
326
0
    outTupDesc = CreateTupleDescCopy(RelationGetDescr(index));
327
0
    att = TupleDescAttr(outTupDesc, spgKeyColumn);
328
    /* It's sufficient to update the type-dependent fields of the column */
329
0
    att->atttypid = keyType->type;
330
0
    att->atttypmod = -1;
331
0
    att->attlen = keyType->attlen;
332
0
    att->attbyval = keyType->attbyval;
333
0
    att->attalign = keyType->attalign;
334
0
    att->attstorage = keyType->attstorage;
335
    /* We shouldn't need to bother with making these valid: */
336
0
    att->attcompression = InvalidCompressionMethod;
337
0
    att->attcollation = InvalidOid;
338
339
0
    populate_compact_attribute(outTupDesc, spgKeyColumn);
340
0
    TupleDescFinalize(outTupDesc);
341
0
  }
342
0
  return outTupDesc;
343
0
}
344
345
/* Initialize SpGistState for working with the given index */
346
void
347
initSpGistState(SpGistState *state, Relation index)
348
0
{
349
0
  SpGistCache *cache;
350
351
0
  state->index = index;
352
353
  /* Get cached static information about index */
354
0
  cache = spgGetCache(index);
355
356
0
  state->config = cache->config;
357
0
  state->attType = cache->attType;
358
0
  state->attLeafType = cache->attLeafType;
359
0
  state->attPrefixType = cache->attPrefixType;
360
0
  state->attLabelType = cache->attLabelType;
361
362
  /* Ensure we have a valid descriptor for leaf tuples */
363
0
  state->leafTupDesc = getSpGistTupleDesc(state->index, &state->attLeafType);
364
365
  /* Make workspace for constructing dead tuples */
366
0
  state->deadTupleStorage = palloc0(SGDTSIZE);
367
368
  /*
369
   * Set horizon XID to use in redirection tuples.  Use our own XID if we
370
   * have one, else use InvalidTransactionId.  The latter case can happen in
371
   * VACUUM or REINDEX CONCURRENTLY, and in neither case would it be okay to
372
   * force an XID to be assigned.  VACUUM won't create any redirection
373
   * tuples anyway, but REINDEX CONCURRENTLY can.  Fortunately, REINDEX
374
   * CONCURRENTLY doesn't mark the index valid until the end, so there could
375
   * never be any concurrent scans "in flight" to a redirection tuple it has
376
   * inserted.  And it locks out VACUUM until the end, too.  So it's okay
377
   * for VACUUM to immediately expire a redirection tuple that contains an
378
   * invalid xid.
379
   */
380
0
  state->redirectXid = GetTopTransactionIdIfAny();
381
382
  /* Assume we're not in an index build (spgbuild will override) */
383
0
  state->isBuild = false;
384
0
}
385
386
/*
387
 * Allocate a new page (either by recycling, or by extending the index file).
388
 *
389
 * The returned buffer is already pinned and exclusive-locked.
390
 * Caller is responsible for initializing the page by calling SpGistInitBuffer.
391
 */
392
Buffer
393
SpGistNewBuffer(Relation index)
394
0
{
395
0
  Buffer    buffer;
396
397
  /* First, try to get a page from FSM */
398
0
  for (;;)
399
0
  {
400
0
    BlockNumber blkno = GetFreeIndexPage(index);
401
402
0
    if (blkno == InvalidBlockNumber)
403
0
      break;       /* nothing known to FSM */
404
405
    /*
406
     * The fixed pages shouldn't ever be listed in FSM, but just in case
407
     * one is, ignore it.
408
     */
409
0
    if (SpGistBlockIsFixed(blkno))
410
0
      continue;
411
412
0
    buffer = ReadBuffer(index, blkno);
413
414
    /*
415
     * We have to guard against the possibility that someone else already
416
     * recycled this page; the buffer may be locked if so.
417
     */
418
0
    if (ConditionalLockBuffer(buffer))
419
0
    {
420
0
      Page    page = BufferGetPage(buffer);
421
422
0
      if (PageIsNew(page))
423
0
        return buffer; /* OK to use, if never initialized */
424
425
0
      if (SpGistPageIsDeleted(page) || PageIsEmpty(page))
426
0
        return buffer; /* OK to use */
427
428
0
      LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
429
0
    }
430
431
    /* Can't use it, so release buffer and try again */
432
0
    ReleaseBuffer(buffer);
433
0
  }
434
435
0
  buffer = ExtendBufferedRel(BMR_REL(index), MAIN_FORKNUM, NULL,
436
0
                 EB_LOCK_FIRST);
437
438
0
  return buffer;
439
0
}
440
441
/*
442
 * Update index metapage's lastUsedPages info from local cache, if possible
443
 *
444
 * Updating meta page isn't critical for index working, so
445
 * 1 use ConditionalLockBuffer to improve concurrency
446
 * 2 don't WAL-log metabuffer changes to decrease WAL traffic
447
 */
448
void
449
SpGistUpdateMetaPage(Relation index)
450
0
{
451
0
  SpGistCache *cache = (SpGistCache *) index->rd_amcache;
452
453
0
  if (cache != NULL)
454
0
  {
455
0
    Buffer    metabuffer;
456
457
0
    metabuffer = ReadBuffer(index, SPGIST_METAPAGE_BLKNO);
458
459
0
    if (ConditionalLockBuffer(metabuffer))
460
0
    {
461
0
      Page    metapage = BufferGetPage(metabuffer);
462
0
      SpGistMetaPageData *metadata = SpGistPageGetMeta(metapage);
463
464
0
      metadata->lastUsedPages = cache->lastUsedPages;
465
466
      /*
467
       * Set pd_lower just past the end of the metadata.  This is
468
       * essential, because without doing so, metadata will be lost if
469
       * xlog.c compresses the page.  (We must do this here because
470
       * pre-v11 versions of PG did not set the metapage's pd_lower
471
       * correctly, so a pg_upgraded index might contain the wrong
472
       * value.)
473
       */
474
0
      ((PageHeader) metapage)->pd_lower =
475
0
        ((char *) metadata + sizeof(SpGistMetaPageData)) - (char *) metapage;
476
477
0
      MarkBufferDirty(metabuffer);
478
0
      UnlockReleaseBuffer(metabuffer);
479
0
    }
480
0
    else
481
0
    {
482
0
      ReleaseBuffer(metabuffer);
483
0
    }
484
0
  }
485
0
}
486
487
/* Macro to select proper element of lastUsedPages cache depending on flags */
488
/* Masking flags with SPGIST_CACHED_PAGES is just for paranoia's sake */
489
0
#define GET_LUP(c, f)  (&(c)->lastUsedPages.cachedPage[((unsigned int) (f)) % SPGIST_CACHED_PAGES])
490
491
/*
492
 * Allocate and initialize a new buffer of the type and parity specified by
493
 * flags.  The returned buffer is already pinned and exclusive-locked.
494
 *
495
 * When requesting an inner page, if we get one with the wrong parity,
496
 * we just release the buffer and try again.  We will get a different page
497
 * because GetFreeIndexPage will have marked the page used in FSM.  The page
498
 * is entered in our local lastUsedPages cache, so there's some hope of
499
 * making use of it later in this session, but otherwise we rely on VACUUM
500
 * to eventually re-enter the page in FSM, making it available for recycling.
501
 * Note that such a page does not get marked dirty here, so unless it's used
502
 * fairly soon, the buffer will just get discarded and the page will remain
503
 * as it was on disk.
504
 *
505
 * When we return a buffer to the caller, the page is *not* entered into
506
 * the lastUsedPages cache; we expect the caller will do so after it's taken
507
 * whatever space it will use.  This is because after the caller has used up
508
 * some space, the page might have less space than whatever was cached already
509
 * so we'd rather not trash the old cache entry.
510
 */
511
static Buffer
512
allocNewBuffer(Relation index, int flags)
513
0
{
514
0
  SpGistCache *cache = spgGetCache(index);
515
0
  uint16    pageflags = 0;
516
517
0
  if (GBUF_REQ_LEAF(flags))
518
0
    pageflags |= SPGIST_LEAF;
519
0
  if (GBUF_REQ_NULLS(flags))
520
0
    pageflags |= SPGIST_NULLS;
521
522
0
  for (;;)
523
0
  {
524
0
    Buffer    buffer;
525
526
0
    buffer = SpGistNewBuffer(index);
527
0
    SpGistInitBuffer(buffer, pageflags);
528
529
0
    if (pageflags & SPGIST_LEAF)
530
0
    {
531
      /* Leaf pages have no parity concerns, so just use it */
532
0
      return buffer;
533
0
    }
534
0
    else
535
0
    {
536
0
      BlockNumber blkno = BufferGetBlockNumber(buffer);
537
0
      int     blkFlags = GBUF_INNER_PARITY(blkno);
538
539
0
      if ((flags & GBUF_PARITY_MASK) == blkFlags)
540
0
      {
541
        /* Page has right parity, use it */
542
0
        return buffer;
543
0
      }
544
0
      else
545
0
      {
546
        /* Page has wrong parity, record it in cache and try again */
547
0
        if (pageflags & SPGIST_NULLS)
548
0
          blkFlags |= GBUF_NULLS;
549
0
        cache->lastUsedPages.cachedPage[blkFlags].blkno = blkno;
550
0
        cache->lastUsedPages.cachedPage[blkFlags].freeSpace =
551
0
          PageGetExactFreeSpace(BufferGetPage(buffer));
552
0
        UnlockReleaseBuffer(buffer);
553
0
      }
554
0
    }
555
0
  }
556
0
}
557
558
/*
559
 * Get a buffer of the type and parity specified by flags, having at least
560
 * as much free space as indicated by needSpace.  We use the lastUsedPages
561
 * cache to assign the same buffer previously requested when possible.
562
 * The returned buffer is already pinned and exclusive-locked.
563
 *
564
 * *isNew is set true if the page was initialized here, false if it was
565
 * already valid.
566
 */
567
Buffer
568
SpGistGetBuffer(Relation index, int flags, int needSpace, bool *isNew)
569
0
{
570
0
  SpGistCache *cache = spgGetCache(index);
571
0
  SpGistLastUsedPage *lup;
572
573
  /* Bail out if even an empty page wouldn't meet the demand */
574
0
  if (needSpace > SPGIST_PAGE_CAPACITY)
575
0
    elog(ERROR, "desired SPGiST tuple size is too big");
576
577
  /*
578
   * If possible, increase the space request to include relation's
579
   * fillfactor.  This ensures that when we add unrelated tuples to a page,
580
   * we try to keep 100-fillfactor% available for adding tuples that are
581
   * related to the ones already on it.  But fillfactor mustn't cause an
582
   * error for requests that would otherwise be legal.
583
   */
584
0
  needSpace += SpGistGetTargetPageFreeSpace(index);
585
0
  needSpace = Min(needSpace, SPGIST_PAGE_CAPACITY);
586
587
  /* Get the cache entry for this flags setting */
588
0
  lup = GET_LUP(cache, flags);
589
590
  /* If we have nothing cached, just turn it over to allocNewBuffer */
591
0
  if (lup->blkno == InvalidBlockNumber)
592
0
  {
593
0
    *isNew = true;
594
0
    return allocNewBuffer(index, flags);
595
0
  }
596
597
  /* fixed pages should never be in cache */
598
0
  Assert(!SpGistBlockIsFixed(lup->blkno));
599
600
  /* If cached freeSpace isn't enough, don't bother looking at the page */
601
0
  if (lup->freeSpace >= needSpace)
602
0
  {
603
0
    Buffer    buffer;
604
0
    Page    page;
605
606
0
    buffer = ReadBuffer(index, lup->blkno);
607
608
0
    if (!ConditionalLockBuffer(buffer))
609
0
    {
610
      /*
611
       * buffer is locked by another process, so return a new buffer
612
       */
613
0
      ReleaseBuffer(buffer);
614
0
      *isNew = true;
615
0
      return allocNewBuffer(index, flags);
616
0
    }
617
618
0
    page = BufferGetPage(buffer);
619
620
0
    if (PageIsNew(page) || SpGistPageIsDeleted(page) || PageIsEmpty(page))
621
0
    {
622
      /* OK to initialize the page */
623
0
      uint16    pageflags = 0;
624
625
0
      if (GBUF_REQ_LEAF(flags))
626
0
        pageflags |= SPGIST_LEAF;
627
0
      if (GBUF_REQ_NULLS(flags))
628
0
        pageflags |= SPGIST_NULLS;
629
0
      SpGistInitBuffer(buffer, pageflags);
630
0
      lup->freeSpace = PageGetExactFreeSpace(page) - needSpace;
631
0
      *isNew = true;
632
0
      return buffer;
633
0
    }
634
635
    /*
636
     * Check that page is of right type and has enough space.  We must
637
     * recheck this since our cache isn't necessarily up to date.
638
     */
639
0
    if ((GBUF_REQ_LEAF(flags) ? SpGistPageIsLeaf(page) : !SpGistPageIsLeaf(page)) &&
640
0
      (GBUF_REQ_NULLS(flags) ? SpGistPageStoresNulls(page) : !SpGistPageStoresNulls(page)))
641
0
    {
642
0
      int     freeSpace = PageGetExactFreeSpace(page);
643
644
0
      if (freeSpace >= needSpace)
645
0
      {
646
        /* Success, update freespace info and return the buffer */
647
0
        lup->freeSpace = freeSpace - needSpace;
648
0
        *isNew = false;
649
0
        return buffer;
650
0
      }
651
0
    }
652
653
    /*
654
     * fallback to allocation of new buffer
655
     */
656
0
    UnlockReleaseBuffer(buffer);
657
0
  }
658
659
  /* No success with cache, so return a new buffer */
660
0
  *isNew = true;
661
0
  return allocNewBuffer(index, flags);
662
0
}
663
664
/*
665
 * Update lastUsedPages cache when done modifying a page.
666
 *
667
 * We update the appropriate cache entry if it already contained this page
668
 * (its freeSpace is likely obsolete), or if this page has more space than
669
 * whatever we had cached.
670
 */
671
void
672
SpGistSetLastUsedPage(Relation index, Buffer buffer)
673
0
{
674
0
  SpGistCache *cache = spgGetCache(index);
675
0
  SpGistLastUsedPage *lup;
676
0
  int     freeSpace;
677
0
  Page    page = BufferGetPage(buffer);
678
0
  BlockNumber blkno = BufferGetBlockNumber(buffer);
679
0
  int     flags;
680
681
  /* Never enter fixed pages (root pages) in cache, though */
682
0
  if (SpGistBlockIsFixed(blkno))
683
0
    return;
684
685
0
  if (SpGistPageIsLeaf(page))
686
0
    flags = GBUF_LEAF;
687
0
  else
688
0
    flags = GBUF_INNER_PARITY(blkno);
689
0
  if (SpGistPageStoresNulls(page))
690
0
    flags |= GBUF_NULLS;
691
692
0
  lup = GET_LUP(cache, flags);
693
694
0
  freeSpace = PageGetExactFreeSpace(page);
695
0
  if (lup->blkno == InvalidBlockNumber || lup->blkno == blkno ||
696
0
    lup->freeSpace < freeSpace)
697
0
  {
698
0
    lup->blkno = blkno;
699
0
    lup->freeSpace = freeSpace;
700
0
  }
701
0
}
702
703
/*
704
 * Initialize an SPGiST page to empty, with specified flags
705
 */
706
void
707
SpGistInitPage(Page page, uint16 f)
708
0
{
709
0
  SpGistPageOpaque opaque;
710
711
0
  PageInit(page, BLCKSZ, sizeof(SpGistPageOpaqueData));
712
0
  opaque = SpGistPageGetOpaque(page);
713
0
  opaque->flags = f;
714
0
  opaque->spgist_page_id = SPGIST_PAGE_ID;
715
0
}
716
717
/*
718
 * Initialize a buffer's page to empty, with specified flags
719
 */
720
void
721
SpGistInitBuffer(Buffer b, uint16 f)
722
0
{
723
0
  Assert(BufferGetPageSize(b) == BLCKSZ);
724
0
  SpGistInitPage(BufferGetPage(b), f);
725
0
}
726
727
/*
728
 * Initialize metadata page
729
 */
730
void
731
SpGistInitMetapage(Page page)
732
0
{
733
0
  SpGistMetaPageData *metadata;
734
0
  int     i;
735
736
0
  SpGistInitPage(page, SPGIST_META);
737
0
  metadata = SpGistPageGetMeta(page);
738
0
  memset(metadata, 0, sizeof(SpGistMetaPageData));
739
0
  metadata->magicNumber = SPGIST_MAGIC_NUMBER;
740
741
  /* initialize last-used-page cache to empty */
742
0
  for (i = 0; i < SPGIST_CACHED_PAGES; i++)
743
0
    metadata->lastUsedPages.cachedPage[i].blkno = InvalidBlockNumber;
744
745
  /*
746
   * Set pd_lower just past the end of the metadata.  This is essential,
747
   * because without doing so, metadata will be lost if xlog.c compresses
748
   * the page.
749
   */
750
0
  ((PageHeader) page)->pd_lower =
751
0
    ((char *) metadata + sizeof(SpGistMetaPageData)) - (char *) page;
752
0
}
753
754
/*
755
 * reloptions processing for SPGiST
756
 */
757
bytea *
758
spgoptions(Datum reloptions, bool validate)
759
0
{
760
0
  static const relopt_parse_elt tab[] = {
761
0
    {"fillfactor", RELOPT_TYPE_INT, offsetof(SpGistOptions, fillfactor)},
762
0
  };
763
764
0
  return (bytea *) build_reloptions(reloptions, validate,
765
0
                    RELOPT_KIND_SPGIST,
766
0
                    sizeof(SpGistOptions),
767
0
                    tab, lengthof(tab));
768
0
}
769
770
/*
771
 * Get the space needed to store a non-null datum of the indicated type
772
 * in an inner tuple (that is, as a prefix or node label).
773
 * Note the result is already rounded up to a MAXALIGN boundary.
774
 * Here we follow the convention that pass-by-val types are just stored
775
 * in their Datum representation (compare memcpyInnerDatum).
776
 */
777
unsigned int
778
SpGistGetInnerTypeSize(SpGistTypeDesc *att, Datum datum)
779
0
{
780
0
  unsigned int size;
781
782
0
  if (att->attbyval)
783
0
    size = sizeof(Datum);
784
0
  else if (att->attlen > 0)
785
0
    size = att->attlen;
786
0
  else
787
0
    size = VARSIZE_ANY(DatumGetPointer(datum));
788
789
0
  return MAXALIGN(size);
790
0
}
791
792
/*
793
 * Copy the given non-null datum to *target, in the inner-tuple case
794
 */
795
static void
796
memcpyInnerDatum(void *target, SpGistTypeDesc *att, Datum datum)
797
0
{
798
0
  unsigned int size;
799
800
0
  if (att->attbyval)
801
0
  {
802
0
    memcpy(target, &datum, sizeof(Datum));
803
0
  }
804
0
  else
805
0
  {
806
0
    size = (att->attlen > 0) ? att->attlen : VARSIZE_ANY(DatumGetPointer(datum));
807
0
    memcpy(target, DatumGetPointer(datum), size);
808
0
  }
809
0
}
810
811
/*
812
 * Compute space required for a leaf tuple holding the given data.
813
 *
814
 * This must match the size-calculation portion of spgFormLeafTuple.
815
 */
816
Size
817
SpGistGetLeafTupleSize(TupleDesc tupleDescriptor,
818
             const Datum *datums, const bool *isnulls)
819
0
{
820
0
  Size    size;
821
0
  Size    data_size;
822
0
  bool    needs_null_mask = false;
823
0
  int     natts = tupleDescriptor->natts;
824
825
  /*
826
   * Decide whether we need a nulls bitmask.
827
   *
828
   * If there is only a key attribute (natts == 1), never use a bitmask, for
829
   * compatibility with the pre-v14 layout of leaf tuples.  Otherwise, we
830
   * need one if any attribute is null.
831
   */
832
0
  if (natts > 1)
833
0
  {
834
0
    for (int i = 0; i < natts; i++)
835
0
    {
836
0
      if (isnulls[i])
837
0
      {
838
0
        needs_null_mask = true;
839
0
        break;
840
0
      }
841
0
    }
842
0
  }
843
844
  /*
845
   * Calculate size of the data part; same as for heap tuples.
846
   */
847
0
  data_size = heap_compute_data_size(tupleDescriptor, datums, isnulls);
848
849
  /*
850
   * Compute total size.
851
   */
852
0
  size = SGLTHDRSZ(needs_null_mask);
853
0
  size += data_size;
854
0
  size = MAXALIGN(size);
855
856
  /*
857
   * Ensure that we can replace the tuple with a dead tuple later. This test
858
   * is unnecessary when there are any non-null attributes, but be safe.
859
   */
860
0
  if (size < SGDTSIZE)
861
0
    size = SGDTSIZE;
862
863
0
  return size;
864
0
}
865
866
/*
867
 * Construct a leaf tuple containing the given heap TID and datum values
868
 */
869
SpGistLeafTuple
870
spgFormLeafTuple(SpGistState *state, const ItemPointerData *heapPtr,
871
         const Datum *datums, const bool *isnulls)
872
0
{
873
0
  SpGistLeafTuple tup;
874
0
  TupleDesc tupleDescriptor = state->leafTupDesc;
875
0
  Size    size;
876
0
  Size    hoff;
877
0
  Size    data_size;
878
0
  bool    needs_null_mask = false;
879
0
  int     natts = tupleDescriptor->natts;
880
0
  char     *tp;       /* ptr to tuple data */
881
0
  uint16    tupmask = 0;  /* unused heap_fill_tuple output */
882
883
  /*
884
   * Decide whether we need a nulls bitmask.
885
   *
886
   * If there is only a key attribute (natts == 1), never use a bitmask, for
887
   * compatibility with the pre-v14 layout of leaf tuples.  Otherwise, we
888
   * need one if any attribute is null.
889
   */
890
0
  if (natts > 1)
891
0
  {
892
0
    for (int i = 0; i < natts; i++)
893
0
    {
894
0
      if (isnulls[i])
895
0
      {
896
0
        needs_null_mask = true;
897
0
        break;
898
0
      }
899
0
    }
900
0
  }
901
902
  /*
903
   * Calculate size of the data part; same as for heap tuples.
904
   */
905
0
  data_size = heap_compute_data_size(tupleDescriptor, datums, isnulls);
906
907
  /*
908
   * Compute total size.
909
   */
910
0
  hoff = SGLTHDRSZ(needs_null_mask);
911
0
  size = hoff + data_size;
912
0
  size = MAXALIGN(size);
913
914
  /*
915
   * Ensure that we can replace the tuple with a dead tuple later. This test
916
   * is unnecessary when there are any non-null attributes, but be safe.
917
   */
918
0
  if (size < SGDTSIZE)
919
0
    size = SGDTSIZE;
920
921
  /* OK, form the tuple */
922
0
  tup = (SpGistLeafTuple) palloc0(size);
923
924
0
  tup->size = size;
925
0
  SGLT_SET_NEXTOFFSET(tup, InvalidOffsetNumber);
926
0
  tup->heapPtr = *heapPtr;
927
928
0
  tp = (char *) tup + hoff;
929
930
0
  if (needs_null_mask)
931
0
  {
932
0
    uint8    *bp;     /* ptr to null bitmap in tuple */
933
934
    /* Set nullmask presence bit in SpGistLeafTuple header */
935
0
    SGLT_SET_HASNULLMASK(tup, true);
936
    /* Fill the data area and null mask */
937
0
    bp = (uint8 *) ((char *) tup + sizeof(SpGistLeafTupleData));
938
0
    heap_fill_tuple(tupleDescriptor, datums, isnulls, tp, data_size,
939
0
            &tupmask, bp);
940
0
  }
941
0
  else if (natts > 1 || !isnulls[spgKeyColumn])
942
0
  {
943
    /* Fill data area only */
944
0
    heap_fill_tuple(tupleDescriptor, datums, isnulls, tp, data_size,
945
0
            &tupmask, (uint8 *) NULL);
946
0
  }
947
  /* otherwise we have no data, nor a bitmap, to fill */
948
949
0
  return tup;
950
0
}
951
952
/*
953
 * Construct a node (to go into an inner tuple) containing the given label
954
 *
955
 * Note that the node's downlink is just set invalid here.  Caller will fill
956
 * it in later.
957
 */
958
SpGistNodeTuple
959
spgFormNodeTuple(SpGistState *state, Datum label, bool isnull)
960
0
{
961
0
  SpGistNodeTuple tup;
962
0
  unsigned int size;
963
0
  unsigned short infomask = 0;
964
965
  /* compute space needed (note result is already maxaligned) */
966
0
  size = SGNTHDRSZ;
967
0
  if (!isnull)
968
0
    size += SpGistGetInnerTypeSize(&state->attLabelType, label);
969
970
  /*
971
   * Here we make sure that the size will fit in the field reserved for it
972
   * in t_info.
973
   */
974
0
  if ((size & INDEX_SIZE_MASK) != size)
975
0
    ereport(ERROR,
976
0
        (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
977
0
         errmsg("index row requires %zu bytes, maximum size is %zu",
978
0
            (Size) size, (Size) INDEX_SIZE_MASK)));
979
980
0
  tup = (SpGistNodeTuple) palloc0(size);
981
982
0
  if (isnull)
983
0
    infomask |= INDEX_NULL_MASK;
984
  /* we don't bother setting the INDEX_VAR_MASK bit */
985
0
  infomask |= size;
986
0
  tup->t_info = infomask;
987
988
  /* The TID field will be filled in later */
989
0
  ItemPointerSetInvalid(&tup->t_tid);
990
991
0
  if (!isnull)
992
0
    memcpyInnerDatum(SGNTDATAPTR(tup), &state->attLabelType, label);
993
994
0
  return tup;
995
0
}
996
997
/*
998
 * Construct an inner tuple containing the given prefix and node array
999
 */
1000
SpGistInnerTuple
1001
spgFormInnerTuple(SpGistState *state, bool hasPrefix, Datum prefix,
1002
          int nNodes, SpGistNodeTuple *nodes)
1003
0
{
1004
0
  SpGistInnerTuple tup;
1005
0
  unsigned int size;
1006
0
  unsigned int prefixSize;
1007
0
  int     i;
1008
0
  char     *ptr;
1009
1010
  /* Compute size needed */
1011
0
  if (hasPrefix)
1012
0
    prefixSize = SpGistGetInnerTypeSize(&state->attPrefixType, prefix);
1013
0
  else
1014
0
    prefixSize = 0;
1015
1016
0
  size = SGITHDRSZ + prefixSize;
1017
1018
  /* Note: we rely on node tuple sizes to be maxaligned already */
1019
0
  for (i = 0; i < nNodes; i++)
1020
0
    size += IndexTupleSize(nodes[i]);
1021
1022
  /*
1023
   * Ensure that we can replace the tuple with a dead tuple later.  This
1024
   * test is unnecessary given current tuple layouts, but let's be safe.
1025
   */
1026
0
  if (size < SGDTSIZE)
1027
0
    size = SGDTSIZE;
1028
1029
  /*
1030
   * Inner tuple should be small enough to fit on a page
1031
   */
1032
0
  if (size > SPGIST_PAGE_CAPACITY - sizeof(ItemIdData))
1033
0
    ereport(ERROR,
1034
0
        (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
1035
0
         errmsg("SP-GiST inner tuple size %zu exceeds maximum %zu",
1036
0
            (Size) size,
1037
0
            SPGIST_PAGE_CAPACITY - sizeof(ItemIdData)),
1038
0
         errhint("Values larger than a buffer page cannot be indexed.")));
1039
1040
  /*
1041
   * Check for overflow of header fields --- probably can't fail if the
1042
   * above succeeded, but let's be paranoid
1043
   */
1044
0
  if (size > SGITMAXSIZE ||
1045
0
    prefixSize > SGITMAXPREFIXSIZE ||
1046
0
    nNodes > SGITMAXNNODES)
1047
0
    elog(ERROR, "SPGiST inner tuple header field is too small");
1048
1049
  /* OK, form the tuple */
1050
0
  tup = (SpGistInnerTuple) palloc0(size);
1051
1052
0
  tup->nNodes = nNodes;
1053
0
  tup->prefixSize = prefixSize;
1054
0
  tup->size = size;
1055
1056
0
  if (hasPrefix)
1057
0
    memcpyInnerDatum(SGITDATAPTR(tup), &state->attPrefixType, prefix);
1058
1059
0
  ptr = (char *) SGITNODEPTR(tup);
1060
1061
0
  for (i = 0; i < nNodes; i++)
1062
0
  {
1063
0
    SpGistNodeTuple node = nodes[i];
1064
1065
0
    memcpy(ptr, node, IndexTupleSize(node));
1066
0
    ptr += IndexTupleSize(node);
1067
0
  }
1068
1069
0
  return tup;
1070
0
}
1071
1072
/*
1073
 * Construct a "dead" tuple to replace a tuple being deleted.
1074
 *
1075
 * The state can be SPGIST_REDIRECT, SPGIST_DEAD, or SPGIST_PLACEHOLDER.
1076
 * For a REDIRECT tuple, a pointer (blkno+offset) must be supplied, and
1077
 * the xid field is filled in automatically.
1078
 *
1079
 * This is called in critical sections, so we don't use palloc; the tuple
1080
 * is built in preallocated storage.  It should be copied before another
1081
 * call with different parameters can occur.
1082
 */
1083
SpGistDeadTuple
1084
spgFormDeadTuple(SpGistState *state, int tupstate,
1085
         BlockNumber blkno, OffsetNumber offnum)
1086
0
{
1087
0
  SpGistDeadTuple tuple = (SpGistDeadTuple) state->deadTupleStorage;
1088
1089
0
  tuple->tupstate = tupstate;
1090
0
  tuple->size = SGDTSIZE;
1091
0
  SGLT_SET_NEXTOFFSET(tuple, InvalidOffsetNumber);
1092
1093
0
  if (tupstate == SPGIST_REDIRECT)
1094
0
  {
1095
0
    ItemPointerSet(&tuple->pointer, blkno, offnum);
1096
0
    tuple->xid = state->redirectXid;
1097
0
  }
1098
0
  else
1099
0
  {
1100
0
    ItemPointerSetInvalid(&tuple->pointer);
1101
0
    tuple->xid = InvalidTransactionId;
1102
0
  }
1103
1104
0
  return tuple;
1105
0
}
1106
1107
/*
1108
 * Convert an SPGiST leaf tuple into Datum/isnull arrays.
1109
 *
1110
 * The caller must allocate sufficient storage for the output arrays.
1111
 * (INDEX_MAX_KEYS entries should be enough.)
1112
 */
1113
void
1114
spgDeformLeafTuple(SpGistLeafTuple tup, TupleDesc tupleDescriptor,
1115
           Datum *datums, bool *isnulls, bool keyColumnIsNull)
1116
0
{
1117
0
  bool    hasNullsMask = SGLT_GET_HASNULLMASK(tup);
1118
0
  char     *tp;       /* ptr to tuple data */
1119
0
  uint8    *bp;       /* ptr to null bitmap in tuple */
1120
1121
0
  if (keyColumnIsNull && tupleDescriptor->natts == 1)
1122
0
  {
1123
    /*
1124
     * Trivial case: there is only the key attribute and we're in a nulls
1125
     * tree.  The hasNullsMask bit in the tuple header should not be set
1126
     * (and thus we can't use index_deform_tuple_internal), but
1127
     * nonetheless the result is NULL.
1128
     *
1129
     * Note: currently this is dead code, because noplace calls this when
1130
     * there is only the key attribute.  But we should cover the case.
1131
     */
1132
0
    Assert(!hasNullsMask);
1133
1134
0
    datums[spgKeyColumn] = (Datum) 0;
1135
0
    isnulls[spgKeyColumn] = true;
1136
0
    return;
1137
0
  }
1138
1139
0
  tp = (char *) tup + SGLTHDRSZ(hasNullsMask);
1140
0
  bp = (uint8 *) ((char *) tup + sizeof(SpGistLeafTupleData));
1141
1142
0
  index_deform_tuple_internal(tupleDescriptor,
1143
0
                datums, isnulls,
1144
0
                tp, bp, hasNullsMask);
1145
1146
  /*
1147
   * Key column isnull value from the tuple should be consistent with
1148
   * keyColumnIsNull flag from the caller.
1149
   */
1150
0
  Assert(keyColumnIsNull == isnulls[spgKeyColumn]);
1151
0
}
1152
1153
/*
1154
 * Extract the label datums of the nodes within innerTuple
1155
 *
1156
 * Returns NULL if label datums are NULLs
1157
 */
1158
Datum *
1159
spgExtractNodeLabels(SpGistState *state, SpGistInnerTuple innerTuple)
1160
0
{
1161
0
  Datum    *nodeLabels;
1162
0
  int     i;
1163
0
  SpGistNodeTuple node;
1164
1165
  /* Either all the labels must be NULL, or none. */
1166
0
  node = SGITNODEPTR(innerTuple);
1167
0
  if (IndexTupleHasNulls(node))
1168
0
  {
1169
0
    SGITITERATE(innerTuple, i, node)
1170
0
    {
1171
0
      if (!IndexTupleHasNulls(node))
1172
0
        elog(ERROR, "some but not all node labels are null in SPGiST inner tuple");
1173
0
    }
1174
    /* They're all null, so just return NULL */
1175
0
    return NULL;
1176
0
  }
1177
0
  else
1178
0
  {
1179
0
    nodeLabels = palloc_array(Datum, innerTuple->nNodes);
1180
0
    SGITITERATE(innerTuple, i, node)
1181
0
    {
1182
0
      if (IndexTupleHasNulls(node))
1183
0
        elog(ERROR, "some but not all node labels are null in SPGiST inner tuple");
1184
0
      nodeLabels[i] = SGNTDATUM(node, state);
1185
0
    }
1186
0
    return nodeLabels;
1187
0
  }
1188
0
}
1189
1190
/*
1191
 * Add a new item to the page, replacing a PLACEHOLDER item if possible.
1192
 * Return the location it's inserted at, or InvalidOffsetNumber on failure.
1193
 *
1194
 * If startOffset isn't NULL, we start searching for placeholders at
1195
 * *startOffset, and update that to the next place to search.  This is just
1196
 * an optimization for repeated insertions.
1197
 *
1198
 * If errorOK is false, we throw error when there's not enough room,
1199
 * rather than returning InvalidOffsetNumber.
1200
 */
1201
OffsetNumber
1202
SpGistPageAddNewItem(SpGistState *state, Page page, const void *item, Size size,
1203
           OffsetNumber *startOffset, bool errorOK)
1204
0
{
1205
0
  SpGistPageOpaque opaque = SpGistPageGetOpaque(page);
1206
0
  OffsetNumber i,
1207
0
        maxoff,
1208
0
        offnum;
1209
1210
0
  if (opaque->nPlaceholder > 0 &&
1211
0
    PageGetExactFreeSpace(page) + SGDTSIZE >= MAXALIGN(size))
1212
0
  {
1213
    /* Try to replace a placeholder */
1214
0
    maxoff = PageGetMaxOffsetNumber(page);
1215
0
    offnum = InvalidOffsetNumber;
1216
1217
0
    for (;;)
1218
0
    {
1219
0
      if (startOffset && *startOffset != InvalidOffsetNumber)
1220
0
        i = *startOffset;
1221
0
      else
1222
0
        i = FirstOffsetNumber;
1223
0
      for (; i <= maxoff; i++)
1224
0
      {
1225
0
        SpGistDeadTuple it = (SpGistDeadTuple) PageGetItem(page,
1226
0
                                   PageGetItemId(page, i));
1227
1228
0
        if (it->tupstate == SPGIST_PLACEHOLDER)
1229
0
        {
1230
0
          offnum = i;
1231
0
          break;
1232
0
        }
1233
0
      }
1234
1235
      /* Done if we found a placeholder */
1236
0
      if (offnum != InvalidOffsetNumber)
1237
0
        break;
1238
1239
0
      if (startOffset && *startOffset != InvalidOffsetNumber)
1240
0
      {
1241
        /* Hint was no good, re-search from beginning */
1242
0
        *startOffset = InvalidOffsetNumber;
1243
0
        continue;
1244
0
      }
1245
1246
      /* Hmm, no placeholder found? */
1247
0
      opaque->nPlaceholder = 0;
1248
0
      break;
1249
0
    }
1250
1251
0
    if (offnum != InvalidOffsetNumber)
1252
0
    {
1253
      /* Replace the placeholder tuple */
1254
0
      PageIndexTupleDelete(page, offnum);
1255
1256
0
      offnum = PageAddItem(page, item, size, offnum, false, false);
1257
1258
      /*
1259
       * We should not have failed given the size check at the top of
1260
       * the function, but test anyway.  If we did fail, we must PANIC
1261
       * because we've already deleted the placeholder tuple, and
1262
       * there's no other way to keep the damage from getting to disk.
1263
       */
1264
0
      if (offnum != InvalidOffsetNumber)
1265
0
      {
1266
0
        Assert(opaque->nPlaceholder > 0);
1267
0
        opaque->nPlaceholder--;
1268
0
        if (startOffset)
1269
0
          *startOffset = offnum + 1;
1270
0
      }
1271
0
      else
1272
0
        elog(PANIC, "failed to add item of size %zu to SPGiST index page",
1273
0
           size);
1274
1275
0
      return offnum;
1276
0
    }
1277
0
  }
1278
1279
  /* No luck in replacing a placeholder, so just add it to the page */
1280
0
  offnum = PageAddItem(page, item, size,
1281
0
             InvalidOffsetNumber, false, false);
1282
1283
0
  if (offnum == InvalidOffsetNumber && !errorOK)
1284
0
    elog(ERROR, "failed to add item of size %zu to SPGiST index page",
1285
0
       size);
1286
1287
0
  return offnum;
1288
0
}
1289
1290
/*
1291
 *  spgproperty() -- Check boolean properties of indexes.
1292
 *
1293
 * This is optional for most AMs, but is required for SP-GiST because the core
1294
 * property code doesn't support AMPROP_DISTANCE_ORDERABLE.
1295
 */
1296
bool
1297
spgproperty(Oid index_oid, int attno,
1298
      IndexAMProperty prop, const char *propname,
1299
      bool *res, bool *isnull)
1300
0
{
1301
0
  Oid     opclass,
1302
0
        opfamily,
1303
0
        opcintype;
1304
0
  CatCList   *catlist;
1305
0
  int     i;
1306
1307
  /* Only answer column-level inquiries */
1308
0
  if (attno == 0)
1309
0
    return false;
1310
1311
0
  switch (prop)
1312
0
  {
1313
0
    case AMPROP_DISTANCE_ORDERABLE:
1314
0
      break;
1315
0
    default:
1316
0
      return false;
1317
0
  }
1318
1319
  /*
1320
   * Currently, SP-GiST distance-ordered scans require that there be a
1321
   * distance operator in the opclass with the default types. So we assume
1322
   * that if such an operator exists, then there's a reason for it.
1323
   */
1324
1325
  /* First we need to know the column's opclass. */
1326
0
  opclass = get_index_column_opclass(index_oid, attno);
1327
0
  if (!OidIsValid(opclass))
1328
0
  {
1329
0
    *isnull = true;
1330
0
    return true;
1331
0
  }
1332
1333
  /* Now look up the opclass family and input datatype. */
1334
0
  if (!get_opclass_opfamily_and_input_type(opclass, &opfamily, &opcintype))
1335
0
  {
1336
0
    *isnull = true;
1337
0
    return true;
1338
0
  }
1339
1340
  /* And now we can check whether the operator is provided. */
1341
0
  catlist = SearchSysCacheList1(AMOPSTRATEGY,
1342
0
                  ObjectIdGetDatum(opfamily));
1343
1344
0
  *res = false;
1345
1346
0
  for (i = 0; i < catlist->n_members; i++)
1347
0
  {
1348
0
    HeapTuple amoptup = &catlist->members[i]->tuple;
1349
0
    Form_pg_amop amopform = (Form_pg_amop) GETSTRUCT(amoptup);
1350
1351
0
    if (amopform->amoppurpose == AMOP_ORDER &&
1352
0
      (amopform->amoplefttype == opcintype ||
1353
0
       amopform->amoprighttype == opcintype) &&
1354
0
      opfamily_can_sort_type(amopform->amopsortfamily,
1355
0
                   get_op_rettype(amopform->amopopr)))
1356
0
    {
1357
0
      *res = true;
1358
0
      break;
1359
0
    }
1360
0
  }
1361
1362
0
  ReleaseSysCacheList(catlist);
1363
1364
0
  *isnull = false;
1365
1366
  return true;
1367
0
}