PostgreSQL Source Code git master
spgutils.c File Reference
#include "postgres.h"
#include "access/amvalidate.h"
#include "access/htup_details.h"
#include "access/reloptions.h"
#include "access/spgist_private.h"
#include "access/toast_compression.h"
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/pg_amop.h"
#include "commands/vacuum.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_coerce.h"
#include "storage/bufmgr.h"
#include "storage/indexfsm.h"
#include "utils/catcache.h"
#include "utils/fmgrprotos.h"
#include "utils/index_selfuncs.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
Include dependency graph for spgutils.c:

Go to the source code of this file.

Macros

#define GET_LUP(c, f)   (&(c)->lastUsedPages.cachedPage[((unsigned int) (f)) % SPGIST_CACHED_PAGES])
 

Functions

Datum spghandler (PG_FUNCTION_ARGS)
 
static Oid GetIndexInputType (Relation index, AttrNumber indexcol)
 
static void fillTypeDesc (SpGistTypeDesc *desc, Oid type)
 
SpGistCachespgGetCache (Relation index)
 
TupleDesc getSpGistTupleDesc (Relation index, SpGistTypeDesc *keyType)
 
void initSpGistState (SpGistState *state, Relation index)
 
Buffer SpGistNewBuffer (Relation index)
 
void SpGistUpdateMetaPage (Relation index)
 
static Buffer allocNewBuffer (Relation index, int flags)
 
Buffer SpGistGetBuffer (Relation index, int flags, int needSpace, bool *isNew)
 
void SpGistSetLastUsedPage (Relation index, Buffer buffer)
 
void SpGistInitPage (Page page, uint16 f)
 
void SpGistInitBuffer (Buffer b, uint16 f)
 
void SpGistInitMetapage (Page page)
 
byteaspgoptions (Datum reloptions, bool validate)
 
unsigned int SpGistGetInnerTypeSize (SpGistTypeDesc *att, Datum datum)
 
static void memcpyInnerDatum (void *target, SpGistTypeDesc *att, Datum datum)
 
Size SpGistGetLeafTupleSize (TupleDesc tupleDescriptor, const Datum *datums, const bool *isnulls)
 
SpGistLeafTuple spgFormLeafTuple (SpGistState *state, ItemPointer heapPtr, const Datum *datums, const bool *isnulls)
 
SpGistNodeTuple spgFormNodeTuple (SpGistState *state, Datum label, bool isnull)
 
SpGistInnerTuple spgFormInnerTuple (SpGistState *state, bool hasPrefix, Datum prefix, int nNodes, SpGistNodeTuple *nodes)
 
SpGistDeadTuple spgFormDeadTuple (SpGistState *state, int tupstate, BlockNumber blkno, OffsetNumber offnum)
 
void spgDeformLeafTuple (SpGistLeafTuple tup, TupleDesc tupleDescriptor, Datum *datums, bool *isnulls, bool keyColumnIsNull)
 
DatumspgExtractNodeLabels (SpGistState *state, SpGistInnerTuple innerTuple)
 
OffsetNumber SpGistPageAddNewItem (SpGistState *state, Page page, Item item, Size size, OffsetNumber *startOffset, bool errorOK)
 
bool spgproperty (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, bool *res, bool *isnull)
 

Macro Definition Documentation

◆ GET_LUP

#define GET_LUP (   c,
 
)    (&(c)->lastUsedPages.cachedPage[((unsigned int) (f)) % SPGIST_CACHED_PAGES])

Definition at line 485 of file spgutils.c.

Function Documentation

◆ allocNewBuffer()

static Buffer allocNewBuffer ( Relation  index,
int  flags 
)
static

Definition at line 508 of file spgutils.c.

509{
510 SpGistCache *cache = spgGetCache(index);
511 uint16 pageflags = 0;
512
513 if (GBUF_REQ_LEAF(flags))
514 pageflags |= SPGIST_LEAF;
515 if (GBUF_REQ_NULLS(flags))
516 pageflags |= SPGIST_NULLS;
517
518 for (;;)
519 {
520 Buffer buffer;
521
522 buffer = SpGistNewBuffer(index);
523 SpGistInitBuffer(buffer, pageflags);
524
525 if (pageflags & SPGIST_LEAF)
526 {
527 /* Leaf pages have no parity concerns, so just use it */
528 return buffer;
529 }
530 else
531 {
532 BlockNumber blkno = BufferGetBlockNumber(buffer);
533 int blkFlags = GBUF_INNER_PARITY(blkno);
534
535 if ((flags & GBUF_PARITY_MASK) == blkFlags)
536 {
537 /* Page has right parity, use it */
538 return buffer;
539 }
540 else
541 {
542 /* Page has wrong parity, record it in cache and try again */
543 if (pageflags & SPGIST_NULLS)
544 blkFlags |= GBUF_NULLS;
545 cache->lastUsedPages.cachedPage[blkFlags].blkno = blkno;
546 cache->lastUsedPages.cachedPage[blkFlags].freeSpace =
548 UnlockReleaseBuffer(buffer);
549 }
550 }
551 }
552}
uint32 BlockNumber
Definition: block.h:31
int Buffer
Definition: buf.h:23
BlockNumber BufferGetBlockNumber(Buffer buffer)
Definition: bufmgr.c:3724
void UnlockReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:4941
static Page BufferGetPage(Buffer buffer)
Definition: bufmgr.h:400
Size PageGetExactFreeSpace(Page page)
Definition: bufpage.c:947
uint16_t uint16
Definition: c.h:487
#define GBUF_NULLS
#define GBUF_PARITY_MASK
#define SPGIST_NULLS
#define GBUF_REQ_NULLS(flags)
#define GBUF_INNER_PARITY(x)
#define GBUF_REQ_LEAF(flags)
#define SPGIST_LEAF
Buffer SpGistNewBuffer(Relation index)
Definition: spgutils.c:389
SpGistCache * spgGetCache(Relation index)
Definition: spgutils.c:183
void SpGistInitBuffer(Buffer b, uint16 f)
Definition: spgutils.c:717
SpGistLUPCache lastUsedPages
SpGistLastUsedPage cachedPage[SPGIST_CACHED_PAGES]
Definition: type.h:96

References SpGistLastUsedPage::blkno, BufferGetBlockNumber(), BufferGetPage(), SpGistLUPCache::cachedPage, SpGistLastUsedPage::freeSpace, GBUF_INNER_PARITY, GBUF_NULLS, GBUF_PARITY_MASK, GBUF_REQ_LEAF, GBUF_REQ_NULLS, SpGistCache::lastUsedPages, PageGetExactFreeSpace(), spgGetCache(), SPGIST_LEAF, SPGIST_NULLS, SpGistInitBuffer(), SpGistNewBuffer(), and UnlockReleaseBuffer().

Referenced by SpGistGetBuffer().

◆ fillTypeDesc()

static void fillTypeDesc ( SpGistTypeDesc desc,
Oid  type 
)
static

Definition at line 161 of file spgutils.c.

162{
163 HeapTuple tp;
164 Form_pg_type typtup;
165
166 desc->type = type;
167 tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type));
168 if (!HeapTupleIsValid(tp))
169 elog(ERROR, "cache lookup failed for type %u", type);
170 typtup = (Form_pg_type) GETSTRUCT(tp);
171 desc->attlen = typtup->typlen;
172 desc->attbyval = typtup->typbyval;
173 desc->attalign = typtup->typalign;
174 desc->attstorage = typtup->typstorage;
175 ReleaseSysCache(tp);
176}
#define ERROR
Definition: elog.h:39
#define elog(elevel,...)
Definition: elog.h:225
#define HeapTupleIsValid(tuple)
Definition: htup.h:78
#define GETSTRUCT(TUP)
Definition: htup_details.h:653
FormData_pg_type * Form_pg_type
Definition: pg_type.h:261
static Datum ObjectIdGetDatum(Oid X)
Definition: postgres.h:257
void ReleaseSysCache(HeapTuple tuple)
Definition: syscache.c:269
HeapTuple SearchSysCache1(int cacheId, Datum key1)
Definition: syscache.c:221
const char * type

References SpGistTypeDesc::attalign, SpGistTypeDesc::attbyval, SpGistTypeDesc::attlen, SpGistTypeDesc::attstorage, elog, ERROR, GETSTRUCT, HeapTupleIsValid, ObjectIdGetDatum(), ReleaseSysCache(), SearchSysCache1(), type, and SpGistTypeDesc::type.

Referenced by spgGetCache().

◆ GetIndexInputType()

static Oid GetIndexInputType ( Relation  index,
AttrNumber  indexcol 
)
static

Definition at line 116 of file spgutils.c.

