1 /*-------------------------------------------------------------------------
4 * utilities routines for the postgres GiST index access method.
7 * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
8 * Portions Copyright (c) 1994, Regents of the University of California
11 * src/backend/access/gist/gistutil.c
12 *-------------------------------------------------------------------------
18 #include "access/gist_private.h"
19 #include "access/htup_details.h"
20 #include "access/reloptions.h"
21 #include "catalog/pg_opclass.h"
22 #include "storage/indexfsm.h"
23 #include "storage/lmgr.h"
24 #include "utils/float.h"
25 #include "utils/syscache.h"
26 #include "utils/snapmgr.h"
27 #include "utils/lsyscache.h"
31 * Write itup vector to page, has no control of free space.
34 gistfillbuffer(Page page, IndexTuple *itup, int len, OffsetNumber off)
36 OffsetNumber l = InvalidOffsetNumber;
39 if (off == InvalidOffsetNumber)
40 off = (PageIsEmpty(page)) ? FirstOffsetNumber :
41 OffsetNumberNext(PageGetMaxOffsetNumber(page));
43 for (i = 0; i < len; i++)
45 Size sz = IndexTupleSize(itup[i]);
47 l = PageAddItem(page, (Item) itup[i], sz, off, false, false);
48 if (l == InvalidOffsetNumber)
49 elog(ERROR, "failed to add item to GiST index page, item %d out of %d, size %d bytes",
56 * Check space for itup vector on page
59 gistnospace(Page page, IndexTuple *itvec, int len, OffsetNumber todelete, Size freespace)
61 unsigned int size = freespace,
65 for (i = 0; i < len; i++)
66 size += IndexTupleSize(itvec[i]) + sizeof(ItemIdData);
68 if (todelete != InvalidOffsetNumber)
70 IndexTuple itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, todelete));
72 deleted = IndexTupleSize(itup) + sizeof(ItemIdData);
75 return (PageGetFreeSpace(page) + deleted < size);
79 gistfitpage(IndexTuple *itvec, int len)
84 for (i = 0; i < len; i++)
85 size += IndexTupleSize(itvec[i]) + sizeof(ItemIdData);
87 /* TODO: Consider fillfactor */
88 return (size <= GiSTPageSize);
92 * Read buffer into itup vector
95 gistextractpage(Page page, int *len /* out */ )
101 maxoff = PageGetMaxOffsetNumber(page);
103 itvec = palloc(sizeof(IndexTuple) * maxoff);
104 for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
105 itvec[i - FirstOffsetNumber] = (IndexTuple) PageGetItem(page, PageGetItemId(page, i));
111 * join two vectors into one
114 gistjoinvector(IndexTuple *itvec, int *len, IndexTuple *additvec, int addlen)
116 itvec = (IndexTuple *) repalloc((void *) itvec, sizeof(IndexTuple) * ((*len) + addlen));
117 memmove(&itvec[*len], additvec, sizeof(IndexTuple) * addlen);
123 * make plain IndexTuple vector
127 gistfillitupvec(IndexTuple *vec, int veclen, int *memlen)
135 for (i = 0; i < veclen; i++)
136 *memlen += IndexTupleSize(vec[i]);
138 ptr = ret = palloc(*memlen);
140 for (i = 0; i < veclen; i++)
142 memcpy(ptr, vec[i], IndexTupleSize(vec[i]));
143 ptr += IndexTupleSize(vec[i]);
146 return (IndexTupleData *) ret;
150 * Make unions of keys in IndexTuple vector (one union datum per index column).
151 * Union Datums are returned into the attr/isnull arrays.
152 * Resulting Datums aren't compressed.
155 gistMakeUnionItVec(GISTSTATE *giststate, IndexTuple *itvec, int len,
156 Datum *attr, bool *isnull)
159 GistEntryVector *evec;
162 evec = (GistEntryVector *) palloc((len + 2) * sizeof(GISTENTRY) + GEVHDRSZ);
164 for (i = 0; i < giststate->nonLeafTupdesc->natts; i++)
168 /* Collect non-null datums for this column */
170 for (j = 0; j < len; j++)
175 datum = index_getattr(itvec[j], i + 1, giststate->leafTupdesc,
180 gistdentryinit(giststate, i,
181 evec->vector + evec->n,
183 NULL, NULL, (OffsetNumber) 0,
188 /* If this column was all NULLs, the union is NULL */
198 /* unionFn may expect at least two inputs */
200 evec->vector[1] = evec->vector[0];
203 /* Make union and store in attr array */
204 attr[i] = FunctionCall2Coll(&giststate->unionFn[i],
205 giststate->supportCollation[i],
206 PointerGetDatum(evec),
207 PointerGetDatum(&attrsize));
215 * Return an IndexTuple containing the result of applying the "union"
216 * method to the specified IndexTuple vector.
219 gistunion(Relation r, IndexTuple *itvec, int len, GISTSTATE *giststate)
221 Datum attr[INDEX_MAX_KEYS];
222 bool isnull[INDEX_MAX_KEYS];
224 gistMakeUnionItVec(giststate, itvec, len, attr, isnull);
226 return gistFormTuple(giststate, r, attr, isnull, false);
230 * makes union of two key
233 gistMakeUnionKey(GISTSTATE *giststate, int attno,
234 GISTENTRY *entry1, bool isnull1,
235 GISTENTRY *entry2, bool isnull2,
236 Datum *dst, bool *dstisnull)
238 /* we need a GistEntryVector with room for exactly 2 elements */
242 char padding[2 * sizeof(GISTENTRY) + GEVHDRSZ];
244 GistEntryVector *evec = &storage.gev;
249 if (isnull1 && isnull2)
256 if (isnull1 == false && isnull2 == false)
258 evec->vector[0] = *entry1;
259 evec->vector[1] = *entry2;
261 else if (isnull1 == false)
263 evec->vector[0] = *entry1;
264 evec->vector[1] = *entry1;
268 evec->vector[0] = *entry2;
269 evec->vector[1] = *entry2;
273 *dst = FunctionCall2Coll(&giststate->unionFn[attno],
274 giststate->supportCollation[attno],
275 PointerGetDatum(evec),
276 PointerGetDatum(&dstsize));
281 gistKeyIsEQ(GISTSTATE *giststate, int attno, Datum a, Datum b)
285 FunctionCall3Coll(&giststate->equalFn[attno],
286 giststate->supportCollation[attno],
288 PointerGetDatum(&result));
293 * Decompress all keys in tuple
296 gistDeCompressAtt(GISTSTATE *giststate, Relation r, IndexTuple tuple, Page p,
297 OffsetNumber o, GISTENTRY *attdata, bool *isnull)
301 for (i = 0; i < IndexRelationGetNumberOfKeyAttributes(r); i++)
305 datum = index_getattr(tuple, i + 1, giststate->leafTupdesc, &isnull[i]);
306 gistdentryinit(giststate, i, &attdata[i],
313 * Forms union of oldtup and addtup, if union == oldtup then return NULL
316 gistgetadjusted(Relation r, IndexTuple oldtup, IndexTuple addtup, GISTSTATE *giststate)
318 bool neednew = false;
319 GISTENTRY oldentries[INDEX_MAX_KEYS],
320 addentries[INDEX_MAX_KEYS];
321 bool oldisnull[INDEX_MAX_KEYS],
322 addisnull[INDEX_MAX_KEYS];
323 Datum attr[INDEX_MAX_KEYS];
324 bool isnull[INDEX_MAX_KEYS];
325 IndexTuple newtup = NULL;
328 gistDeCompressAtt(giststate, r, oldtup, NULL,
329 (OffsetNumber) 0, oldentries, oldisnull);
331 gistDeCompressAtt(giststate, r, addtup, NULL,
332 (OffsetNumber) 0, addentries, addisnull);
334 for (i = 0; i < IndexRelationGetNumberOfKeyAttributes(r); i++)
336 gistMakeUnionKey(giststate, i,
337 oldentries + i, oldisnull[i],
338 addentries + i, addisnull[i],
339 attr + i, isnull + i);
342 /* we already need new key, so we can skip check */
346 /* union of key may be NULL if and only if both keys are NULL */
352 !gistKeyIsEQ(giststate, i, oldentries[i].key, attr[i]))
359 /* need to update key */
360 newtup = gistFormTuple(giststate, r, attr, isnull, false);
361 newtup->t_tid = oldtup->t_tid;
368 * Search an upper index page for the entry with lowest penalty for insertion
369 * of the new index key contained in "it".
371 * Returns the index of the page entry to insert into.
374 gistchoose(Relation r, Page p, IndexTuple it, /* it has compressed entry */
375 GISTSTATE *giststate)
380 float best_penalty[INDEX_MAX_KEYS];
382 identry[INDEX_MAX_KEYS];
383 bool isnull[INDEX_MAX_KEYS];
384 int keep_current_best;
386 Assert(!GistPageIsLeaf(p));
388 gistDeCompressAtt(giststate, r,
389 it, NULL, (OffsetNumber) 0,
392 /* we'll return FirstOffsetNumber if page is empty (shouldn't happen) */
393 result = FirstOffsetNumber;
396 * The index may have multiple columns, and there's a penalty value for
397 * each column. The penalty associated with a column that appears earlier
398 * in the index definition is strictly more important than the penalty of
399 * a column that appears later in the index definition.
401 * best_penalty[j] is the best penalty we have seen so far for column j,
402 * or -1 when we haven't yet examined column j. Array entries to the
403 * right of the first -1 are undefined.
405 best_penalty[0] = -1;
408 * If we find a tuple that's exactly as good as the currently best one, we
409 * could use either one. When inserting a lot of tuples with the same or
410 * similar keys, it's preferable to descend down the same path when
411 * possible, as that's more cache-friendly. On the other hand, if all
412 * inserts land on the same leaf page after a split, we're never going to
413 * insert anything to the other half of the split, and will end up using
414 * only 50% of the available space. Distributing the inserts evenly would
415 * lead to better space usage, but that hurts cache-locality during
416 * insertion. To get the best of both worlds, when we find a tuple that's
417 * exactly as good as the previous best, choose randomly whether to stick
418 * to the old best, or use the new one. Once we decide to stick to the
419 * old best, we keep sticking to it for any subsequent equally good tuples
420 * we might find. This favors tuples with low offsets, but still allows
421 * some inserts to go to other equally-good subtrees.
423 * keep_current_best is -1 if we haven't yet had to make a random choice
424 * whether to keep the current best tuple. If we have done so, and
425 * decided to keep it, keep_current_best is 1; if we've decided to
426 * replace, keep_current_best is 0. (This state will be reset to -1 as
427 * soon as we've made the replacement, but sometimes we make the choice in
428 * advance of actually finding a replacement best tuple.)
430 keep_current_best = -1;
433 * Loop over tuples on page.
435 maxoff = PageGetMaxOffsetNumber(p);
436 Assert(maxoff >= FirstOffsetNumber);
438 for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
440 IndexTuple itup = (IndexTuple) PageGetItem(p, PageGetItemId(p, i));
446 /* Loop over index attributes. */
447 for (j = 0; j < IndexRelationGetNumberOfKeyAttributes(r); j++)
453 /* Compute penalty for this column. */
454 datum = index_getattr(itup, j + 1, giststate->leafTupdesc,
456 gistdentryinit(giststate, j, &entry, datum, r, p, i,
458 usize = gistpenalty(giststate, j, &entry, IsNull,
459 &identry[j], isnull[j]);
461 zero_penalty = false;
463 if (best_penalty[j] < 0 || usize < best_penalty[j])
466 * New best penalty for column. Tentatively select this tuple
467 * as the target, and record the best penalty. Then reset the
468 * next column's penalty to "unknown" (and indirectly, the
469 * same for all the ones to its right). This will force us to
470 * adopt this tuple's penalty values as the best for all the
471 * remaining columns during subsequent loop iterations.
474 best_penalty[j] = usize;
476 if (j < IndexRelationGetNumberOfKeyAttributes(r) - 1)
477 best_penalty[j + 1] = -1;
479 /* we have new best, so reset keep-it decision */
480 keep_current_best = -1;
482 else if (best_penalty[j] == usize)
485 * The current tuple is exactly as good for this column as the
486 * best tuple seen so far. The next iteration of this loop
487 * will compare the next column.
493 * The current tuple is worse for this column than the best
494 * tuple seen so far. Skip the remaining columns and move on
495 * to the next tuple, if any.
497 zero_penalty = false; /* so outer loop won't exit */
503 * If we looped past the last column, and did not update "result",
504 * then this tuple is exactly as good as the prior best tuple.
506 if (j == IndexRelationGetNumberOfKeyAttributes(r) && result != i)
508 if (keep_current_best == -1)
510 /* we didn't make the random choice yet for this old best */
511 keep_current_best = (random() <= (MAX_RANDOM_VALUE / 2)) ? 1 : 0;
513 if (keep_current_best == 0)
515 /* we choose to use the new tuple */
517 /* choose again if there are even more exactly-as-good ones */
518 keep_current_best = -1;
523 * If we find a tuple with zero penalty for all columns, and we've
524 * decided we don't want to search for another tuple with equal
525 * penalty, there's no need to examine remaining tuples; just break
526 * out of the loop and return it.
530 if (keep_current_best == -1)
532 /* we didn't make the random choice yet for this old best */
533 keep_current_best = (random() <= (MAX_RANDOM_VALUE / 2)) ? 1 : 0;
535 if (keep_current_best == 1)
544 * initialize a GiST entry with a decompressed version of key
547 gistdentryinit(GISTSTATE *giststate, int nkey, GISTENTRY *e,
548 Datum k, Relation r, Page pg, OffsetNumber o,
555 gistentryinit(*e, k, r, pg, o, l);
557 /* there may not be a decompress function in opclass */
558 if (!OidIsValid(giststate->decompressFn[nkey].fn_oid))
562 DatumGetPointer(FunctionCall1Coll(&giststate->decompressFn[nkey],
563 giststate->supportCollation[nkey],
564 PointerGetDatum(e)));
565 /* decompressFn may just return the given pointer */
567 gistentryinit(*e, dep->key, dep->rel, dep->page, dep->offset,
571 gistentryinit(*e, (Datum) 0, r, pg, o, l);
575 gistFormTuple(GISTSTATE *giststate, Relation r,
576 Datum attdata[], bool isnull[], bool isleaf)
578 Datum compatt[INDEX_MAX_KEYS];
583 * Call the compress method on each attribute.
585 for (i = 0; i < IndexRelationGetNumberOfKeyAttributes(r); i++)
588 compatt[i] = (Datum) 0;
594 gistentryinit(centry, attdata[i], r, NULL, (OffsetNumber) 0,
596 /* there may not be a compress function in opclass */
597 if (OidIsValid(giststate->compressFn[i].fn_oid))
599 DatumGetPointer(FunctionCall1Coll(&giststate->compressFn[i],
600 giststate->supportCollation[i],
601 PointerGetDatum(¢ry)));
604 compatt[i] = cep->key;
611 * Emplace each included attribute if any.
613 for (; i < r->rd_att->natts; i++)
616 compatt[i] = (Datum) 0;
618 compatt[i] = attdata[i];
622 res = index_form_tuple(isleaf ? giststate->leafTupdesc :
623 giststate->nonLeafTupdesc,
627 * The offset number on tuples on internal pages is unused. For historical
628 * reasons, it is set to 0xffff.
630 ItemPointerSetOffsetNumber(&(res->t_tid), 0xffff);
635 * initialize a GiST entry with fetched value in key field
638 gistFetchAtt(GISTSTATE *giststate, int nkey, Datum k, Relation r)
643 gistentryinit(fentry, k, r, NULL, (OffsetNumber) 0, false);
646 DatumGetPointer(FunctionCall1Coll(&giststate->fetchFn[nkey],
647 giststate->supportCollation[nkey],
648 PointerGetDatum(&fentry)));
650 /* fetchFn set 'key', return it to the caller */
655 * Fetch all keys in tuple.
656 * Returns a new HeapTuple containing the originally-indexed data.
659 gistFetchTuple(GISTSTATE *giststate, Relation r, IndexTuple tuple)
661 MemoryContext oldcxt = MemoryContextSwitchTo(giststate->tempCxt);
662 Datum fetchatt[INDEX_MAX_KEYS];
663 bool isnull[INDEX_MAX_KEYS];
666 for (i = 0; i < IndexRelationGetNumberOfKeyAttributes(r); i++)
670 datum = index_getattr(tuple, i + 1, giststate->leafTupdesc, &isnull[i]);
672 if (giststate->fetchFn[i].fn_oid != InvalidOid)
675 fetchatt[i] = gistFetchAtt(giststate, i, datum, r);
677 fetchatt[i] = (Datum) 0;
679 else if (giststate->compressFn[i].fn_oid == InvalidOid)
682 * If opclass does not provide compress method that could change
683 * original value, att is necessarily stored in original form.
688 fetchatt[i] = (Datum) 0;
693 * Index-only scans not supported for this column. Since the
694 * planner chose an index-only scan anyway, it is not interested
695 * in this column, and we can replace it with a NULL.
698 fetchatt[i] = (Datum) 0;
703 * Get each included attribute.
705 for (; i < r->rd_att->natts; i++)
707 fetchatt[i] = index_getattr(tuple, i + 1, giststate->leafTupdesc,
710 MemoryContextSwitchTo(oldcxt);
712 return heap_form_tuple(giststate->fetchTupdesc, fetchatt, isnull);
716 gistpenalty(GISTSTATE *giststate, int attno,
717 GISTENTRY *orig, bool isNullOrig,
718 GISTENTRY *add, bool isNullAdd)
722 if (giststate->penaltyFn[attno].fn_strict == false ||
723 (isNullOrig == false && isNullAdd == false))
725 FunctionCall3Coll(&giststate->penaltyFn[attno],
726 giststate->supportCollation[attno],
727 PointerGetDatum(orig),
728 PointerGetDatum(add),
729 PointerGetDatum(&penalty));
730 /* disallow negative or NaN penalty */
731 if (isnan(penalty) || penalty < 0.0)
734 else if (isNullOrig && isNullAdd)
738 /* try to prevent mixing null and non-null values */
739 penalty = get_float4_infinity();
746 * Initialize a new index page
749 GISTInitBuffer(Buffer b, uint32 f)
751 GISTPageOpaque opaque;
755 pageSize = BufferGetPageSize(b);
756 page = BufferGetPage(b);
757 PageInit(page, pageSize, sizeof(GISTPageOpaqueData));
759 opaque = GistPageGetOpaque(page);
760 /* page was already zeroed by PageInit, so this is not needed: */
761 /* memset(&(opaque->nsn), 0, sizeof(GistNSN)); */
762 opaque->rightlink = InvalidBlockNumber;
764 opaque->gist_page_id = GIST_PAGE_ID;
768 * Verify that a freshly-read page looks sane.
771 gistcheckpage(Relation rel, Buffer buf)
773 Page page = BufferGetPage(buf);
776 * ReadBuffer verifies that every newly-read page passes
777 * PageHeaderIsValid, which means it either contains a reasonably sane
778 * page header or is all-zero. We have to defend against the all-zero
783 (errcode(ERRCODE_INDEX_CORRUPTED),
784 errmsg("index \"%s\" contains unexpected zero page at block %u",
785 RelationGetRelationName(rel),
786 BufferGetBlockNumber(buf)),
787 errhint("Please REINDEX it.")));
790 * Additionally check that the special area looks sane.
792 if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GISTPageOpaqueData)))
794 (errcode(ERRCODE_INDEX_CORRUPTED),
795 errmsg("index \"%s\" contains corrupted page at block %u",
796 RelationGetRelationName(rel),
797 BufferGetBlockNumber(buf)),
798 errhint("Please REINDEX it.")));
803 * Allocate a new page (either by recycling, or by extending the index file)
805 * The returned buffer is already pinned and exclusive-locked
807 * Caller is responsible for initializing the page by calling GISTInitBuffer
810 gistNewBuffer(Relation r)
815 /* First, try to get a page from FSM */
818 BlockNumber blkno = GetFreeIndexPage(r);
820 if (blkno == InvalidBlockNumber)
821 break; /* nothing left in FSM */
823 buffer = ReadBuffer(r, blkno);
826 * We have to guard against the possibility that someone else already
827 * recycled this page; the buffer may be locked if so.
829 if (ConditionalLockBuffer(buffer))
831 Page page = BufferGetPage(buffer);
834 * If the page was never initialized, it's OK to use.
839 gistcheckpage(r, buffer);
842 * Otherwise, recycle it if deleted, and too old to have any
843 * processes interested in it.
845 if (gistPageRecyclable(page))
848 * If we are generating WAL for Hot Standby then create a WAL
849 * record that will allow us to conflict with queries running
850 * on standby, in case they have snapshots older than the
853 if (XLogStandbyInfoActive() && RelationNeedsWAL(r))
854 gistXLogPageReuse(r, blkno, GistPageGetDeleteXid(page));
859 LockBuffer(buffer, GIST_UNLOCK);
862 /* Can't use it, so release buffer and try again */
863 ReleaseBuffer(buffer);
866 /* Must extend the file */
867 needLock = !RELATION_IS_LOCAL(r);
870 LockRelationForExtension(r, ExclusiveLock);
872 buffer = ReadBuffer(r, P_NEW);
873 LockBuffer(buffer, GIST_EXCLUSIVE);
876 UnlockRelationForExtension(r, ExclusiveLock);
881 /* Can this page be recycled yet? */
883 gistPageRecyclable(Page page)
887 if (GistPageIsDeleted(page))
890 * The page was deleted, but when? If it was just deleted, a scan
891 * might have seen the downlink to it, and will read the page later.
892 * As long as that can happen, we must keep the deleted page around as
895 * Compare the deletion XID with RecentGlobalXmin. If deleteXid <
896 * RecentGlobalXmin, then no scan that's still in progress could have
897 * seen its downlink, and we can recycle it.
899 FullTransactionId deletexid_full = GistPageGetDeleteXid(page);
900 FullTransactionId recentxmin_full = GetFullRecentGlobalXmin();
902 if (FullTransactionIdPrecedes(deletexid_full, recentxmin_full))
909 gistoptions(Datum reloptions, bool validate)
911 relopt_value *options;
914 static const relopt_parse_elt tab[] = {
915 {"fillfactor", RELOPT_TYPE_INT, offsetof(GiSTOptions, fillfactor)},
916 {"buffering", RELOPT_TYPE_STRING, offsetof(GiSTOptions, bufferingModeOffset)}
919 options = parseRelOptions(reloptions, validate, RELOPT_KIND_GIST,
922 /* if none set, we're done */
926 rdopts = allocateReloptStruct(sizeof(GiSTOptions), options, numoptions);
928 fillRelOptions((void *) rdopts, sizeof(GiSTOptions), options, numoptions,
929 validate, tab, lengthof(tab));
933 return (bytea *) rdopts;
937 * gistproperty() -- Check boolean properties of indexes.
939 * This is optional for most AMs, but is required for GiST because the core
940 * property code doesn't support AMPROP_DISTANCE_ORDERABLE. We also handle
941 * AMPROP_RETURNABLE here to save opening the rel to call gistcanreturn.
944 gistproperty(Oid index_oid, int attno,
945 IndexAMProperty prop, const char *propname,
946 bool *res, bool *isnull)
953 /* Only answer column-level inquiries */
958 * Currently, GiST distance-ordered scans require that there be a distance
959 * function in the opclass with the default types (i.e. the one loaded
960 * into the relcache entry, see initGISTstate). So we assume that if such
961 * a function exists, then there's a reason for it (rather than grubbing
962 * through all the opfamily's operators to find an ordered one).
964 * Essentially the same code can test whether we support returning the
965 * column data, since that's true if the opclass provides a fetch proc.
970 case AMPROP_DISTANCE_ORDERABLE:
971 procno = GIST_DISTANCE_PROC;
973 case AMPROP_RETURNABLE:
974 procno = GIST_FETCH_PROC;
980 /* First we need to know the column's opclass. */
981 opclass = get_index_column_opclass(index_oid, attno);
982 if (!OidIsValid(opclass))
988 /* Now look up the opclass family and input datatype. */
989 if (!get_opclass_opfamily_and_input_type(opclass, &opfamily, &opcintype))
995 /* And now we can check whether the function is provided. */
997 *res = SearchSysCacheExists4(AMPROCNUM,
998 ObjectIdGetDatum(opfamily),
999 ObjectIdGetDatum(opcintype),
1000 ObjectIdGetDatum(opcintype),
1001 Int16GetDatum(procno));
1004 * Special case: even without a fetch function, AMPROP_RETURNABLE is true
1005 * if the opclass has no compress function.
1007 if (prop == AMPROP_RETURNABLE && !*res)
1009 *res = !SearchSysCacheExists4(AMPROCNUM,
1010 ObjectIdGetDatum(opfamily),
1011 ObjectIdGetDatum(opcintype),
1012 ObjectIdGetDatum(opcintype),
1013 Int16GetDatum(GIST_COMPRESS_PROC));
1022 * Temporary and unlogged GiST indexes are not WAL-logged, but we need LSNs
1023 * to detect concurrent page splits anyway. This function provides a fake
1024 * sequence of LSNs for that purpose.
1027 gistGetFakeLSN(Relation rel)
1029 static XLogRecPtr counter = FirstNormalUnloggedLSN;
1031 if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
1034 * Temporary relations are only accessible in our session, so a simple
1035 * backend-local counter will do.
1042 * Unlogged relations are accessible from other backends, and survive
1043 * (clean) restarts. GetFakeLSNForUnloggedRel() handles that for us.
1045 Assert(rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED);
1046 return GetFakeLSNForUnloggedRel();