1 : /*-------------------------------------------------------------------------
2 : *
3 : * gistutil.c
4 : * utilities routines for the postgres GiST index access method.
5 : *
6 : *
7 : * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
8 : * Portions Copyright (c) 1994, Regents of the University of California
9 : *
10 : * IDENTIFICATION
11 : * $PostgreSQL: pgsql/src/backend/access/gist/gistutil.c,v 1.24 2007/09/20 17:56:30 tgl Exp $
12 : *-------------------------------------------------------------------------
13 : */
14 : #include "postgres.h"
15 :
16 : #include "access/gist_private.h"
17 : #include "access/heapam.h"
18 : #include "access/reloptions.h"
19 : #include "storage/freespace.h"
20 :
21 : /*
22 : * static *S used for temrorary storage (saves stack and palloc() call)
23 : */
24 :
25 : static Datum attrS[INDEX_MAX_KEYS];
26 : static bool isnullS[INDEX_MAX_KEYS];
27 :
28 : /*
29 : * Write itup vector to page, has no control of free space
30 : */
31 : OffsetNumber
32 : gistfillbuffer(Relation r, Page page, IndexTuple *itup,
33 : int len, OffsetNumber off)
34 11496 : {
35 11496 : OffsetNumber l = InvalidOffsetNumber;
36 : int i;
37 :
38 11496 : if (off == InvalidOffsetNumber)
39 11492 : off = (PageIsEmpty(page)) ? FirstOffsetNumber :
40 : OffsetNumberNext(PageGetMaxOffsetNumber(page));
41 :
42 23101 : for (i = 0; i < len; i++)
43 : {
44 11605 : l = PageAddItem(page, (Item) itup[i], IndexTupleSize(itup[i]),
45 : off, false, false);
46 11605 : if (l == InvalidOffsetNumber)
47 0 : elog(ERROR, "failed to add item to index page in \"%s\"",
48 : RelationGetRelationName(r));
49 11605 : off++;
50 : }
51 11496 : return l;
52 : }
53 :
54 : /*
55 : * Check space for itup vector on page
56 : */
57 : bool
58 : gistnospace(Page page, IndexTuple *itvec, int len, OffsetNumber todelete, Size freespace)
59 11601 : {
60 11601 : unsigned int size = freespace,
61 11601 : deleted = 0;
62 : int i;
63 :
64 23307 : for (i = 0; i < len; i++)
65 11706 : size += IndexTupleSize(itvec[i]) + sizeof(ItemIdData);
66 :
67 11601 : if (todelete != InvalidOffsetNumber)
68 : {
69 945 : IndexTuple itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, todelete));
70 :
71 945 : deleted = IndexTupleSize(itup) + sizeof(ItemIdData);
72 : }
73 :
74 11601 : return (PageGetFreeSpace(page) + deleted < size);
75 : }
76 :
77 : bool
78 : gistfitpage(IndexTuple *itvec, int len)
79 218 : {
80 : int i;
81 218 : Size size = 0;
82 :
83 15988 : for (i = 0; i < len; i++)
84 15770 : size += IndexTupleSize(itvec[i]) + sizeof(ItemIdData);
85 :
86 : /* TODO: Consider fillfactor */
87 218 : return (size <= GiSTPageSize);
88 : }
89 :
90 : /*
91 : * Read buffer into itup vector
92 : */
93 : IndexTuple *
94 : gistextractpage(Page page, int *len /* out */ )
95 109 : {
96 : OffsetNumber i,
97 : maxoff;
98 : IndexTuple *itvec;
99 :
100 109 : maxoff = PageGetMaxOffsetNumber(page);
101 109 : *len = maxoff;
102 109 : itvec = palloc(sizeof(IndexTuple) * maxoff);
103 15770 : for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
104 15661 : itvec[i - FirstOffsetNumber] = (IndexTuple) PageGetItem(page, PageGetItemId(page, i));
105 :
106 109 : return itvec;
107 : }
108 :
109 : /*
110 : * join two vectors into one
111 : */
112 : IndexTuple *
113 : gistjoinvector(IndexTuple *itvec, int *len, IndexTuple *additvec, int addlen)
114 109 : {
115 109 : itvec = (IndexTuple *) repalloc((void *) itvec, sizeof(IndexTuple) * ((*len) + addlen));
116 109 : memmove(&itvec[*len], additvec, sizeof(IndexTuple) * addlen);
117 109 : *len += addlen;
118 109 : return itvec;
119 : }
120 :
121 : /*
122 : * make plain IndexTupleVector
123 : */
124 :
125 : IndexTupleData *
126 : gistfillitupvec(IndexTuple *vec, int veclen, int *memlen)
127 218 : {
128 : char *ptr,
129 : *ret;
130 : int i;
131 :
132 218 : *memlen = 0;
133 :
134 15988 : for (i = 0; i < veclen; i++)
135 15770 : *memlen += IndexTupleSize(vec[i]);
136 :
137 218 : ptr = ret = palloc(*memlen);
138 :
139 15988 : for (i = 0; i < veclen; i++)
140 : {
141 15770 : memcpy(ptr, vec[i], IndexTupleSize(vec[i]));
142 15770 : ptr += IndexTupleSize(vec[i]);
143 : }
144 :
145 218 : return (IndexTupleData *) ret;
146 : }
147 :
148 : /*
149 : * Make unions of keys in IndexTuple vector, return FALSE if itvec contains
150 : * invalid tuple. Resulting Datums aren't compressed.
151 : */
152 :
153 : bool
154 : gistMakeUnionItVec(GISTSTATE *giststate, IndexTuple *itvec, int len, int startkey,
155 : Datum *attr, bool *isnull)
156 111 : {
157 : int i;
158 : GistEntryVector *evec;
159 : int attrsize;
160 :
161 111 : evec = (GistEntryVector *) palloc((len + 2) * sizeof(GISTENTRY) + GEVHDRSZ);
162 :
163 222 : for (i = startkey; i < giststate->tupdesc->natts; i++)
164 : {
165 : int j;
166 :
167 111 : evec->n = 0;
168 111 : if (!isnull[i])
169 : {
170 0 : gistentryinit(evec->vector[evec->n], attr[i],
171 : NULL, NULL, (OffsetNumber) 0,
172 : FALSE);
173 0 : evec->n++;
174 : }
175 :
176 846 : for (j = 0; j < len; j++)
177 : {
178 : Datum datum;
179 : bool IsNull;
180 :
181 735 : if (GistTupleIsInvalid(itvec[j]))
182 0 : return FALSE; /* signals that union with invalid tuple =>
183 : * result is invalid */
184 :
185 735 : datum = index_getattr(itvec[j], i + 1, giststate->tupdesc, &IsNull);
186 735 : if (IsNull)
187 36 : continue;
188 :
189 699 : gistdentryinit(giststate, i,
190 : evec->vector + evec->n,
191 : datum,
192 : NULL, NULL, (OffsetNumber) 0,
193 : FALSE, IsNull);
194 699 : evec->n++;
195 : }
196 :
197 : /* If this tuple vector was all NULLs, the union is NULL */
198 111 : if (evec->n == 0)
199 : {
200 3 : attr[i] = (Datum) 0;
201 3 : isnull[i] = TRUE;
202 : }
203 : else
204 : {
205 108 : if (evec->n == 1)
206 : {
207 0 : evec->n = 2;
208 0 : evec->vector[1] = evec->vector[0];
209 : }
210 :
211 : /* Make union and store in attr array */
212 108 : attr[i] = FunctionCall2(&giststate->unionFn[i],
213 : PointerGetDatum(evec),
214 : PointerGetDatum(&attrsize));
215 :
216 108 : isnull[i] = FALSE;
217 : }
218 : }
219 :
220 111 : return TRUE;
221 : }
222 :
223 : /*
224 : * Return an IndexTuple containing the result of applying the "union"
225 : * method to the specified IndexTuple vector.
226 : */
227 : IndexTuple
228 : gistunion(Relation r, IndexTuple *itvec, int len, GISTSTATE *giststate)
229 105 : {
230 105 : memset(isnullS, TRUE, sizeof(bool) * giststate->tupdesc->natts);
231 :
232 105 : if (!gistMakeUnionItVec(giststate, itvec, len, 0, attrS, isnullS))
233 0 : return gist_form_invalid_tuple(InvalidBlockNumber);
234 :
235 105 : return gistFormTuple(giststate, r, attrS, isnullS, false);
236 : }
237 :
238 : /*
239 : * makes union of two key
240 : */
241 : void
242 : gistMakeUnionKey(GISTSTATE *giststate, int attno,
243 : GISTENTRY *entry1, bool isnull1,
244 : GISTENTRY *entry2, bool isnull2,
245 : Datum *dst, bool *dstisnull)
246 9973 : {
247 :
248 : int dstsize;
249 :
250 : static char storage[2 * sizeof(GISTENTRY) + GEVHDRSZ];
251 9973 : GistEntryVector *evec = (GistEntryVector *) storage;
252 :
253 9973 : evec->n = 2;
254 :
255 9973 : if (isnull1 && isnull2)
256 : {
257 798 : *dstisnull = TRUE;
258 798 : *dst = (Datum) 0;
259 : }
260 : else
261 : {
262 9175 : if (isnull1 == FALSE && isnull2 == FALSE)
263 : {
264 9175 : evec->vector[0] = *entry1;
265 9175 : evec->vector[1] = *entry2;
266 : }
267 0 : else if (isnull1 == FALSE)
268 : {
269 0 : evec->vector[0] = *entry1;
270 0 : evec->vector[1] = *entry1;
271 : }
272 : else
273 : {
274 0 : evec->vector[0] = *entry2;
275 0 : evec->vector[1] = *entry2;
276 : }
277 :
278 9175 : *dstisnull = FALSE;
279 9175 : *dst = FunctionCall2(&giststate->unionFn[attno],
280 : PointerGetDatum(evec),
281 : PointerGetDatum(&dstsize));
282 : }
283 9973 : }
284 :
285 : bool
286 : gistKeyIsEQ(GISTSTATE *giststate, int attno, Datum a, Datum b)
287 9175 : {
288 : bool result;
289 :
290 9175 : FunctionCall3(&giststate->equalFn[attno],
291 : a, b,
292 : PointerGetDatum(&result));
293 9175 : return result;
294 : }
295 :
296 : /*
297 : * Decompress all keys in tuple
298 : */
299 : void
300 : gistDeCompressAtt(GISTSTATE *giststate, Relation r, IndexTuple tuple, Page p,
301 : OffsetNumber o, GISTENTRY *attdata, bool *isnull)
302 30024 : {
303 : int i;
304 :
305 60048 : for (i = 0; i < r->rd_att->natts; i++)
306 : {
307 30024 : Datum datum = index_getattr(tuple, i + 1, giststate->tupdesc, &isnull[i]);
308 :
309 30024 : gistdentryinit(giststate, i, &attdata[i],
310 : datum, r, p, o,
311 : FALSE, isnull[i]);
312 : }
313 30024 : }
314 :
315 : /*
316 : * Forms union of oldtup and addtup, if union == oldtup then return NULL
317 : */
318 : IndexTuple
319 : gistgetadjusted(Relation r, IndexTuple oldtup, IndexTuple addtup, GISTSTATE *giststate)
320 9973 : {
321 9973 : bool neednew = FALSE;
322 : GISTENTRY oldentries[INDEX_MAX_KEYS],
323 : addentries[INDEX_MAX_KEYS];
324 : bool oldisnull[INDEX_MAX_KEYS],
325 : addisnull[INDEX_MAX_KEYS];
326 9973 : IndexTuple newtup = NULL;
327 : int i;
328 :
329 9973 : if (GistTupleIsInvalid(oldtup) || GistTupleIsInvalid(addtup))
330 0 : return gist_form_invalid_tuple(ItemPointerGetBlockNumber(&(oldtup->t_tid)));
331 :
332 9973 : gistDeCompressAtt(giststate, r, oldtup, NULL,
333 : (OffsetNumber) 0, oldentries, oldisnull);
334 :
335 9973 : gistDeCompressAtt(giststate, r, addtup, NULL,
336 : (OffsetNumber) 0, addentries, addisnull);
337 :
338 19946 : for (i = 0; i < r->rd_att->natts; i++)
339 : {
340 9973 : gistMakeUnionKey(giststate, i,
341 : oldentries + i, oldisnull[i],
342 : addentries + i, addisnull[i],
343 : attrS + i, isnullS + i);
344 :
345 9973 : if (neednew)
346 : /* we already need new key, so we can skip check */
347 0 : continue;
348 :
349 9973 : if (isnullS[i])
350 : /* union of key may be NULL if and only if both keys are NULL */
351 798 : continue;
352 :
353 9175 : if (!addisnull[i])
354 : {
355 9175 : if (oldisnull[i] || gistKeyIsEQ(giststate, i, oldentries[i].key, attrS[i]) == false)
356 840 : neednew = true;
357 : }
358 : }
359 :
360 9973 : if (neednew)
361 : {
362 : /* need to update key */
363 840 : newtup = gistFormTuple(giststate, r, attrS, isnullS, false);
364 840 : newtup->t_tid = oldtup->t_tid;
365 : }
366 :
367 9973 : return newtup;
368 : }
369 :
370 : /*
371 : * find entry with lowest penalty
372 : */
373 : OffsetNumber
374 : gistchoose(Relation r, Page p, IndexTuple it, /* it has compressed entry */
375 : GISTSTATE *giststate)
376 10078 : {
377 : OffsetNumber maxoff;
378 : OffsetNumber i;
379 : OffsetNumber which;
380 : float sum_grow,
381 : which_grow[INDEX_MAX_KEYS];
382 : GISTENTRY entry,
383 : identry[INDEX_MAX_KEYS];
384 : bool isnull[INDEX_MAX_KEYS];
385 :
386 10078 : maxoff = PageGetMaxOffsetNumber(p);
387 10078 : *which_grow = -1.0;
388 10078 : which = InvalidOffsetNumber;
389 10078 : sum_grow = 1;
390 10078 : gistDeCompressAtt(giststate, r,
391 : it, NULL, (OffsetNumber) 0,
392 : identry, isnull);
393 :
394 : Assert(maxoff >= FirstOffsetNumber);
395 : Assert(!GistPageIsLeaf(p));
396 :
397 118727 : for (i = FirstOffsetNumber; i <= maxoff && sum_grow; i = OffsetNumberNext(i))
398 : {
399 : int j;
400 108649 : IndexTuple itup = (IndexTuple) PageGetItem(p, PageGetItemId(p, i));
401 :
402 108649 : if (!GistPageIsLeaf(p) && GistTupleIsInvalid(itup))
403 : {
404 0 : ereport(LOG,
405 : (errmsg("index \"%s\" needs VACUUM or REINDEX to finish crash recovery",
406 : RelationGetRelationName(r))));
407 : continue;
408 : }
409 :
410 108649 : sum_grow = 0;
411 172038 : for (j = 0; j < r->rd_att->natts; j++)
412 : {
413 : Datum datum;
414 : float usize;
415 : bool IsNull;
416 :
417 108649 : datum = index_getattr(itup, j + 1, giststate->tupdesc, &IsNull);
418 108649 : gistdentryinit(giststate, j, &entry, datum, r, p, i,
419 : FALSE, IsNull);
420 108649 : usize = gistpenalty(giststate, j, &entry, IsNull,
421 : &identry[j], isnull[j]);
422 :
423 171729 : if (which_grow[j] < 0 || usize < which_grow[j])
424 : {
425 63080 : which = i;
426 63080 : which_grow[j] = usize;
427 63080 : if (j < r->rd_att->natts - 1 && i == FirstOffsetNumber)
428 0 : which_grow[j + 1] = -1;
429 63080 : sum_grow += which_grow[j];
430 : }
431 45569 : else if (which_grow[j] == usize)
432 309 : sum_grow += usize;
433 : else
434 : {
435 45260 : sum_grow = 1;
436 45260 : break;
437 : }
438 : }
439 : }
440 :
441 10078 : if (which == InvalidOffsetNumber)
442 0 : which = FirstOffsetNumber;
443 :
444 10078 : return which;
445 : }
446 :
447 : /*
448 : * initialize a GiST entry with a decompressed version of key
449 : */
450 : void
451 : gistdentryinit(GISTSTATE *giststate, int nkey, GISTENTRY *e,
452 : Datum k, Relation r, Page pg, OffsetNumber o,
453 : bool l, bool isNull)
454 159241 : {
455 159241 : if (!isNull)
456 : {
457 : GISTENTRY *dep;
458 :
459 147210 : gistentryinit(*e, k, r, pg, o, l);
460 147210 : dep = (GISTENTRY *)
461 : DatumGetPointer(FunctionCall1(&giststate->decompressFn[nkey],
462 : PointerGetDatum(e)));
463 : /* decompressFn may just return the given pointer */
464 147210 : if (dep != e)
465 0 : gistentryinit(*e, dep->key, dep->rel, dep->page, dep->offset,
466 : dep->leafkey);
467 : }
468 : else
469 12031 : gistentryinit(*e, (Datum) 0, r, pg, o, l);
470 159241 : }
471 :
472 :
473 : /*
474 : * initialize a GiST entry with a compressed version of key
475 : */
476 : void
477 : gistcentryinit(GISTSTATE *giststate, int nkey,
478 : GISTENTRY *e, Datum k, Relation r,
479 : Page pg, OffsetNumber o, bool l, bool isNull)
480 10982 : {
481 10982 : if (!isNull)
482 : {
483 : GISTENTRY *cep;
484 :
485 10982 : gistentryinit(*e, k, r, pg, o, l);
486 10982 : cep = (GISTENTRY *)
487 : DatumGetPointer(FunctionCall1(&giststate->compressFn[nkey],
488 : PointerGetDatum(e)));
489 : /* compressFn may just return the given pointer */
490 10982 : if (cep != e)
491 6722 : gistentryinit(*e, cep->key, cep->rel, cep->page, cep->offset,
492 : cep->leafkey);
493 : }
494 : else
495 0 : gistentryinit(*e, (Datum) 0, r, pg, o, l);
496 10982 : }
497 :
498 : IndexTuple
499 : gistFormTuple(GISTSTATE *giststate, Relation r,
500 : Datum attdata[], bool isnull[], bool newValues)
501 11819 : {
502 : GISTENTRY centry[INDEX_MAX_KEYS];
503 : Datum compatt[INDEX_MAX_KEYS];
504 : int i;
505 : IndexTuple res;
506 :
507 23638 : for (i = 0; i < r->rd_att->natts; i++)
508 : {
509 11819 : if (isnull[i])
510 837 : compatt[i] = (Datum) 0;
511 : else
512 : {
513 10982 : gistcentryinit(giststate, i, ¢ry[i], attdata[i],
514 : r, NULL, (OffsetNumber) 0,
515 : newValues,
516 : FALSE);
517 10982 : compatt[i] = centry[i].key;
518 : }
519 : }
520 :
521 11819 : res = index_form_tuple(giststate->tupdesc, compatt, isnull);
522 11819 : GistTupleSetValid(res);
523 11819 : return res;
524 : }
525 :
526 : float
527 : gistpenalty(GISTSTATE *giststate, int attno,
528 : GISTENTRY *orig, bool isNullOrig,
529 : GISTENTRY *add, bool isNullAdd)
530 108649 : {
531 108649 : float penalty = 0.0;
532 :
533 207697 : if (giststate->penaltyFn[attno].fn_strict == FALSE || (isNullOrig == FALSE && isNullAdd == FALSE))
534 99048 : FunctionCall3(&giststate->penaltyFn[attno],
535 : PointerGetDatum(orig),
536 : PointerGetDatum(add),
537 : PointerGetDatum(&penalty));
538 9601 : else if (isNullOrig && isNullAdd)
539 798 : penalty = 0.0;
540 : else
541 8803 : penalty = 1e10; /* try to prevent to mix null and non-null
542 : * value */
543 :
544 108649 : return penalty;
545 : }
546 :
547 : /*
548 : * Initialize a new index page
549 : */
550 : void
551 : GISTInitBuffer(Buffer b, uint32 f)
552 124 : {
553 : GISTPageOpaque opaque;
554 : Page page;
555 : Size pageSize;
556 :
557 124 : pageSize = BufferGetPageSize(b);
558 124 : page = BufferGetPage(b);
559 124 : PageInit(page, pageSize, sizeof(GISTPageOpaqueData));
560 :
561 124 : opaque = GistPageGetOpaque(page);
562 : /* page was already zeroed by PageInit, so this is not needed: */
563 : /* memset(&(opaque->nsn), 0, sizeof(GistNSN)); */
564 124 : opaque->rightlink = InvalidBlockNumber;
565 124 : opaque->flags = f;
566 124 : opaque->gist_page_id = GIST_PAGE_ID;
567 124 : }
568 :
569 : /*
570 : * Verify that a freshly-read page looks sane.
571 : */
572 : void
573 : gistcheckpage(Relation rel, Buffer buf)
574 31332 : {
575 31332 : Page page = BufferGetPage(buf);
576 :
577 : /*
578 : * ReadBuffer verifies that every newly-read page passes
579 : * PageHeaderIsValid, which means it either contains a reasonably sane
580 : * page header or is all-zero. We have to defend against the all-zero
581 : * case, however.
582 : */
583 31332 : if (PageIsNew(page))
584 0 : ereport(ERROR,
585 : (errcode(ERRCODE_INDEX_CORRUPTED),
586 : errmsg("index \"%s\" contains unexpected zero page at block %u",
587 : RelationGetRelationName(rel),
588 : BufferGetBlockNumber(buf)),
589 : errhint("Please REINDEX it.")));
590 :
591 : /*
592 : * Additionally check that the special area looks sane.
593 : */
594 31332 : if (((PageHeader) (page))->pd_special !=
595 : (BLCKSZ - MAXALIGN(sizeof(GISTPageOpaqueData))))
596 0 : ereport(ERROR,
597 : (errcode(ERRCODE_INDEX_CORRUPTED),
598 : errmsg("index \"%s\" contains corrupted page at block %u",
599 : RelationGetRelationName(rel),
600 : BufferGetBlockNumber(buf)),
601 : errhint("Please REINDEX it.")));
602 31332 : }
603 :
604 :
605 : /*
606 : * Allocate a new page (either by recycling, or by extending the index file)
607 : *
608 : * The returned buffer is already pinned and exclusive-locked
609 : *
610 : * Caller is responsible for initializing the page by calling GISTInitBuffer
611 : */
612 : Buffer
613 : gistNewBuffer(Relation r)
614 120 : {
615 : Buffer buffer;
616 : bool needLock;
617 :
618 : /* First, try to get a page from FSM */
619 : for (;;)
620 : {
621 120 : BlockNumber blkno = GetFreeIndexPage(&r->rd_node);
622 :
623 120 : if (blkno == InvalidBlockNumber)
624 120 : break; /* nothing left in FSM */
625 :
626 0 : buffer = ReadBuffer(r, blkno);
627 :
628 : /*
629 : * We have to guard against the possibility that someone else already
630 : * recycled this page; the buffer may be locked if so.
631 : */
632 0 : if (ConditionalLockBuffer(buffer))
633 : {
634 0 : Page page = BufferGetPage(buffer);
635 :
636 0 : if (PageIsNew(page))
637 0 : return buffer; /* OK to use, if never initialized */
638 :
639 0 : gistcheckpage(r, buffer);
640 :
641 0 : if (GistPageIsDeleted(page))
642 0 : return buffer; /* OK to use */
643 :
644 0 : LockBuffer(buffer, GIST_UNLOCK);
645 : }
646 :
647 : /* Can't use it, so release buffer and try again */
648 0 : ReleaseBuffer(buffer);
649 0 : }
650 :
651 : /* Must extend the file */
652 120 : needLock = !RELATION_IS_LOCAL(r);
653 :
654 120 : if (needLock)
655 0 : LockRelationForExtension(r, ExclusiveLock);
656 :
657 120 : buffer = ReadBuffer(r, P_NEW);
658 120 : LockBuffer(buffer, GIST_EXCLUSIVE);
659 :
660 120 : if (needLock)
661 0 : UnlockRelationForExtension(r, ExclusiveLock);
662 :
663 120 : return buffer;
664 : }
665 :
666 : Datum
667 : gistoptions(PG_FUNCTION_ARGS)
668 0 : {
669 0 : Datum reloptions = PG_GETARG_DATUM(0);
670 0 : bool validate = PG_GETARG_BOOL(1);
671 : bytea *result;
672 :
673 0 : result = default_reloptions(reloptions, validate,
674 : GIST_MIN_FILLFACTOR,
675 : GIST_DEFAULT_FILLFACTOR);
676 0 : if (result)
677 0 : PG_RETURN_BYTEA_P(result);
678 0 : PG_RETURN_NULL();
679 : }
|