117{
118 Oid opcintype;
119 AttrNumber heapcol;
120 List *indexprs;
121 ListCell *indexpr_item;
122
123 Assert(index->rd_index != NULL);
124 Assert(indexcol > 0 && indexcol <= index->rd_index->indnkeyatts);
125 opcintype = index->rd_opcintype[indexcol - 1];
126 if (!IsPolymorphicType(opcintype))
127 return opcintype;
128 heapcol = index->rd_index->indkey.values[indexcol - 1];
129 if (heapcol != 0) /* Simple index column? */
130 return getBaseType(get_atttype(index->rd_index->indrelid, heapcol));
131
132 /*
133 * If the index expressions are already cached, skip calling
134 * RelationGetIndexExpressions, as it will make a copy which is overkill.
135 * We're not going to modify the trees, and we're not going to do anything
136 * that would invalidate the relcache entry before we're done.
137 */
138 if (index->rd_indexprs)
139 indexprs = index->rd_indexprs;
140 else
142 indexpr_item = list_head(indexprs);
143 for (int i = 1; i <= index->rd_index->indnkeyatts; i++)
144 {
145 if (index->rd_index->indkey.values[i - 1] == 0)
146 {
147 /* expression column */
148 if (indexpr_item == NULL)
149 elog(ERROR, "wrong number of index expressions");
150 if (i == indexcol)
151 return getBaseType(exprType((Node *) lfirst(indexpr_item)));
152 indexpr_item = lnext(indexprs, indexpr_item);
153 }
154 }
155 elog(ERROR, "wrong number of index expressions");
156 return InvalidOid; /* keep compiler quiet */
157}
int16 AttrNumber
Definition: attnum.h:21
#define Assert(condition)
Definition: c.h:815
int i
Definition: isn.c:72
Oid getBaseType(Oid typid)
Definition: lsyscache.c:2521
Oid get_atttype(Oid relid, AttrNumber attnum)
Definition: lsyscache.c:913
Oid exprType(const Node *expr)
Definition: nodeFuncs.c:42
#define lfirst(lc)
Definition: pg_list.h:172
static ListCell * list_head(const List *l)
Definition: pg_list.h:128
static ListCell * lnext(const List *l, const ListCell *c)
Definition: pg_list.h:343
#define InvalidOid
Definition: postgres_ext.h:37
unsigned int Oid
Definition: postgres_ext.h:32
List * RelationGetIndexExpressions(Relation relation)
Definition: relcache.c:5017
Definition: pg_list.h:54
Definition: nodes.h:129

References Assert, elog, ERROR, exprType(), get_atttype(), getBaseType(), i, InvalidOid, lfirst, list_head(), lnext(), and RelationGetIndexExpressions().

Referenced by spgGetCache().

◆ getSpGistTupleDesc()

TupleDesc getSpGistTupleDesc ( Relation  index,
SpGistTypeDesc keyType 
)

Definition at line 310 of file spgutils.c.

