1
/*-------------------------------------------------------------------------
4
* utilities routines for the postgres GiST index access method.
7
* Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
8
* Portions Copyright (c) 1994, Regents of the University of California
11
* src/backend/access/gist/gistutil.c
12
*-------------------------------------------------------------------------
16
#include "access/gist_private.h"
17
#include "access/reloptions.h"
18
#include "storage/freespace.h"
19
#include "storage/indexfsm.h"
20
#include "storage/lmgr.h"
21
#include "storage/bufmgr.h"
22
#include "utils/rel.h"
25
* static *S used for temrorary storage (saves stack and palloc() call)
28
static Datum attrS[INDEX_MAX_KEYS];
29
static bool isnullS[INDEX_MAX_KEYS];
32
* Write itup vector to page, has no control of free space.
35
gistfillbuffer(Page page, IndexTuple *itup, int len, OffsetNumber off)
37
OffsetNumber l = InvalidOffsetNumber;
40
if (off == InvalidOffsetNumber)
41
off = (PageIsEmpty(page)) ? FirstOffsetNumber :
42
OffsetNumberNext(PageGetMaxOffsetNumber(page));
44
for (i = 0; i < len; i++)
46
Size sz = IndexTupleSize(itup[i]);
48
l = PageAddItem(page, (Item) itup[i], sz, off, false, false);
49
if (l == InvalidOffsetNumber)
50
elog(ERROR, "failed to add item to GiST index page, item %d out of %d, size %d bytes",
57
* Check space for itup vector on page
60
gistnospace(Page page, IndexTuple *itvec, int len, OffsetNumber todelete, Size freespace)
62
unsigned int size = freespace,
66
for (i = 0; i < len; i++)
67
size += IndexTupleSize(itvec[i]) + sizeof(ItemIdData);
69
if (todelete != InvalidOffsetNumber)
71
IndexTuple itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, todelete));
73
deleted = IndexTupleSize(itup) + sizeof(ItemIdData);
76
return (PageGetFreeSpace(page) + deleted < size);
80
gistfitpage(IndexTuple *itvec, int len)
85
for (i = 0; i < len; i++)
86
size += IndexTupleSize(itvec[i]) + sizeof(ItemIdData);
88
/* TODO: Consider fillfactor */
89
return (size <= GiSTPageSize);
93
* Read buffer into itup vector
96
gistextractpage(Page page, int *len /* out */ )
102
maxoff = PageGetMaxOffsetNumber(page);
104
itvec = palloc(sizeof(IndexTuple) * maxoff);
105
for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
106
itvec[i - FirstOffsetNumber] = (IndexTuple) PageGetItem(page, PageGetItemId(page, i));
112
* join two vectors into one
115
gistjoinvector(IndexTuple *itvec, int *len, IndexTuple *additvec, int addlen)
117
itvec = (IndexTuple *) repalloc((void *) itvec, sizeof(IndexTuple) * ((*len) + addlen));
118
memmove(&itvec[*len], additvec, sizeof(IndexTuple) * addlen);
124
* make plain IndexTupleVector
128
gistfillitupvec(IndexTuple *vec, int veclen, int *memlen)
136
for (i = 0; i < veclen; i++)
137
*memlen += IndexTupleSize(vec[i]);
139
ptr = ret = palloc(*memlen);
141
for (i = 0; i < veclen; i++)
143
memcpy(ptr, vec[i], IndexTupleSize(vec[i]));
144
ptr += IndexTupleSize(vec[i]);
147
return (IndexTupleData *) ret;
151
* Make unions of keys in IndexTuple vector, return FALSE if itvec contains
152
* invalid tuple. Resulting Datums aren't compressed.
156
gistMakeUnionItVec(GISTSTATE *giststate, IndexTuple *itvec, int len, int startkey,
157
Datum *attr, bool *isnull)
160
GistEntryVector *evec;
163
evec = (GistEntryVector *) palloc((len + 2) * sizeof(GISTENTRY) + GEVHDRSZ);
165
for (i = startkey; i < giststate->tupdesc->natts; i++)
172
gistentryinit(evec->vector[evec->n], attr[i],
173
NULL, NULL, (OffsetNumber) 0,
178
for (j = 0; j < len; j++)
183
datum = index_getattr(itvec[j], i + 1, giststate->tupdesc, &IsNull);
187
gistdentryinit(giststate, i,
188
evec->vector + evec->n,
190
NULL, NULL, (OffsetNumber) 0,
195
/* If this tuple vector was all NULLs, the union is NULL */
206
evec->vector[1] = evec->vector[0];
209
/* Make union and store in attr array */
210
attr[i] = FunctionCall2Coll(&giststate->unionFn[i],
211
giststate->supportCollation[i],
212
PointerGetDatum(evec),
213
PointerGetDatum(&attrsize));
221
* Return an IndexTuple containing the result of applying the "union"
222
* method to the specified IndexTuple vector.
225
gistunion(Relation r, IndexTuple *itvec, int len, GISTSTATE *giststate)
227
memset(isnullS, TRUE, sizeof(bool) * giststate->tupdesc->natts);
229
gistMakeUnionItVec(giststate, itvec, len, 0, attrS, isnullS);
231
return gistFormTuple(giststate, r, attrS, isnullS, false);
235
* makes union of two key
238
gistMakeUnionKey(GISTSTATE *giststate, int attno,
239
GISTENTRY *entry1, bool isnull1,
240
GISTENTRY *entry2, bool isnull2,
241
Datum *dst, bool *dstisnull)
246
static char storage[2 * sizeof(GISTENTRY) + GEVHDRSZ];
247
GistEntryVector *evec = (GistEntryVector *) storage;
251
if (isnull1 && isnull2)
258
if (isnull1 == FALSE && isnull2 == FALSE)
260
evec->vector[0] = *entry1;
261
evec->vector[1] = *entry2;
263
else if (isnull1 == FALSE)
265
evec->vector[0] = *entry1;
266
evec->vector[1] = *entry1;
270
evec->vector[0] = *entry2;
271
evec->vector[1] = *entry2;
275
*dst = FunctionCall2Coll(&giststate->unionFn[attno],
276
giststate->supportCollation[attno],
277
PointerGetDatum(evec),
278
PointerGetDatum(&dstsize));
283
gistKeyIsEQ(GISTSTATE *giststate, int attno, Datum a, Datum b)
287
FunctionCall3Coll(&giststate->equalFn[attno],
288
giststate->supportCollation[attno],
290
PointerGetDatum(&result));
295
* Decompress all keys in tuple
298
gistDeCompressAtt(GISTSTATE *giststate, Relation r, IndexTuple tuple, Page p,
299
OffsetNumber o, GISTENTRY *attdata, bool *isnull)
303
for (i = 0; i < r->rd_att->natts; i++)
305
Datum datum = index_getattr(tuple, i + 1, giststate->tupdesc, &isnull[i]);
307
gistdentryinit(giststate, i, &attdata[i],
314
* Forms union of oldtup and addtup, if union == oldtup then return NULL
317
gistgetadjusted(Relation r, IndexTuple oldtup, IndexTuple addtup, GISTSTATE *giststate)
319
bool neednew = FALSE;
320
GISTENTRY oldentries[INDEX_MAX_KEYS],
321
addentries[INDEX_MAX_KEYS];
322
bool oldisnull[INDEX_MAX_KEYS],
323
addisnull[INDEX_MAX_KEYS];
324
IndexTuple newtup = NULL;
327
gistDeCompressAtt(giststate, r, oldtup, NULL,
328
(OffsetNumber) 0, oldentries, oldisnull);
330
gistDeCompressAtt(giststate, r, addtup, NULL,
331
(OffsetNumber) 0, addentries, addisnull);
333
for (i = 0; i < r->rd_att->natts; i++)
335
gistMakeUnionKey(giststate, i,
336
oldentries + i, oldisnull[i],
337
addentries + i, addisnull[i],
338
attrS + i, isnullS + i);
341
/* we already need new key, so we can skip check */
345
/* union of key may be NULL if and only if both keys are NULL */
350
if (oldisnull[i] || gistKeyIsEQ(giststate, i, oldentries[i].key, attrS[i]) == false)
357
/* need to update key */
358
newtup = gistFormTuple(giststate, r, attrS, isnullS, false);
359
newtup->t_tid = oldtup->t_tid;
366
* find entry with lowest penalty
369
gistchoose(Relation r, Page p, IndexTuple it, /* it has compressed entry */
370
GISTSTATE *giststate)
376
which_grow[INDEX_MAX_KEYS];
378
identry[INDEX_MAX_KEYS];
379
bool isnull[INDEX_MAX_KEYS];
381
maxoff = PageGetMaxOffsetNumber(p);
383
which = InvalidOffsetNumber;
385
gistDeCompressAtt(giststate, r,
386
it, NULL, (OffsetNumber) 0,
389
Assert(maxoff >= FirstOffsetNumber);
390
Assert(!GistPageIsLeaf(p));
392
for (i = FirstOffsetNumber; i <= maxoff && sum_grow; i = OffsetNumberNext(i))
395
IndexTuple itup = (IndexTuple) PageGetItem(p, PageGetItemId(p, i));
398
for (j = 0; j < r->rd_att->natts; j++)
404
datum = index_getattr(itup, j + 1, giststate->tupdesc, &IsNull);
405
gistdentryinit(giststate, j, &entry, datum, r, p, i,
407
usize = gistpenalty(giststate, j, &entry, IsNull,
408
&identry[j], isnull[j]);
410
if (which_grow[j] < 0 || usize < which_grow[j])
413
which_grow[j] = usize;
414
if (j < r->rd_att->natts - 1 && i == FirstOffsetNumber)
415
which_grow[j + 1] = -1;
416
sum_grow += which_grow[j];
418
else if (which_grow[j] == usize)
428
if (which == InvalidOffsetNumber)
429
which = FirstOffsetNumber;
435
* initialize a GiST entry with a decompressed version of key
438
gistdentryinit(GISTSTATE *giststate, int nkey, GISTENTRY *e,
439
Datum k, Relation r, Page pg, OffsetNumber o,
446
gistentryinit(*e, k, r, pg, o, l);
448
DatumGetPointer(FunctionCall1Coll(&giststate->decompressFn[nkey],
449
giststate->supportCollation[nkey],
450
PointerGetDatum(e)));
451
/* decompressFn may just return the given pointer */
453
gistentryinit(*e, dep->key, dep->rel, dep->page, dep->offset,
457
gistentryinit(*e, (Datum) 0, r, pg, o, l);
462
* initialize a GiST entry with a compressed version of key
465
gistcentryinit(GISTSTATE *giststate, int nkey,
466
GISTENTRY *e, Datum k, Relation r,
467
Page pg, OffsetNumber o, bool l, bool isNull)
473
gistentryinit(*e, k, r, pg, o, l);
475
DatumGetPointer(FunctionCall1Coll(&giststate->compressFn[nkey],
476
giststate->supportCollation[nkey],
477
PointerGetDatum(e)));
478
/* compressFn may just return the given pointer */
480
gistentryinit(*e, cep->key, cep->rel, cep->page, cep->offset,
484
gistentryinit(*e, (Datum) 0, r, pg, o, l);
488
gistFormTuple(GISTSTATE *giststate, Relation r,
489
Datum attdata[], bool isnull[], bool newValues)
491
GISTENTRY centry[INDEX_MAX_KEYS];
492
Datum compatt[INDEX_MAX_KEYS];
496
for (i = 0; i < r->rd_att->natts; i++)
499
compatt[i] = (Datum) 0;
502
gistcentryinit(giststate, i, ¢ry[i], attdata[i],
503
r, NULL, (OffsetNumber) 0,
506
compatt[i] = centry[i].key;
510
res = index_form_tuple(giststate->tupdesc, compatt, isnull);
513
* The offset number on tuples on internal pages is unused. For historical
514
* reasons, it is set 0xffff.
516
ItemPointerSetOffsetNumber(&(res->t_tid), 0xffff);
521
gistpenalty(GISTSTATE *giststate, int attno,
522
GISTENTRY *orig, bool isNullOrig,
523
GISTENTRY *add, bool isNullAdd)
527
if (giststate->penaltyFn[attno].fn_strict == FALSE ||
528
(isNullOrig == FALSE && isNullAdd == FALSE))
529
FunctionCall3Coll(&giststate->penaltyFn[attno],
530
giststate->supportCollation[attno],
531
PointerGetDatum(orig),
532
PointerGetDatum(add),
533
PointerGetDatum(&penalty));
534
else if (isNullOrig && isNullAdd)
537
penalty = 1e10; /* try to prevent to mix null and non-null
544
* Initialize a new index page
547
GISTInitBuffer(Buffer b, uint32 f)
549
GISTPageOpaque opaque;
553
pageSize = BufferGetPageSize(b);
554
page = BufferGetPage(b);
555
PageInit(page, pageSize, sizeof(GISTPageOpaqueData));
557
opaque = GistPageGetOpaque(page);
558
/* page was already zeroed by PageInit, so this is not needed: */
559
/* memset(&(opaque->nsn), 0, sizeof(GistNSN)); */
560
opaque->rightlink = InvalidBlockNumber;
562
opaque->gist_page_id = GIST_PAGE_ID;
566
* Verify that a freshly-read page looks sane.
569
gistcheckpage(Relation rel, Buffer buf)
571
Page page = BufferGetPage(buf);
574
* ReadBuffer verifies that every newly-read page passes
575
* PageHeaderIsValid, which means it either contains a reasonably sane
576
* page header or is all-zero. We have to defend against the all-zero
581
(errcode(ERRCODE_INDEX_CORRUPTED),
582
errmsg("index \"%s\" contains unexpected zero page at block %u",
583
RelationGetRelationName(rel),
584
BufferGetBlockNumber(buf)),
585
errhint("Please REINDEX it.")));
588
* Additionally check that the special area looks sane.
590
if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GISTPageOpaqueData)))
592
(errcode(ERRCODE_INDEX_CORRUPTED),
593
errmsg("index \"%s\" contains corrupted page at block %u",
594
RelationGetRelationName(rel),
595
BufferGetBlockNumber(buf)),
596
errhint("Please REINDEX it.")));
601
* Allocate a new page (either by recycling, or by extending the index file)
603
* The returned buffer is already pinned and exclusive-locked
605
* Caller is responsible for initializing the page by calling GISTInitBuffer
608
gistNewBuffer(Relation r)
613
/* First, try to get a page from FSM */
616
BlockNumber blkno = GetFreeIndexPage(r);
618
if (blkno == InvalidBlockNumber)
619
break; /* nothing left in FSM */
621
buffer = ReadBuffer(r, blkno);
624
* We have to guard against the possibility that someone else already
625
* recycled this page; the buffer may be locked if so.
627
if (ConditionalLockBuffer(buffer))
629
Page page = BufferGetPage(buffer);
632
return buffer; /* OK to use, if never initialized */
634
gistcheckpage(r, buffer);
636
if (GistPageIsDeleted(page))
637
return buffer; /* OK to use */
639
LockBuffer(buffer, GIST_UNLOCK);
642
/* Can't use it, so release buffer and try again */
643
ReleaseBuffer(buffer);
646
/* Must extend the file */
647
needLock = !RELATION_IS_LOCAL(r);
650
LockRelationForExtension(r, ExclusiveLock);
652
buffer = ReadBuffer(r, P_NEW);
653
LockBuffer(buffer, GIST_EXCLUSIVE);
656
UnlockRelationForExtension(r, ExclusiveLock);
662
gistoptions(PG_FUNCTION_ARGS)
664
Datum reloptions = PG_GETARG_DATUM(0);
665
bool validate = PG_GETARG_BOOL(1);
668
result = default_reloptions(reloptions, validate, RELOPT_KIND_GIST);
671
PG_RETURN_BYTEA_P(result);
676
* Temporary GiST indexes are not WAL-logged, but we need LSNs to detect
677
* concurrent page splits anyway. GetXLogRecPtrForTemp() provides a fake
678
* sequence of LSNs for that purpose. Each call generates an LSN that is
679
* greater than any previous value returned by this function in the same
683
GetXLogRecPtrForTemp(void)
685
static XLogRecPtr counter = {0, 1};
688
if (counter.xrecoff == 0)