311{
312 TupleDesc outTupDesc;
314
315 if (keyType->type ==
317 outTupDesc = RelationGetDescr(index);
318 else
319 {
321 att = TupleDescAttr(outTupDesc, spgKeyColumn);
322 /* It's sufficient to update the type-dependent fields of the column */
323 att->atttypid = keyType->type;
324 att->atttypmod = -1;
325 att->attlen = keyType->attlen;
326 att->attbyval = keyType->attbyval;
327 att->attalign = keyType->attalign;
328 att->attstorage = keyType->attstorage;
329 /* We shouldn't need to bother with making these valid: */
330 att->attcompression = InvalidCompressionMethod;
331 att->attcollation = InvalidOid;
332 /* In case we changed typlen, we'd better reset following offsets */
333 for (int i = spgFirstIncludeColumn; i < outTupDesc->natts; i++)
334 TupleDescCompactAttr(outTupDesc, i)->attcacheoff = -1;
335
337 }
338 return outTupDesc;
339}
FormData_pg_attribute * Form_pg_attribute
Definition: pg_attribute.h:200
#define RelationGetDescr(relation)
Definition: rel.h:531
#define spgFirstIncludeColumn
#define spgKeyColumn
int32 attcacheoff
Definition: tupdesc.h:69
#define InvalidCompressionMethod
TupleDesc CreateTupleDescCopy(TupleDesc tupdesc)
Definition: tupdesc.c:234
void populate_compact_attribute(TupleDesc tupdesc, int attnum)
Definition: tupdesc.c:107
static FormData_pg_attribute * TupleDescAttr(TupleDesc tupdesc, int i)
Definition: tupdesc.h:153
static CompactAttribute * TupleDescCompactAttr(TupleDesc tupdesc, int i)
Definition: tupdesc.h:168

References SpGistTypeDesc::attalign, SpGistTypeDesc::attbyval, CompactAttribute::attcacheoff, SpGistTypeDesc::attlen, SpGistTypeDesc::attstorage, CreateTupleDescCopy(), i, InvalidCompressionMethod, InvalidOid, TupleDescData::natts, populate_compact_attribute(), RelationGetDescr, spgFirstIncludeColumn, spgKeyColumn, TupleDescAttr(), TupleDescCompactAttr(), and SpGistTypeDesc::type.

Referenced by initSpGistState(), and spgbeginscan().

◆ initSpGistState()

void initSpGistState ( SpGistState state,
Relation  index 
)

Definition at line 343 of file spgutils.c.

344{
345 SpGistCache *cache;
346
347 state->index = index;
348
349 /* Get cached static information about index */
350 cache = spgGetCache(index);
351
352 state->config = cache->config;
353 state->attType = cache->attType;
354 state->attLeafType = cache->attLeafType;
355 state->attPrefixType = cache->attPrefixType;
356 state->attLabelType = cache->attLabelType;
357
358 /* Ensure we have a valid descriptor for leaf tuples */
359 state->leafTupDesc = getSpGistTupleDesc(state->index, &state->attLeafType);
360
361 /* Make workspace for constructing dead tuples */
362 state->deadTupleStorage = palloc0(SGDTSIZE);
363
364 /*
365 * Set horizon XID to use in redirection tuples. Use our own XID if we
366 * have one, else use InvalidTransactionId. The latter case can happen in
367 * VACUUM or REINDEX CONCURRENTLY, and in neither case would it be okay to
368 * force an XID to be assigned. VACUUM won't create any redirection
369 * tuples anyway, but REINDEX CONCURRENTLY can. Fortunately, REINDEX
370 * CONCURRENTLY doesn't mark the index valid until the end, so there could
371 * never be any concurrent scans "in flight" to a redirection tuple it has
372 * inserted. And it locks out VACUUM until the end, too. So it's okay
373 * for VACUUM to immediately expire a redirection tuple that contains an
374 * invalid xid.
375 */
376 state->redirectXid = GetTopTransactionIdIfAny();
377
378 /* Assume we're not in an index build (spgbuild will override) */
379 state->isBuild = false;
380}
void * palloc0(Size size)
Definition: mcxt.c:1347
#define SGDTSIZE
TupleDesc getSpGistTupleDesc(Relation index, SpGistTypeDesc *keyType)
Definition: spgutils.c:310
SpGistTypeDesc attPrefixType
SpGistTypeDesc attLeafType
SpGistTypeDesc attType
spgConfigOut config
SpGistTypeDesc attLabelType
Definition: regguts.h:323
TransactionId GetTopTransactionIdIfAny(void)
Definition: xact.c:440

References SpGistCache::attLabelType, SpGistCache::attLeafType, SpGistCache::attPrefixType, SpGistCache::attType, SpGistCache::config, getSpGistTupleDesc(), GetTopTransactionIdIfAny(), palloc0(), SGDTSIZE, and spgGetCache().

Referenced by spgbeginscan(), spgbuild(), spginsert(), and spgvacuumscan().

◆ memcpyInnerDatum()

static void memcpyInnerDatum ( void *  target,
SpGistTypeDesc att,
Datum  datum 
)
static

Definition at line 792 of file spgutils.c.

793{
794 unsigned int size;
795
796 if (att->attbyval)
797 {
798 memcpy(target, &datum, sizeof(Datum));
799 }
800 else
801 {
802 size = (att->attlen > 0) ? att->attlen : VARSIZE_ANY(datum);
803 memcpy(target, DatumGetPointer(datum), size);
804 }
805}
uintptr_t Datum
Definition: postgres.h:69
static Pointer DatumGetPointer(Datum X)
Definition: postgres.h:317
static pg_noinline void Size size
Definition: slab.c:607
#define VARSIZE_ANY(PTR)
Definition: varatt.h:311

References SpGistTypeDesc::attbyval, SpGistTypeDesc::attlen, DatumGetPointer(), size, and VARSIZE_ANY.

Referenced by spgFormInnerTuple(), and spgFormNodeTuple().

◆ spgDeformLeafTuple()

void spgDeformLeafTuple ( SpGistLeafTuple  tup,
TupleDesc  tupleDescriptor,
Datum datums,
bool *  isnulls,
bool  keyColumnIsNull 
)

Definition at line 1110 of file spgutils.c.

1112{
1113 bool hasNullsMask = SGLT_GET_HASNULLMASK(tup);
1114 char *tp; /* ptr to tuple data */
1115 bits8 *bp; /* ptr to null bitmap in tuple */
1116
1117 if (keyColumnIsNull && tupleDescriptor->natts == 1)
1118 {
1119 /*
1120 * Trivial case: there is only the key attribute and we're in a nulls
1121 * tree. The hasNullsMask bit in the tuple header should not be set
1122 * (and thus we can't use index_deform_tuple_internal), but
1123 * nonetheless the result is NULL.
1124 *
1125 * Note: currently this is dead code, because noplace calls this when
1126 * there is only the key attribute. But we should cover the case.
1127 */
1128 Assert(!hasNullsMask);
1129
1130 datums[spgKeyColumn] = (Datum) 0;
1131 isnulls[spgKeyColumn] = true;
1132 return;
1133 }
1134
1135 tp = (char *) tup + SGLTHDRSZ(hasNullsMask);
1136 bp = (bits8 *) ((char *) tup + sizeof(SpGistLeafTupleData));
1137
1138 index_deform_tuple_internal(tupleDescriptor,
1139 datums, isnulls,
1140 tp, bp, hasNullsMask);
1141
1142 /*
1143 * Key column isnull value from the tuple should be consistent with
1144 * keyColumnIsNull flag from the caller.
1145 */
1146 Assert(keyColumnIsNull == isnulls[spgKeyColumn]);
1147}
uint8 bits8
Definition: c.h:495
void index_deform_tuple_internal(TupleDesc tupleDescriptor, Datum *values, bool *isnull, char *tp, bits8 *bp, int hasnulls)
Definition: indextuple.c:479
#define SGLT_GET_HASNULLMASK(spgLeafTuple)
#define SGLTHDRSZ(hasnulls)
struct SpGistLeafTupleData SpGistLeafTupleData

References Assert, index_deform_tuple_internal(), TupleDescData::natts, SGLT_GET_HASNULLMASK, SGLTHDRSZ, and spgKeyColumn.

Referenced by doPickSplit(), and storeGettuple().

◆ spgExtractNodeLabels()

Datum * spgExtractNodeLabels ( SpGistState state,
SpGistInnerTuple  innerTuple 
)

Definition at line 1155 of file spgutils.c.

1156{
1157 Datum *nodeLabels;
1158 int i;
1159 SpGistNodeTuple node;
1160
1161 /* Either all the labels must be NULL, or none. */
1162 node = SGITNODEPTR(innerTuple);
1163 if (IndexTupleHasNulls(node))
1164 {
1165 SGITITERATE(innerTuple, i, node)
1166 {
1167 if (!IndexTupleHasNulls(node))
1168 elog(ERROR, "some but not all node labels are null in SPGiST inner tuple");
1169 }
1170 /* They're all null, so just return NULL */
1171 return NULL;
1172 }
1173 else
1174 {
1175 nodeLabels = (Datum *) palloc(sizeof(Datum) * innerTuple->nNodes);
1176 SGITITERATE(innerTuple, i, node)
1177 {
1178 if (IndexTupleHasNulls(node))
1179 elog(ERROR, "some but not all node labels are null in SPGiST inner tuple");
1180 nodeLabels[i] = SGNTDATUM(node, state);
1181 }
1182 return nodeLabels;
1183 }
1184}
#define IndexTupleHasNulls(itup)
Definition: itup.h:71
void * palloc(Size size)
Definition: mcxt.c:1317
#define SGITITERATE(x, i, nt)
#define SGNTDATUM(x, s)
#define SGITNODEPTR(x)

References elog, ERROR, i, IndexTupleHasNulls, SpGistInnerTupleData::nNodes, palloc(), SGITITERATE, SGITNODEPTR, and SGNTDATUM.

Referenced by spgdoinsert(), and spgInitInnerConsistentIn().

◆ spgFormDeadTuple()

SpGistDeadTuple spgFormDeadTuple ( SpGistState state,
int  tupstate,
BlockNumber  blkno,
OffsetNumber  offnum 
)

Definition at line 1080 of file spgutils.c.

1082{
1083 SpGistDeadTuple tuple = (SpGistDeadTuple) state->deadTupleStorage;
1084
1085 tuple->tupstate = tupstate;
1086 tuple->size = SGDTSIZE;
1088
1089 if (tupstate == SPGIST_REDIRECT)
1090 {
1091 ItemPointerSet(&tuple->pointer, blkno, offnum);
1092 tuple->xid = state->redirectXid;
1093 }
1094 else
1095 {
1097 tuple->xid = InvalidTransactionId;
1098 }
1099
1100 return tuple;
1101}
static void ItemPointerSet(ItemPointerData *pointer, BlockNumber blockNumber, OffsetNumber offNum)
Definition: itemptr.h:135
static void ItemPointerSetInvalid(ItemPointerData *pointer)
Definition: itemptr.h:184
#define InvalidOffsetNumber
Definition: off.h:26
SpGistDeadTupleData * SpGistDeadTuple
#define SPGIST_REDIRECT
#define SGLT_SET_NEXTOFFSET(spgLeafTuple, offsetNumber)
unsigned int tupstate
ItemPointerData pointer
#define InvalidTransactionId
Definition: transam.h:31

References InvalidOffsetNumber, InvalidTransactionId, ItemPointerSet(), ItemPointerSetInvalid(), SpGistDeadTupleData::pointer, SGDTSIZE, SGLT_SET_NEXTOFFSET, SpGistDeadTupleData::size, SPGIST_REDIRECT, SpGistDeadTupleData::tupstate, and SpGistDeadTupleData::xid.

Referenced by spgAddNodeAction(), spgPageIndexMultiDelete(), and spgRedoAddNode().

◆ spgFormInnerTuple()

SpGistInnerTuple spgFormInnerTuple ( SpGistState state,
bool  hasPrefix,
Datum  prefix,
int  nNodes,
SpGistNodeTuple nodes 
)

Definition at line 997 of file spgutils.c.

999{
1000 SpGistInnerTuple tup;
1001 unsigned int size;
1002 unsigned int prefixSize;
1003 int i;
1004 char *ptr;
1005
1006 /* Compute size needed */
1007 if (hasPrefix)
1008 prefixSize = SpGistGetInnerTypeSize(&state->attPrefixType, prefix);
1009 else
1010 prefixSize = 0;
1011
1012 size = SGITHDRSZ + prefixSize;
1013
1014 /* Note: we rely on node tuple sizes to be maxaligned already */
1015 for (i = 0; i < nNodes; i++)
1016 size += IndexTupleSize(nodes[i]);
1017
1018 /*
1019 * Ensure that we can replace the tuple with a dead tuple later. This
1020 * test is unnecessary given current tuple layouts, but let's be safe.
1021 */
1022 if (size < SGDTSIZE)
1023 size = SGDTSIZE;
1024
1025 /*
1026 * Inner tuple should be small enough to fit on a page
1027 */
1028 if (size > SPGIST_PAGE_CAPACITY - sizeof(ItemIdData))
1029 ereport(ERROR,
1030 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
1031 errmsg("SP-GiST inner tuple size %zu exceeds maximum %zu",
1032 (Size) size,
1034 errhint("Values larger than a buffer page cannot be indexed.")));
1035
1036 /*
1037 * Check for overflow of header fields --- probably can't fail if the
1038 * above succeeded, but let's be paranoid
1039 */
1040 if (size > SGITMAXSIZE ||
1041 prefixSize > SGITMAXPREFIXSIZE ||
1042 nNodes > SGITMAXNNODES)
1043 elog(ERROR, "SPGiST inner tuple header field is too small");
1044
1045 /* OK, form the tuple */
1046 tup = (SpGistInnerTuple) palloc0(size);
1047
1048 tup->nNodes = nNodes;
1049 tup->prefixSize = prefixSize;
1050 tup->size = size;
1051
1052 if (hasPrefix)
1053 memcpyInnerDatum(SGITDATAPTR(tup), &state->attPrefixType, prefix);
1054
1055 ptr = (char *) SGITNODEPTR(tup);
1056
1057 for (i = 0; i < nNodes; i++)
1058 {
1059 SpGistNodeTuple node = nodes[i];
1060
1061 memcpy(ptr, node, IndexTupleSize(node));
1062 ptr += IndexTupleSize(node);
1063 }
1064
1065 return tup;
1066}
size_t Size
Definition: c.h:562
int errhint(const char *fmt,...)
Definition: elog.c:1317
int errcode(int sqlerrcode)
Definition: elog.c:853
int errmsg(const char *fmt,...)
Definition: elog.c:1070
#define ereport(elevel,...)
Definition: elog.h:149
#define IndexTupleSize(itup)
Definition: itup.h:70
SpGistInnerTupleData * SpGistInnerTuple
#define SGITDATAPTR(x)
#define SGITMAXSIZE
#define SGITMAXPREFIXSIZE
#define SGITHDRSZ
#define SPGIST_PAGE_CAPACITY
#define SGITMAXNNODES
static void memcpyInnerDatum(void *target, SpGistTypeDesc *att, Datum datum)
Definition: spgutils.c:792
unsigned int SpGistGetInnerTypeSize(SpGistTypeDesc *att, Datum datum)
Definition: spgutils.c:774
unsigned int prefixSize

References elog, ereport, errcode(), errhint(), errmsg(), ERROR, i, IndexTupleSize, memcpyInnerDatum(), SpGistInnerTupleData::nNodes, palloc0(), SpGistInnerTupleData::prefixSize, SGDTSIZE, SGITDATAPTR, SGITHDRSZ, SGITMAXNNODES, SGITMAXPREFIXSIZE, SGITMAXSIZE, SGITNODEPTR, size, SpGistInnerTupleData::size, SPGIST_PAGE_CAPACITY, and SpGistGetInnerTypeSize().

Referenced by addNode(), doPickSplit(), and spgSplitNodeAction().

◆ spgFormLeafTuple()

SpGistLeafTuple spgFormLeafTuple ( SpGistState state,
ItemPointer  heapPtr,
const Datum datums,
const bool *  isnulls 
)

Definition at line 866 of file spgutils.c.

868{
869 SpGistLeafTuple tup;
870 TupleDesc tupleDescriptor = state->leafTupDesc;
871 Size size;
872 Size hoff;
873 Size data_size;
874 bool needs_null_mask = false;
875 int natts = tupleDescriptor->natts;
876 char *tp; /* ptr to tuple data */
877 uint16 tupmask = 0; /* unused heap_fill_tuple output */
878
879 /*
880 * Decide whether we need a nulls bitmask.
881 *
882 * If there is only a key attribute (natts == 1), never use a bitmask, for
883 * compatibility with the pre-v14 layout of leaf tuples. Otherwise, we
884 * need one if any attribute is null.
885 */
886 if (natts > 1)
887 {
888 for (int i = 0; i < natts; i++)
889 {
890 if (isnulls[i])
891 {
892 needs_null_mask = true;
893 break;
894 }
895 }
896 }
897
898 /*
899 * Calculate size of the data part; same as for heap tuples.
900 */
901 data_size = heap_compute_data_size(tupleDescriptor, datums, isnulls);
902
903 /*
904 * Compute total size.
905 */
906 hoff = SGLTHDRSZ(needs_null_mask);
907 size = hoff + data_size;
908 size = MAXALIGN(size);
909
910 /*
911 * Ensure that we can replace the tuple with a dead tuple later. This test
912 * is unnecessary when there are any non-null attributes, but be safe.
913 */
914 if (size < SGDTSIZE)
915 size = SGDTSIZE;
916
917 /* OK, form the tuple */
919
920 tup->size = size;
922 tup->heapPtr = *heapPtr;
923
924 tp = (char *) tup + hoff;
925
926 if (needs_null_mask)
927 {
928 bits8 *bp; /* ptr to null bitmap in tuple */
929
930 /* Set nullmask presence bit in SpGistLeafTuple header */
931 SGLT_SET_HASNULLMASK(tup, true);
932 /* Fill the data area and null mask */
933 bp = (bits8 *) ((char *) tup + sizeof(SpGistLeafTupleData));
934 heap_fill_tuple(tupleDescriptor, datums, isnulls, tp, data_size,
935 &tupmask, bp);
936 }
937 else if (natts > 1 || !isnulls[spgKeyColumn])
938 {
939 /* Fill data area only */
940 heap_fill_tuple(tupleDescriptor, datums, isnulls, tp, data_size,
941 &tupmask, (bits8 *) NULL);
942 }
943 /* otherwise we have no data, nor a bitmap, to fill */
944
945 return tup;
946}
#define MAXALIGN(LEN)
Definition: c.h:768
Size heap_compute_data_size(TupleDesc tupleDesc, const Datum *values, const bool *isnull)
Definition: heaptuple.c:219
void heap_fill_tuple(TupleDesc tupleDesc, const Datum *values, const bool *isnull, char *data, Size data_size, uint16 *infomask, bits8 *bit)
Definition: heaptuple.c:401
#define SGLT_SET_HASNULLMASK(spgLeafTuple, hasnulls)
struct SpGistLeafTupleData * SpGistLeafTuple
ItemPointerData heapPtr

References heap_compute_data_size(), heap_fill_tuple(), SpGistLeafTupleData::heapPtr, i, InvalidOffsetNumber, MAXALIGN, TupleDescData::natts, palloc0(), SGDTSIZE, SGLT_SET_HASNULLMASK, SGLT_SET_NEXTOFFSET, SGLTHDRSZ, size, SpGistLeafTupleData::size, and spgKeyColumn.

Referenced by doPickSplit(), and spgdoinsert().

◆ spgFormNodeTuple()

SpGistNodeTuple spgFormNodeTuple ( SpGistState state,
Datum  label,
bool  isnull 
)

Definition at line 955 of file spgutils.c.

956{
957 SpGistNodeTuple tup;
958 unsigned int size;
959 unsigned short infomask = 0;
960
961 /* compute space needed (note result is already maxaligned) */
962 size = SGNTHDRSZ;
963 if (!isnull)
964 size += SpGistGetInnerTypeSize(&state->attLabelType, label);
965
966 /*
967 * Here we make sure that the size will fit in the field reserved for it
968 * in t_info.
969 */
970 if ((size & INDEX_SIZE_MASK) != size)
972 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
973 errmsg("index row requires %zu bytes, maximum size is %zu",
975
977
978 if (isnull)
979 infomask |= INDEX_NULL_MASK;
980 /* we don't bother setting the INDEX_VAR_MASK bit */
981 infomask |= size;
982 tup->t_info = infomask;
983
984 /* The TID field will be filled in later */
986
987 if (!isnull)
988 memcpyInnerDatum(SGNTDATAPTR(tup), &state->attLabelType, label);
989
990 return tup;
991}
#define INDEX_NULL_MASK
Definition: itup.h:68
#define INDEX_SIZE_MASK
Definition: itup.h:65
static char * label
#define SGNTDATAPTR(x)
SpGistNodeTupleData * SpGistNodeTuple
#define SGNTHDRSZ
ItemPointerData t_tid
Definition: itup.h:37
unsigned short t_info
Definition: itup.h:49

References ereport, errcode(), errmsg(), ERROR, INDEX_NULL_MASK, INDEX_SIZE_MASK, ItemPointerSetInvalid(), label, memcpyInnerDatum(), palloc0(), SGNTDATAPTR, SGNTHDRSZ, size, SpGistGetInnerTypeSize(), IndexTupleData::t_info, and IndexTupleData::t_tid.

Referenced by addNode(), doPickSplit(), and spgSplitNodeAction().

◆ spgGetCache()

SpGistCache * spgGetCache ( Relation  index)

Definition at line 183 of file spgutils.c.

184{
185 SpGistCache *cache;
186
187 if (index->rd_amcache == NULL)
188 {
189 Oid atttype;
190 spgConfigIn in;
191 FmgrInfo *procinfo;
192
193 cache = MemoryContextAllocZero(index->rd_indexcxt,
194 sizeof(SpGistCache));
195
196 /* SPGiST must have one key column and can also have INCLUDE columns */
199
200 /*
201 * Get the actual (well, nominal) data type of the key column. We
202 * pass this to the opclass config function so that polymorphic
203 * opclasses are possible.
204 */
205 atttype = GetIndexInputType(index, spgKeyColumn + 1);
206
207 /* Call the config function to get config info for the opclass */
208 in.attType = atttype;
209
211 FunctionCall2Coll(procinfo,
212 index->rd_indcollation[spgKeyColumn],
213 PointerGetDatum(&in),
214 PointerGetDatum(&cache->config));
215
216 /*
217 * If leafType isn't specified, use the declared index column type,
218 * which index.c will have derived from the opclass's opcintype.
219 * (Although we now make spgvalidate.c warn if these aren't the same,
220 * old user-defined opclasses may not set the STORAGE parameter
221 * correctly, so believe leafType if it's given.)
222 */
223 if (!OidIsValid(cache->config.leafType))
224 {
225 cache->config.leafType =
227
228 /*
229 * If index column type is binary-coercible to atttype (for
230 * example, it's a domain over atttype), treat it as plain atttype
231 * to avoid thinking we need to compress.
232 */
233 if (cache->config.leafType != atttype &&
234 IsBinaryCoercible(cache->config.leafType, atttype))
235 cache->config.leafType = atttype;
236 }
237
238 /* Get the information we need about each relevant datatype */
239 fillTypeDesc(&cache->attType, atttype);
240
241 if (cache->config.leafType != atttype)
242 {
245 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
246 errmsg("compress method must be defined when leaf type is different from input type")));
247
248 fillTypeDesc(&cache->attLeafType, cache->config.leafType);
249 }
250 else
251 {
252 /* Save lookups in this common case */
253 cache->attLeafType = cache->attType;
254 }
255
257 fillTypeDesc(&cache->attLabelType, cache->config.labelType);
258
259 /*
260 * Finally, if it's a real index (not a partitioned one), get the
261 * lastUsedPages data from the metapage
262 */
263 if (index->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
264 {
265 Buffer metabuffer;
266 SpGistMetaPageData *metadata;
267
269 LockBuffer(metabuffer, BUFFER_LOCK_SHARE);
270
271 metadata = SpGistPageGetMeta(BufferGetPage(metabuffer));
272
273 if (metadata->magicNumber != SPGIST_MAGIC_NUMBER)
274 elog(ERROR, "index \"%s\" is not an SP-GiST index",
276
277 cache->lastUsedPages = metadata->lastUsedPages;
278
279 UnlockReleaseBuffer(metabuffer);
280 }
281
282 index->rd_amcache = cache;
283 }
284 else
285 {
286 /* assume it's up to date */
287 cache = (SpGistCache *) index->rd_amcache;
288 }
289
290 return cache;
291}
void LockBuffer(Buffer buffer, int mode)
Definition: bufmgr.c:5158
Buffer ReadBuffer(Relation reln, BlockNumber blockNum)
Definition: bufmgr.c:746
#define BUFFER_LOCK_SHARE
Definition: bufmgr.h:190
#define OidIsValid(objectId)
Definition: c.h:732
Datum FunctionCall2Coll(FmgrInfo *flinfo, Oid collation, Datum arg1, Datum arg2)
Definition: fmgr.c:1149
FmgrInfo * index_getprocinfo(Relation irel, AttrNumber attnum, uint16 procnum)
Definition: indexam.c:862
RegProcedure index_getprocid(Relation irel, AttrNumber attnum, uint16 procnum)
Definition: indexam.c:828
void * MemoryContextAllocZero(MemoryContext context, Size size)
Definition: mcxt.c:1215
bool IsBinaryCoercible(Oid srctype, Oid targettype)
#define INDEX_MAX_KEYS
static Datum PointerGetDatum(const void *X)
Definition: postgres.h:327
#define RelationGetRelationName(relation)
Definition: rel.h:539
#define IndexRelationGetNumberOfAttributes(relation)
Definition: rel.h:517
#define IndexRelationGetNumberOfKeyAttributes(relation)
Definition: rel.h:524
#define SPGIST_COMPRESS_PROC
Definition: spgist.h:28
#define SPGIST_CONFIG_PROC
Definition: spgist.h:23
#define SpGistPageGetMeta(p)
#define SPGIST_METAPAGE_BLKNO
#define SPGIST_MAGIC_NUMBER
static Oid GetIndexInputType(Relation index, AttrNumber indexcol)
Definition: spgutils.c:116
static void fillTypeDesc(SpGistTypeDesc *desc, Oid type)
Definition: spgutils.c:161
Definition: fmgr.h:57
SpGistLUPCache lastUsedPages
Oid attType
Definition: spgist.h:38
Oid leafType
Definition: spgist.h:45
Oid labelType
Definition: spgist.h:44
Oid prefixType
Definition: spgist.h:43

References Assert, SpGistCache::attLabelType, SpGistCache::attLeafType, SpGistCache::attPrefixType, spgConfigIn::attType, SpGistCache::attType, BUFFER_LOCK_SHARE, BufferGetPage(), SpGistCache::config, elog, ereport, errcode(), errmsg(), ERROR, fillTypeDesc(), FunctionCall2Coll(), GetIndexInputType(), index_getprocid(), index_getprocinfo(), INDEX_MAX_KEYS, IndexRelationGetNumberOfAttributes, IndexRelationGetNumberOfKeyAttributes, IsBinaryCoercible(), spgConfigOut::labelType, SpGistMetaPageData::lastUsedPages, SpGistCache::lastUsedPages, spgConfigOut::leafType, LockBuffer(), SpGistMetaPageData::magicNumber, MemoryContextAllocZero(), OidIsValid, PointerGetDatum(), spgConfigOut::prefixType, ReadBuffer(), RelationGetDescr, RelationGetRelationName, SPGIST_COMPRESS_PROC, SPGIST_CONFIG_PROC, SPGIST_MAGIC_NUMBER, SPGIST_METAPAGE_BLKNO, SpGistPageGetMeta, spgKeyColumn, TupleDescAttr(), and UnlockReleaseBuffer().

Referenced by allocNewBuffer(), initSpGistState(), spgcanreturn(), SpGistGetBuffer(), and SpGistSetLastUsedPage().

◆ spghandler()

Datum spghandler ( PG_FUNCTION_ARGS  )

Definition at line 44 of file spgutils.c.

45{
47
48 amroutine->amstrategies = 0;
49 amroutine->amsupport = SPGISTNProc;
51 amroutine->amcanorder = false;
52 amroutine->amcanorderbyop = true;
53 amroutine->amcanbackward = false;
54 amroutine->amcanunique = false;
55 amroutine->amcanmulticol = false;
56 amroutine->amoptionalkey = true;
57 amroutine->amsearcharray = false;
58 amroutine->amsearchnulls = true;
59 amroutine->amstorage = true;
60 amroutine->amclusterable = false;
61 amroutine->ampredlocks = false;
62 amroutine->amcanparallel = false;
63 amroutine->amcanbuildparallel = false;
64 amroutine->amcaninclude = true;
65 amroutine->amusemaintenanceworkmem = false;
66 amroutine->amsummarizing = false;
67 amroutine->amparallelvacuumoptions =
69 amroutine->amkeytype = InvalidOid;
70
71 amroutine->ambuild = spgbuild;
72 amroutine->ambuildempty = spgbuildempty;
73 amroutine->aminsert = spginsert;
74 amroutine->aminsertcleanup = NULL;
75 amroutine->ambulkdelete = spgbulkdelete;
77 amroutine->amcanreturn = spgcanreturn;
79 amroutine->amgettreeheight = NULL;
80 amroutine->amoptions = spgoptions;
81 amroutine->amproperty = spgproperty;
82 amroutine->ambuildphasename = NULL;
83 amroutine->amvalidate = spgvalidate;
85 amroutine->ambeginscan = spgbeginscan;
86 amroutine->amrescan = spgrescan;
87 amroutine->amgettuple = spggettuple;
88 amroutine->amgetbitmap = spggetbitmap;
89 amroutine->amendscan = spgendscan;
90 amroutine->ammarkpos = NULL;
91 amroutine->amrestrpos = NULL;
92 amroutine->amestimateparallelscan = NULL;
93 amroutine->aminitparallelscan = NULL;
94 amroutine->amparallelrescan = NULL;
95
96 PG_RETURN_POINTER(amroutine);
97}
#define PG_RETURN_POINTER(x)
Definition: fmgr.h:361
#define makeNode(_type_)
Definition: nodes.h:155
void spgcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, Cost *indexStartupCost, Cost *indexTotalCost, Selectivity *indexSelectivity, double *indexCorrelation, double *indexPages)
Definition: selfuncs.c:7262
IndexBuildResult * spgbuild(Relation heap, Relation index, IndexInfo *indexInfo)
Definition: spginsert.c:73
bool spginsert(Relation index, Datum *values, bool *isnull, ItemPointer ht_ctid, Relation heapRel, IndexUniqueCheck checkUnique, bool indexUnchanged, IndexInfo *indexInfo)
Definition: spginsert.c:183
void spgbuildempty(Relation index)
Definition: spginsert.c:154
#define SPGIST_OPTIONS_PROC
Definition: spgist.h:29
#define SPGISTNProc
Definition: spgist.h:31
IndexScanDesc spgbeginscan(Relation rel, int keysz, int orderbysz)
Definition: spgscan.c:304
bool spgcanreturn(Relation index, int attno)
Definition: spgscan.c:1081
bool spggettuple(IndexScanDesc scan, ScanDirection dir)
Definition: spgscan.c:1024
void spgendscan(IndexScanDesc scan)
Definition: spgscan.c:427
void spgrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys, ScanKey orderbys, int norderbys)
Definition: spgscan.c:380
int64 spggetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
Definition: spgscan.c:940
bytea * spgoptions(Datum reloptions, bool validate)
Definition: spgutils.c:754
bool spgproperty(Oid index_oid, int attno, IndexAMProperty prop, const char *propname, bool *res, bool *isnull)
Definition: spgutils.c:1293
IndexBulkDeleteResult * spgbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state)
Definition: spgvacuum.c:916
IndexBulkDeleteResult * spgvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
Definition: spgvacuum.c:947
bool spgvalidate(Oid opclassoid)
Definition: spgvalidate.c:39
void spgadjustmembers(Oid opfamilyoid, Oid opclassoid, List *operators, List *functions)
Definition: spgvalidate.c:332
ambuildphasename_function ambuildphasename
Definition: amapi.h:289
ambuildempty_function ambuildempty
Definition: amapi.h:279
amvacuumcleanup_function amvacuumcleanup
Definition: amapi.h:283
bool amclusterable
Definition: amapi.h:253
amoptions_function amoptions
Definition: amapi.h:287
amestimateparallelscan_function amestimateparallelscan
Definition: amapi.h:301
amrestrpos_function amrestrpos
Definition: amapi.h:298
aminsert_function aminsert
Definition: amapi.h:280
amendscan_function amendscan
Definition: amapi.h:296
uint16 amoptsprocnum
Definition: amapi.h:233
amparallelrescan_function amparallelrescan
Definition: amapi.h:303
Oid amkeytype
Definition: amapi.h:269
bool ampredlocks
Definition: amapi.h:255
uint16 amsupport
Definition: amapi.h:231
amcostestimate_function amcostestimate
Definition: amapi.h:285
bool amcanorderbyop
Definition: amapi.h:237
amadjustmembers_function amadjustmembers
Definition: amapi.h:291
ambuild_function ambuild
Definition: amapi.h:278
bool amstorage
Definition: amapi.h:251
uint16 amstrategies
Definition: amapi.h:229
bool amoptionalkey
Definition: amapi.h:245
amgettuple_function amgettuple
Definition: amapi.h:294
amcanreturn_function amcanreturn
Definition: amapi.h:284
bool amcanunique
Definition: amapi.h:241
amgetbitmap_function amgetbitmap
Definition: amapi.h:295
amproperty_function amproperty
Definition: amapi.h:288
ambulkdelete_function ambulkdelete
Definition: amapi.h:282
bool amsearcharray
Definition: amapi.h:247
bool amsummarizing
Definition: amapi.h:265
amvalidate_function amvalidate
Definition: amapi.h:290
ammarkpos_function ammarkpos
Definition: amapi.h:297
bool amcanmulticol
Definition: amapi.h:243
bool amusemaintenanceworkmem
Definition: amapi.h:263
ambeginscan_function ambeginscan
Definition: amapi.h:292
bool amcanparallel
Definition: amapi.h:257
amrescan_function amrescan
Definition: amapi.h:293
bool amcanorder
Definition: amapi.h:235
bool amcanbuildparallel
Definition: amapi.h:259
aminitparallelscan_function aminitparallelscan
Definition: amapi.h:302
uint8 amparallelvacuumoptions
Definition: amapi.h:267
aminsertcleanup_function aminsertcleanup
Definition: amapi.h:281
bool amcanbackward
Definition: amapi.h:239
amgettreeheight_function amgettreeheight
Definition: amapi.h:286
bool amcaninclude
Definition: amapi.h:261
bool amsearchnulls
Definition: amapi.h:249
#define VACUUM_OPTION_PARALLEL_BULKDEL
Definition: vacuum.h:48
#define VACUUM_OPTION_PARALLEL_COND_CLEANUP
Definition: vacuum.h:55

References IndexAmRoutine::amadjustmembers, IndexAmRoutine::ambeginscan, IndexAmRoutine::ambuild, IndexAmRoutine::ambuildempty, IndexAmRoutine::ambuildphasename, IndexAmRoutine::ambulkdelete, IndexAmRoutine::amcanbackward, IndexAmRoutine::amcanbuildparallel, IndexAmRoutine::amcaninclude, IndexAmRoutine::amcanmulticol, IndexAmRoutine::amcanorder, IndexAmRoutine::amcanorderbyop, IndexAmRoutine::amcanparallel, IndexAmRoutine::amcanreturn, IndexAmRoutine::amcanunique, IndexAmRoutine::amclusterable, IndexAmRoutine::amcostestimate, IndexAmRoutine::amendscan, IndexAmRoutine::amestimateparallelscan, IndexAmRoutine::amgetbitmap, IndexAmRoutine::amgettreeheight, IndexAmRoutine::amgettuple, IndexAmRoutine::aminitparallelscan, IndexAmRoutine::aminsert, IndexAmRoutine::aminsertcleanup, IndexAmRoutine::amkeytype, IndexAmRoutine::ammarkpos, IndexAmRoutine::amoptionalkey, IndexAmRoutine::amoptions, IndexAmRoutine::amoptsprocnum, IndexAmRoutine::amparallelrescan, IndexAmRoutine::amparallelvacuumoptions, IndexAmRoutine::ampredlocks, IndexAmRoutine::amproperty, IndexAmRoutine::amrescan, IndexAmRoutine::amrestrpos, IndexAmRoutine::amsearcharray, IndexAmRoutine::amsearchnulls, IndexAmRoutine::amstorage, IndexAmRoutine::amstrategies, IndexAmRoutine::amsummarizing, IndexAmRoutine::amsupport, IndexAmRoutine::amusemaintenanceworkmem, IndexAmRoutine::amvacuumcleanup, IndexAmRoutine::amvalidate, InvalidOid, makeNode, PG_RETURN_POINTER, spgadjustmembers(), spgbeginscan(), spgbuild(), spgbuildempty(), spgbulkdelete(), spgcanreturn(), spgcostestimate(), spgendscan(), spggetbitmap(), spggettuple(), spginsert(), SPGIST_OPTIONS_PROC, SPGISTNProc, spgoptions(), spgproperty(), spgrescan(), spgvacuumcleanup(), spgvalidate(), VACUUM_OPTION_PARALLEL_BULKDEL, and VACUUM_OPTION_PARALLEL_COND_CLEANUP.

◆ SpGistGetBuffer()

Buffer SpGistGetBuffer ( Relation  index,
int  flags,
int  needSpace,
bool *  isNew 
)

Definition at line 564 of file spgutils.c.

565{
566 SpGistCache *cache = spgGetCache(index);
568
569 /* Bail out if even an empty page wouldn't meet the demand */
570 if (needSpace > SPGIST_PAGE_CAPACITY)
571 elog(ERROR, "desired SPGiST tuple size is too big");
572
573 /*
574 * If possible, increase the space request to include relation's
575 * fillfactor. This ensures that when we add unrelated tuples to a page,
576 * we try to keep 100-fillfactor% available for adding tuples that are
577 * related to the ones already on it. But fillfactor mustn't cause an
578 * error for requests that would otherwise be legal.
579 */
581 needSpace = Min(needSpace, SPGIST_PAGE_CAPACITY);
582
583 /* Get the cache entry for this flags setting */
584 lup = GET_LUP(cache, flags);
585
586 /* If we have nothing cached, just turn it over to allocNewBuffer */
587 if (lup->blkno == InvalidBlockNumber)
588 {
589 *isNew = true;
590 return allocNewBuffer(index, flags);
591 }
592
593 /* fixed pages should never be in cache */
595
596 /* If cached freeSpace isn't enough, don't bother looking at the page */
597 if (lup->freeSpace >= needSpace)
598 {
599 Buffer buffer;
600 Page page;
601
602 buffer = ReadBuffer(index, lup->blkno);
603
604 if (!ConditionalLockBuffer(buffer))
605 {
606 /*
607 * buffer is locked by another process, so return a new buffer
608 */
609 ReleaseBuffer(buffer);
610 *isNew = true;
611 return allocNewBuffer(index, flags);
612 }
613
614 page = BufferGetPage(buffer);
615
616 if (PageIsNew(page) || SpGistPageIsDeleted(page) || PageIsEmpty(page))
617 {
618 /* OK to initialize the page */
619 uint16 pageflags = 0;
620
621 if (GBUF_REQ_LEAF(flags))
622 pageflags |= SPGIST_LEAF;
623 if (GBUF_REQ_NULLS(flags))
624 pageflags |= SPGIST_NULLS;
625 SpGistInitBuffer(buffer, pageflags);
626 lup->freeSpace = PageGetExactFreeSpace(page) - needSpace;
627 *isNew = true;
628 return buffer;
629 }
630
631 /*
632 * Check that page is of right type and has enough space. We must
633 * recheck this since our cache isn't necessarily up to date.
634 */
635 if ((GBUF_REQ_LEAF(flags) ? SpGistPageIsLeaf(page) : !SpGistPageIsLeaf(page)) &&
637 {
638 int freeSpace = PageGetExactFreeSpace(page);
639
640 if (freeSpace >= needSpace)
641 {
642 /* Success, update freespace info and return the buffer */
643 lup->freeSpace = freeSpace - needSpace;
644 *isNew = false;
645 return buffer;
646 }
647 }
648
649 /*
650 * fallback to allocation of new buffer
651 */
652 UnlockReleaseBuffer(buffer);
653 }
654
655 /* No success with cache, so return a new buffer */
656 *isNew = true;
657 return allocNewBuffer(index, flags);
658}
#define InvalidBlockNumber
Definition: block.h:33
bool ConditionalLockBuffer(Buffer buffer)
Definition: bufmgr.c:5184
void ReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:4924
static bool PageIsEmpty(Page page)
Definition: bufpage.h:223
Pointer Page
Definition: bufpage.h:81
static bool PageIsNew(Page page)
Definition: bufpage.h:233
#define Min(x, y)
Definition: c.h:961
#define SpGistPageStoresNulls(page)
#define SpGistGetTargetPageFreeSpace(relation)
#define SpGistPageIsLeaf(page)
#define SpGistPageIsDeleted(page)
#define SpGistBlockIsFixed(blkno)
static Buffer allocNewBuffer(Relation index, int flags)
Definition: spgutils.c:508
#define GET_LUP(c, f)
Definition: spgutils.c:485

References allocNewBuffer(), Assert, SpGistLastUsedPage::blkno, BufferGetPage(), ConditionalLockBuffer(), elog, ERROR, SpGistLastUsedPage::freeSpace, GBUF_REQ_LEAF, GBUF_REQ_NULLS, GET_LUP, InvalidBlockNumber, Min, PageGetExactFreeSpace(), PageIsEmpty(), PageIsNew(), ReadBuffer(), ReleaseBuffer(), spgGetCache(), SPGIST_LEAF, SPGIST_NULLS, SPGIST_PAGE_CAPACITY, SpGistBlockIsFixed, SpGistGetTargetPageFreeSpace, SpGistInitBuffer(), SpGistPageIsDeleted, SpGistPageIsLeaf, SpGistPageStoresNulls, and UnlockReleaseBuffer().

Referenced by doPickSplit(), moveLeafs(), spgAddNodeAction(), spgdoinsert(), and spgSplitNodeAction().

◆ SpGistGetInnerTypeSize()

unsigned int SpGistGetInnerTypeSize ( SpGistTypeDesc att,
Datum  datum 
)

Definition at line 774 of file spgutils.c.

775{
776 unsigned int size;
777
778 if (att->attbyval)
779 size = sizeof(Datum);
780 else if (att->attlen > 0)
781 size = att->attlen;
782 else
783 size = VARSIZE_ANY(datum);
784
785 return MAXALIGN(size);
786}

References SpGistTypeDesc::attbyval, SpGistTypeDesc::attlen, MAXALIGN, size, and VARSIZE_ANY.

Referenced by spgFormInnerTuple(), and spgFormNodeTuple().

◆ SpGistGetLeafTupleSize()

Size SpGistGetLeafTupleSize ( TupleDesc  tupleDescriptor,
const Datum datums,
const bool *  isnulls 
)

Definition at line 813 of file spgutils.c.

815{
816 Size size;
817 Size data_size;
818 bool needs_null_mask = false;
819 int natts = tupleDescriptor->natts;
820
821 /*
822 * Decide whether we need a nulls bitmask.
823 *
824 * If there is only a key attribute (natts == 1), never use a bitmask, for
825 * compatibility with the pre-v14 layout of leaf tuples. Otherwise, we
826 * need one if any attribute is null.
827 */
828 if (natts > 1)
829 {
830 for (int i = 0; i < natts; i++)
831 {
832 if (isnulls[i])
833 {
834 needs_null_mask = true;
835 break;
836 }
837 }
838 }
839
840 /*
841 * Calculate size of the data part; same as for heap tuples.
842 */
843 data_size = heap_compute_data_size(tupleDescriptor, datums, isnulls);
844
845 /*
846 * Compute total size.
847 */
848 size = SGLTHDRSZ(needs_null_mask);
849 size += data_size;
850 size = MAXALIGN(size);
851
852 /*
853 * Ensure that we can replace the tuple with a dead tuple later. This test
854 * is unnecessary when there are any non-null attributes, but be safe.
855 */
856 if (size < SGDTSIZE)
857 size = SGDTSIZE;
858
859 return size;
860}

References heap_compute_data_size(), i, MAXALIGN, TupleDescData::natts, SGDTSIZE, SGLTHDRSZ, and size.

Referenced by spgdoinsert().

◆ SpGistInitBuffer()

void SpGistInitBuffer ( Buffer  b,
uint16  f 
)

Definition at line 717 of file spgutils.c.

718{
719 Assert(BufferGetPageSize(b) == BLCKSZ);
721}
static Size BufferGetPageSize(Buffer buffer)
Definition: bufmgr.h:389
int b
Definition: isn.c:69
void SpGistInitPage(Page page, uint16 f)
Definition: spgutils.c:703

References Assert, b, BufferGetPage(), BufferGetPageSize(), and SpGistInitPage().

Referenced by allocNewBuffer(), doPickSplit(), spgbuild(), SpGistGetBuffer(), spgRedoAddLeaf(), spgRedoAddNode(), spgRedoMoveLeafs(), spgRedoPickSplit(), and spgRedoSplitTuple().

◆ SpGistInitMetapage()

void SpGistInitMetapage ( Page  page)

Definition at line 727 of file spgutils.c.

728{
729 SpGistMetaPageData *metadata;
730 int i;
731
733 metadata = SpGistPageGetMeta(page);
734 memset(metadata, 0, sizeof(SpGistMetaPageData));
736
737 /* initialize last-used-page cache to empty */
738 for (i = 0; i < SPGIST_CACHED_PAGES; i++)
740
741 /*
742 * Set pd_lower just past the end of the metadata. This is essential,
743 * because without doing so, metadata will be lost if xlog.c compresses
744 * the page.
745 */
746 ((PageHeader) page)->pd_lower =
747 ((char *) metadata + sizeof(SpGistMetaPageData)) - (char *) page;
748}
#define SPGIST_META
#define SPGIST_CACHED_PAGES

References SpGistLastUsedPage::blkno, SpGistLUPCache::cachedPage, i, InvalidBlockNumber, SpGistMetaPageData::lastUsedPages, SpGistMetaPageData::magicNumber, SPGIST_CACHED_PAGES, SPGIST_MAGIC_NUMBER, SPGIST_META, SpGistInitPage(), and SpGistPageGetMeta.

Referenced by spgbuild(), and spgbuildempty().

◆ SpGistInitPage()

void SpGistInitPage ( Page  page,
uint16  f 
)

Definition at line 703 of file spgutils.c.

704{
705 SpGistPageOpaque opaque;
706
707 PageInit(page, BLCKSZ, sizeof(SpGistPageOpaqueData));
708 opaque = SpGistPageGetOpaque(page);
709 opaque->flags = f;
711}
void PageInit(Page page, Size pageSize, Size specialSize)
Definition: bufpage.c:42
#define SpGistPageGetOpaque(page)
#define SPGIST_PAGE_ID

References SpGistPageOpaqueData::flags, PageInit(), SpGistPageOpaqueData::spgist_page_id, SPGIST_PAGE_ID, and SpGistPageGetOpaque.

Referenced by spgbuildempty(), SpGistInitBuffer(), and SpGistInitMetapage().

◆ SpGistNewBuffer()

Buffer SpGistNewBuffer ( Relation  index)

Definition at line 389 of file spgutils.c.

390{
391 Buffer buffer;
392
393 /* First, try to get a page from FSM */
394 for (;;)
395 {
397
398 if (blkno == InvalidBlockNumber)
399 break; /* nothing known to FSM */
400
401 /*
402 * The fixed pages shouldn't ever be listed in FSM, but just in case
403 * one is, ignore it.
404 */
405 if (SpGistBlockIsFixed(blkno))
406 continue;
407
408 buffer = ReadBuffer(index, blkno);
409
410 /*
411 * We have to guard against the possibility that someone else already
412 * recycled this page; the buffer may be locked if so.
413 */
414 if (ConditionalLockBuffer(buffer))
415 {
416 Page page = BufferGetPage(buffer);
417
418 if (PageIsNew(page))
419 return buffer; /* OK to use, if never initialized */
420
421 if (SpGistPageIsDeleted(page) || PageIsEmpty(page))
422 return buffer; /* OK to use */
423
425 }
426
427 /* Can't use it, so release buffer and try again */
428 ReleaseBuffer(buffer);
429 }
430
433
434 return buffer;
435}
Buffer ExtendBufferedRel(BufferManagerRelation bmr, ForkNumber forkNum, BufferAccessStrategy strategy, uint32 flags)
Definition: bufmgr.c:846
#define BUFFER_LOCK_UNLOCK
Definition: bufmgr.h:189
@ EB_LOCK_FIRST
Definition: bufmgr.h:86
#define BMR_REL(p_rel)
Definition: bufmgr.h:107
BlockNumber GetFreeIndexPage(Relation rel)
Definition: indexfsm.c:38
@ MAIN_FORKNUM
Definition: relpath.h:58

References BMR_REL, BUFFER_LOCK_UNLOCK, BufferGetPage(), ConditionalLockBuffer(), EB_LOCK_FIRST, ExtendBufferedRel(), GetFreeIndexPage(), InvalidBlockNumber, LockBuffer(), MAIN_FORKNUM, PageIsEmpty(), PageIsNew(), ReadBuffer(), ReleaseBuffer(), SpGistBlockIsFixed, and SpGistPageIsDeleted.

Referenced by allocNewBuffer(), and spgbuild().

◆ SpGistPageAddNewItem()

OffsetNumber SpGistPageAddNewItem ( SpGistState state,
Page  page,
Item  item,
Size  size,
OffsetNumber startOffset,
bool  errorOK 
)

Definition at line 1198 of file spgutils.c.

1200{
1203 maxoff,
1204 offnum;
1205
1206 if (opaque->nPlaceholder > 0 &&
1208 {
1209 /* Try to replace a placeholder */
1210 maxoff = PageGetMaxOffsetNumber(page);
1211 offnum = InvalidOffsetNumber;
1212
1213 for (;;)
1214 {
1215 if (startOffset && *startOffset != InvalidOffsetNumber)
1216 i = *startOffset;
1217 else
1219 for (; i <= maxoff; i++)
1220 {
1222 PageGetItemId(page, i));
1223
1224 if (it->tupstate == SPGIST_PLACEHOLDER)
1225 {
1226 offnum = i;
1227 break;
1228 }
1229 }
1230
1231 /* Done if we found a placeholder */
1232 if (offnum != InvalidOffsetNumber)
1233 break;
1234
1235 if (startOffset && *startOffset != InvalidOffsetNumber)
1236 {
1237 /* Hint was no good, re-search from beginning */
1238 *startOffset = InvalidOffsetNumber;
1239 continue;
1240 }
1241
1242 /* Hmm, no placeholder found? */
1243 opaque->nPlaceholder = 0;
1244 break;
1245 }
1246
1247 if (offnum != InvalidOffsetNumber)
1248 {
1249 /* Replace the placeholder tuple */
1250 PageIndexTupleDelete(page, offnum);
1251
1252 offnum = PageAddItem(page, item, size, offnum, false, false);
1253
1254 /*
1255 * We should not have failed given the size check at the top of
1256 * the function, but test anyway. If we did fail, we must PANIC
1257 * because we've already deleted the placeholder tuple, and
1258 * there's no other way to keep the damage from getting to disk.
1259 */
1260 if (offnum != InvalidOffsetNumber)
1261 {
1262 Assert(opaque->nPlaceholder > 0);
1263 opaque->nPlaceholder--;
1264 if (startOffset)
1265 *startOffset = offnum + 1;
1266 }
1267 else
1268 elog(PANIC, "failed to add item of size %zu to SPGiST index page",
1269 size);
1270
1271 return offnum;
1272 }
1273 }
1274
1275 /* No luck in replacing a placeholder, so just add it to the page */
1276 offnum = PageAddItem(page, item, size,
1277 InvalidOffsetNumber, false, false);
1278
1279 if (offnum == InvalidOffsetNumber && !errorOK)
1280 elog(ERROR, "failed to add item of size %zu to SPGiST index page",
1281 size);
1282
1283 return offnum;
1284}
void PageIndexTupleDelete(Page page, OffsetNumber offnum)
Definition: bufpage.c:1041
static Item PageGetItem(Page page, ItemId itemId)
Definition: bufpage.h:354
static ItemId PageGetItemId(Page page, OffsetNumber offsetNumber)
Definition: bufpage.h:243
static OffsetNumber PageGetMaxOffsetNumber(Page page)
Definition: bufpage.h:372
#define PageAddItem(page, item, size, offsetNumber, overwrite, is_heap)
Definition: bufpage.h:471
#define PANIC
Definition: elog.h:42
uint16 OffsetNumber
Definition: off.h:24
#define FirstOffsetNumber
Definition: off.h:27
#define SPGIST_PLACEHOLDER

References Assert, elog, ERROR, FirstOffsetNumber, i, InvalidOffsetNumber, MAXALIGN, SpGistPageOpaqueData::nPlaceholder, PageAddItem, PageGetExactFreeSpace(), PageGetItem(), PageGetItemId(), PageGetMaxOffsetNumber(), PageIndexTupleDelete(), PANIC, SGDTSIZE, size, SPGIST_PLACEHOLDER, SpGistPageGetOpaque, and SpGistDeadTupleData::tupstate.

Referenced by addLeafTuple(), doPickSplit(), moveLeafs(), spgAddNodeAction(), and spgSplitNodeAction().

◆ SpGistSetLastUsedPage()

void SpGistSetLastUsedPage ( Relation  index,
Buffer  buffer 
)

Definition at line 668 of file spgutils.c.

669{
670 SpGistCache *cache = spgGetCache(index);
672 int freeSpace;
673 Page page = BufferGetPage(buffer);
674 BlockNumber blkno = BufferGetBlockNumber(buffer);
675 int flags;
676
677 /* Never enter fixed pages (root pages) in cache, though */
678 if (SpGistBlockIsFixed(blkno))
679 return;
680
681 if (SpGistPageIsLeaf(page))
682 flags = GBUF_LEAF;
683 else
684 flags = GBUF_INNER_PARITY(blkno);
685 if (SpGistPageStoresNulls(page))
686 flags |= GBUF_NULLS;
687
688 lup = GET_LUP(cache, flags);
689
690 freeSpace = PageGetExactFreeSpace(page);
691 if (lup->blkno == InvalidBlockNumber || lup->blkno == blkno ||
692 lup->freeSpace < freeSpace)
693 {
694 lup->blkno = blkno;
695 lup->freeSpace = freeSpace;
696 }
697}
#define GBUF_LEAF

References SpGistLastUsedPage::blkno, BufferGetBlockNumber(), BufferGetPage(), SpGistLastUsedPage::freeSpace, GBUF_INNER_PARITY, GBUF_LEAF, GBUF_NULLS, GET_LUP, InvalidBlockNumber, PageGetExactFreeSpace(), spgGetCache(), SpGistBlockIsFixed, SpGistPageIsLeaf, and SpGistPageStoresNulls.

Referenced by doPickSplit(), moveLeafs(), spgAddNodeAction(), spgdoinsert(), spgMatchNodeAction(), spgprocesspending(), spgSplitNodeAction(), and spgvacuumpage().

◆ SpGistUpdateMetaPage()

void SpGistUpdateMetaPage ( Relation  index)

Definition at line 445 of file spgutils.c.

446{
447 SpGistCache *cache = (SpGistCache *) index->rd_amcache;
448
449 if (cache != NULL)
450 {
451 Buffer metabuffer;
452
454
455 if (ConditionalLockBuffer(metabuffer))
456 {
457 Page metapage = BufferGetPage(metabuffer);
458 SpGistMetaPageData *metadata = SpGistPageGetMeta(metapage);
459
460 metadata->lastUsedPages = cache->lastUsedPages;
461
462 /*
463 * Set pd_lower just past the end of the metadata. This is
464 * essential, because without doing so, metadata will be lost if
465 * xlog.c compresses the page. (We must do this here because
466 * pre-v11 versions of PG did not set the metapage's pd_lower
467 * correctly, so a pg_upgraded index might contain the wrong
468 * value.)
469 */
470 ((PageHeader) metapage)->pd_lower =
471 ((char *) metadata + sizeof(SpGistMetaPageData)) - (char *) metapage;
472
473 MarkBufferDirty(metabuffer);
474 UnlockReleaseBuffer(metabuffer);
475 }
476 else
477 {
478 ReleaseBuffer(metabuffer);
479 }
480 }
481}
void MarkBufferDirty(Buffer buffer)
Definition: bufmgr.c:2532
PageHeaderData * PageHeader
Definition: bufpage.h:173
if(TABLE==NULL||TABLE_index==NULL)
Definition: isn.c:76

References BufferGetPage(), ConditionalLockBuffer(), if(), SpGistMetaPageData::lastUsedPages, SpGistCache::lastUsedPages, MarkBufferDirty(), ReadBuffer(), ReleaseBuffer(), SPGIST_METAPAGE_BLKNO, SpGistPageGetMeta, and UnlockReleaseBuffer().

Referenced by spgbuild(), spginsert(), and spgvacuumscan().

◆ spgoptions()

bytea * spgoptions ( Datum  reloptions,
bool  validate 
)

Definition at line 754 of file spgutils.c.

755{
756 static const relopt_parse_elt tab[] = {
757 {"fillfactor", RELOPT_TYPE_INT, offsetof(SpGistOptions, fillfactor)},
758 };
759
760 return (bytea *) build_reloptions(reloptions, validate,
762 sizeof(SpGistOptions),
763 tab, lengthof(tab));
764}
#define lengthof(array)
Definition: c.h:745
static int fillfactor
Definition: pgbench.c:187
void * build_reloptions(Datum reloptions, bool validate, relopt_kind kind, Size relopt_struct_size, const relopt_parse_elt *relopt_elems, int num_relopt_elems)
Definition: reloptions.c:1908
@ RELOPT_KIND_SPGIST
Definition: reloptions.h:50
@ RELOPT_TYPE_INT
Definition: reloptions.h:32
Definition: c.h:644

References build_reloptions(), fillfactor, lengthof, RELOPT_KIND_SPGIST, and RELOPT_TYPE_INT.

Referenced by spghandler().

◆ spgproperty()

bool spgproperty ( Oid  index_oid,
int  attno,
IndexAMProperty  prop,
const char *  propname,
bool *  res,
bool *  isnull 
)

Definition at line 1293 of file spgutils.c.

1296{
1297 Oid opclass,
1298 opfamily,
1299 opcintype;
1300 CatCList *catlist;
1301 int i;
1302
1303 /* Only answer column-level inquiries */
1304 if (attno == 0)
1305 return false;
1306
1307 switch (prop)
1308 {
1310 break;
1311 default:
1312 return false;
1313 }
1314
1315 /*
1316 * Currently, SP-GiST distance-ordered scans require that there be a
1317 * distance operator in the opclass with the default types. So we assume
1318 * that if such an operator exists, then there's a reason for it.
1319 */
1320
1321 /* First we need to know the column's opclass. */
1322 opclass = get_index_column_opclass(index_oid, attno);
1323 if (!OidIsValid(opclass))
1324 {
1325 *isnull = true;
1326 return true;
1327 }
1328
1329 /* Now look up the opclass family and input datatype. */
1330 if (!get_opclass_opfamily_and_input_type(opclass, &opfamily, &opcintype))
1331 {
1332 *isnull = true;
1333 return true;
1334 }
1335
1336 /* And now we can check whether the operator is provided. */
1337 catlist = SearchSysCacheList1(AMOPSTRATEGY,
1338 ObjectIdGetDatum(opfamily));
1339
1340 *res = false;
1341
1342 for (i = 0; i < catlist->n_members; i++)
1343 {
1344 HeapTuple amoptup = &catlist->members[i]->tuple;
1345 Form_pg_amop amopform = (Form_pg_amop) GETSTRUCT(amoptup);
1346
1347 if (amopform->amoppurpose == AMOP_ORDER &&
1348 (amopform->amoplefttype == opcintype ||
1349 amopform->amoprighttype == opcintype) &&
1350 opfamily_can_sort_type(amopform->amopsortfamily,
1351 get_op_rettype(amopform->amopopr)))
1352 {
1353 *res = true;
1354 break;
1355 }
1356 }
1357
1358 ReleaseSysCacheList(catlist);
1359
1360 *isnull = false;
1361
1362 return true;
1363}
@ AMPROP_DISTANCE_ORDERABLE
Definition: amapi.h:42
bool opfamily_can_sort_type(Oid opfamilyoid, Oid datatypeoid)
Definition: amvalidate.c:271
bool get_opclass_opfamily_and_input_type(Oid opclass, Oid *opfamily, Oid *opcintype)
Definition: lsyscache.c:1235
Oid get_op_rettype(Oid opno)
Definition: lsyscache.c:1333
Oid get_index_column_opclass(Oid index_oid, int attno)
Definition: lsyscache.c:3512
FormData_pg_amop * Form_pg_amop
Definition: pg_amop.h:88
CatCTup * members[FLEXIBLE_ARRAY_MEMBER]
Definition: catcache.h:180
int n_members
Definition: catcache.h:178
HeapTupleData tuple
Definition: catcache.h:123
#define ReleaseSysCacheList(x)
Definition: syscache.h:134
#define SearchSysCacheList1(cacheId, key1)
Definition: syscache.h:127

References AMPROP_DISTANCE_ORDERABLE, get_index_column_opclass(), get_op_rettype(), get_opclass_opfamily_and_input_type(), GETSTRUCT, i, catclist::members, catclist::n_members, ObjectIdGetDatum(), OidIsValid, opfamily_can_sort_type(), ReleaseSysCacheList, res, SearchSysCacheList1, and catctup::tuple.

Referenced by spghandler().