diff -c -r -N src.orig/backend/access/gist/gistget.c src/backend/access/gist/gistget.c
*** src.orig/backend/access/gist/gistget.c	2010-01-12 14:51:36.000000000 +0300
--- src/backend/access/gist/gistget.c	2010-01-12 20:22:34.000000000 +0300
***************
*** 17,522 ****
  #include "access/gist_private.h"
  #include "access/relscan.h"
  #include "executor/execdebug.h"
  #include "miscadmin.h"
  #include "pgstat.h"
  #include "storage/bufmgr.h"
  #include "utils/memutils.h"
  
! 
! static OffsetNumber gistfindnext(IndexScanDesc scan, OffsetNumber n);
! static int64 gistnext(IndexScanDesc scan, TIDBitmap *tbm);
! static bool gistindex_keytest(IndexTuple tuple, IndexScanDesc scan,
! 				  OffsetNumber offset);
! 
! static void
! killtuple(Relation r, GISTScanOpaque so, ItemPointer iptr)
  {
! 	Page		p;
! 	OffsetNumber offset;
  
! 	LockBuffer(so->curbuf, GIST_SHARE);
! 	gistcheckpage(r, so->curbuf);
! 	p = (Page) BufferGetPage(so->curbuf);
  
! 	if (XLByteEQ(so->stack->lsn, PageGetLSN(p)))
! 	{
! 		/* page unchanged, so all is simple */
! 		offset = ItemPointerGetOffsetNumber(iptr);
! 		ItemIdMarkDead(PageGetItemId(p, offset));
! 		SetBufferCommitInfoNeedsSave(so->curbuf);
! 	}
! 	else
  	{
! 		OffsetNumber maxoff = PageGetMaxOffsetNumber(p);
  
! 		for (offset = FirstOffsetNumber; offset <= maxoff; offset = OffsetNumberNext(offset))
  		{
! 			IndexTuple	ituple = (IndexTuple) PageGetItem(p, PageGetItemId(p, offset));
  
! 			if (ItemPointerEquals(&(ituple->t_tid), iptr))
  			{
! 				/* found */
! 				ItemIdMarkDead(PageGetItemId(p, offset));
! 				SetBufferCommitInfoNeedsSave(so->curbuf);
! 				break;
  			}
  		}
! 	}
! 
! 	LockBuffer(so->curbuf, GIST_UNLOCK);
! }
  
! /*
!  * gistgettuple() -- Get the next tuple in the scan
!  */
! Datum
! gistgettuple(PG_FUNCTION_ARGS)
! {
! 	IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
! 	ScanDirection dir = (ScanDirection) PG_GETARG_INT32(1);
! 	GISTScanOpaque so;
! 	bool		res;
  
! 	so = (GISTScanOpaque) scan->opaque;
  
! 	if (dir != ForwardScanDirection)
! 		elog(ERROR, "GiST doesn't support other scan directions than forward");
  
! 	/*
! 	 * If we have produced an index tuple in the past and the executor has
! 	 * informed us we need to mark it as "killed", do so now.
! 	 */
! 	if (scan->kill_prior_tuple && ItemPointerIsValid(&(so->curpos)))
! 		killtuple(scan->indexRelation, so, &(so->curpos));
  
! 	/*
! 	 * Get the next tuple that matches the search key.
! 	 */
! 	res = (gistnext(scan, NULL) > 0);
  
! 	PG_RETURN_BOOL(res);
! }
  
! Datum
! gistgetbitmap(PG_FUNCTION_ARGS)
! {
! 	IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
! 	TIDBitmap  *tbm = (TIDBitmap *) PG_GETARG_POINTER(1);
! 	int64		ntids;
  
! 	ntids = gistnext(scan, tbm);
  
! 	PG_RETURN_INT64(ntids);
  }
  
  /*
!  * Fetch tuple(s) that match the search key; this can be invoked
!  * either to fetch the first such tuple or subsequent matching tuples.
!  *
!  * This function is used by both gistgettuple and gistgetbitmap. When
!  * invoked from gistgettuple, tbm is null and the next matching tuple
!  * is returned in scan->xs_ctup.t_self.  When invoked from getbitmap,
!  * tbm is non-null and all matching tuples are added to tbm before
!  * returning.  In both cases, the function result is the number of
!  * returned tuples.
!  *
!  * If scan specifies to skip killed tuples, continue looping until we find a
!  * non-killed tuple that matches the search key.
   */
! static int64
! gistnext(IndexScanDesc scan, TIDBitmap *tbm)
  {
! 	Page		p;
! 	OffsetNumber n;
! 	GISTScanOpaque so;
! 	GISTSearchStack *stk;
! 	IndexTuple	it;
! 	GISTPageOpaque opaque;
! 	int64		ntids = 0;
! 
! 	so = (GISTScanOpaque) scan->opaque;
! 
! 	if (so->qual_ok == false)
! 		return 0;
! 
! 	if (so->curbuf == InvalidBuffer)
  	{
! 		if (ItemPointerIsValid(&so->curpos) == false)
! 		{
! 			/* Being asked to fetch the first entry, so start at the root */
! 			Assert(so->curbuf == InvalidBuffer);
! 			Assert(so->stack == NULL);
  
! 			so->curbuf = ReadBuffer(scan->indexRelation, GIST_ROOT_BLKNO);
  
! 			stk = so->stack = (GISTSearchStack *) palloc0(sizeof(GISTSearchStack));
  
! 			stk->next = NULL;
! 			stk->block = GIST_ROOT_BLKNO;
  
! 			pgstat_count_index_scan(scan->indexRelation);
! 		}
! 		else
  		{
! 			/* scan is finished */
! 			return 0;
  		}
  	}
  
  	/*
! 	 * check stored pointers from last visit
  	 */
! 	if (so->nPageData > 0)
  	{
  		/*
! 		 * gistgetmulti never should go here
  		 */
! 		Assert(tbm == NULL);
! 
! 		if (so->curPageData < so->nPageData)
! 		{
! 			scan->xs_ctup.t_self = so->pageData[so->curPageData].heapPtr;
! 			scan->xs_recheck = so->pageData[so->curPageData].recheck;
! 
! 			ItemPointerSet(&so->curpos,
! 						   BufferGetBlockNumber(so->curbuf),
! 						   so->pageData[so->curPageData].pageOffset);
! 
! 			so->curPageData++;
! 
! 			return 1;
! 		}
! 		else
  		{
! 			/*
! 			 * Go to the next page
! 			 */
! 			stk = so->stack->next;
! 			pfree(so->stack);
! 			so->stack = stk;
! 
! 			/* If we're out of stack entries, we're done */
! 			if (so->stack == NULL)
! 			{
! 				ReleaseBuffer(so->curbuf);
! 				so->curbuf = InvalidBuffer;
! 				return 0;
  			}
! 
! 			so->curbuf = ReleaseAndReadBuffer(so->curbuf,
! 											  scan->indexRelation,
! 											  stk->block);
! 		}
! 	}
! 
! 	for (;;)
! 	{
! 		CHECK_FOR_INTERRUPTS();
! 
! 		/* First of all, we need lock buffer */
! 		Assert(so->curbuf != InvalidBuffer);
! 		LockBuffer(so->curbuf, GIST_SHARE);
! 		gistcheckpage(scan->indexRelation, so->curbuf);
! 		p = BufferGetPage(so->curbuf);
! 		opaque = GistPageGetOpaque(p);
! 
! 		/* remember lsn to identify page changed for tuple's killing */
! 		so->stack->lsn = PageGetLSN(p);
! 
! 		/* check page split, occured since visit to parent */
! 		if (!XLogRecPtrIsInvalid(so->stack->parentlsn) &&
! 			XLByteLT(so->stack->parentlsn, opaque->nsn) &&
! 			opaque->rightlink != InvalidBlockNumber /* sanity check */ &&
! 			(so->stack->next == NULL || so->stack->next->block != opaque->rightlink)	/* check if already
! 				added */ )
! 		{
! 			/* detect page split, follow right link to add pages */
! 
! 			stk = (GISTSearchStack *) palloc(sizeof(GISTSearchStack));
! 			stk->next = so->stack->next;
! 			stk->block = opaque->rightlink;
! 			stk->parentlsn = so->stack->parentlsn;
! 			memset(&(stk->lsn), 0, sizeof(GistNSN));
! 			so->stack->next = stk;
! 		}
! 
! 		/* if page is empty, then just skip it */
! 		if (PageIsEmpty(p))
! 		{
! 			LockBuffer(so->curbuf, GIST_UNLOCK);
! 			stk = so->stack->next;
! 			pfree(so->stack);
! 			so->stack = stk;
! 
! 			if (so->stack == NULL)
! 			{
! 				ReleaseBuffer(so->curbuf);
! 				so->curbuf = InvalidBuffer;
! 				return ntids;
  			}
! 
! 			so->curbuf = ReleaseAndReadBuffer(so->curbuf, scan->indexRelation,
! 											  stk->block);
! 			continue;
! 		}
! 
! 		n = FirstOffsetNumber;
! 
! 		/* wonderful, we can look at page */
! 		so->nPageData = so->curPageData = 0;
! 
! 		for (;;)
! 		{
! 			n = gistfindnext(scan, n);
! 
! 			if (!OffsetNumberIsValid(n))
  			{
! 				/*
! 				 * If we was called from gistgettuple and current buffer
! 				 * contains something matched then make a recursive call - it
! 				 * will return ItemPointer from so->pageData. But we save
! 				 * buffer pinned to support tuple's killing
  				 */
! 				if (!tbm && so->nPageData > 0)
! 				{
! 					LockBuffer(so->curbuf, GIST_UNLOCK);
! 					return gistnext(scan, NULL);
! 				}
  
! 				/*
! 				 * We ran out of matching index entries on the current page,
! 				 * so pop the top stack entry and use it to continue the
! 				 * search.
! 				 */
! 				LockBuffer(so->curbuf, GIST_UNLOCK);
! 				stk = so->stack->next;
! 				pfree(so->stack);
! 				so->stack = stk;
  
! 				/* If we're out of stack entries, we're done */
  
! 				if (so->stack == NULL)
  				{
! 					ReleaseBuffer(so->curbuf);
! 					so->curbuf = InvalidBuffer;
! 					return ntids;
  				}
  
! 				so->curbuf = ReleaseAndReadBuffer(so->curbuf,
! 												  scan->indexRelation,
! 												  stk->block);
! 				/* XXX	go up */
! 				break;
! 			}
  
! 			if (GistPageIsLeaf(p))
! 			{
! 				/*
! 				 * We've found a matching index entry in a leaf page, so
! 				 * return success. Note that we keep "curbuf" pinned so that
! 				 * we can efficiently resume the index scan later.
! 				 */
  
! 				if (!(scan->ignore_killed_tuples &&
! 					  ItemIdIsDead(PageGetItemId(p, n))))
  				{
! 					it = (IndexTuple) PageGetItem(p, PageGetItemId(p, n));
! 					ntids++;
! 					if (tbm != NULL)
! 						tbm_add_tuples(tbm, &it->t_tid, 1, scan->xs_recheck);
! 					else
! 					{
! 						so->pageData[so->nPageData].heapPtr = it->t_tid;
! 						so->pageData[so->nPageData].pageOffset = n;
! 						so->pageData[so->nPageData].recheck = scan->xs_recheck;
! 						so->nPageData++;
! 					}
  				}
  			}
! 			else
! 			{
! 				/*
! 				 * We've found an entry in an internal node whose key is
! 				 * consistent with the search key, so push it to stack
! 				 */
! 				stk = (GISTSearchStack *) palloc(sizeof(GISTSearchStack));
  
! 				it = (IndexTuple) PageGetItem(p, PageGetItemId(p, n));
! 				stk->block = ItemPointerGetBlockNumber(&(it->t_tid));
! 				memset(&(stk->lsn), 0, sizeof(GistNSN));
! 				stk->parentlsn = so->stack->lsn;
  
! 				stk->next = so->stack->next;
! 				so->stack->next = stk;
! 			}
  
! 			n = OffsetNumberNext(n);
  		}
  	}
  
! 	return ntids;
  }
  
  /*
!  * gistindex_keytest() -- does this index tuple satisfy the scan key(s)?
!  *
!  * On success return for a leaf tuple, scan->xs_recheck is set to indicate
!  * whether recheck is needed.  We recheck if any of the consistent() functions
!  * request it.
!  *
!  * We must decompress the key in the IndexTuple before passing it to the
!  * sk_func (and we have previously overwritten the sk_func to use the
!  * user-defined Consistent method, so we actually are invoking that).
!  *
!  * Note that this function is always invoked in a short-lived memory context,
!  * so we don't need to worry about cleaning up allocated memory, either here
!  * or in the implementation of any Consistent methods.
   */
  static bool
! gistindex_keytest(IndexTuple tuple,
! 				  IndexScanDesc scan,
! 				  OffsetNumber offset)
  {
! 	int			keySize = scan->numberOfKeys;
! 	ScanKey		key = scan->keyData;
! 	Relation	r = scan->indexRelation;
! 	GISTScanOpaque so;
! 	Page		p;
! 	GISTSTATE  *giststate;
! 
! 	so = (GISTScanOpaque) scan->opaque;
! 	giststate = so->giststate;
! 	p = BufferGetPage(so->curbuf);
! 
! 	scan->xs_recheck = false;
  
! 	/*
! 	 * Tuple doesn't restore after crash recovery because of incomplete insert
! 	 */
! 	if (!GistPageIsLeaf(p) && GistTupleIsInvalid(tuple))
! 		return true;
! 
! 	while (keySize > 0)
  	{
! 		Datum		datum;
! 		bool		isNull;
! 		Datum		test;
! 		bool		recheck;
! 		GISTENTRY	de;
  
! 		datum = index_getattr(tuple,
! 							  key->sk_attno,
! 							  giststate->tupdesc,
! 							  &isNull);
  
! 		if (key->sk_flags & SK_ISNULL)
! 		{
! 			/*
! 			 * On non-leaf page we can't conclude that child hasn't NULL
! 			 * values because of assumption in GiST: union (VAL, NULL) is VAL.
! 			 * But if on non-leaf page key IS NULL, then all children are NULL.
! 			 */
! 			if (key->sk_flags & SK_SEARCHNULL)
! 			{
! 				if (GistPageIsLeaf(p) && !isNull)
! 					return false;
! 			}
! 			else
! 			{
! 				Assert(key->sk_flags & SK_SEARCHNOTNULL);
! 				if (isNull)
! 					return false;
! 			}
! 		}
! 		else if (isNull)
  		{
! 			return false;
  		}
  		else
  		{
! 			gistdentryinit(giststate, key->sk_attno - 1, &de,
! 						   datum, r, p, offset,
! 						   FALSE, isNull);
! 
! 			/*
! 			 * Call the Consistent function to evaluate the test.  The
! 			 * arguments are the index datum (as a GISTENTRY*), the comparison
! 			 * datum, the comparison operator's strategy number and subtype
! 			 * from pg_amop, and the recheck flag.
! 			 *
! 			 * (Presently there's no need to pass the subtype since it'll
! 			 * always be zero, but might as well pass it for possible future
! 			 * use.)
! 			 *
! 			 * We initialize the recheck flag to true (the safest assumption)
! 			 * in case the Consistent function forgets to set it.
! 			 */
! 			recheck = true;
  
! 			test = FunctionCall5(&key->sk_func,
! 								 PointerGetDatum(&de),
! 								 key->sk_argument,
! 								 Int32GetDatum(key->sk_strategy),
! 								 ObjectIdGetDatum(key->sk_subtype),
! 								 PointerGetDatum(&recheck));
! 
! 			if (!DatumGetBool(test))
! 				return false;
! 			scan->xs_recheck |= recheck;
  		}
  
! 		keySize--;
! 		key++;
! 	}
  
! 	return true;
  }
  
  /*
!  * Return the offset of the first index entry that is consistent with
!  * the search key after offset 'n' in the current page. If there are
!  * no more consistent entries, return InvalidOffsetNumber.
!  * On success, scan->xs_recheck is set correctly, too.
!  * Page should be locked....
   */
! static OffsetNumber
! gistfindnext(IndexScanDesc scan, OffsetNumber n)
  {
! 	OffsetNumber maxoff;
! 	IndexTuple	it;
! 	GISTScanOpaque so;
! 	MemoryContext oldcxt;
! 	Page		p;
! 
! 	so = (GISTScanOpaque) scan->opaque;
! 	p = BufferGetPage(so->curbuf);
! 	maxoff = PageGetMaxOffsetNumber(p);
  
! 	/*
! 	 * Make sure we're in a short-lived memory context when we invoke a
! 	 * user-supplied GiST method in gistindex_keytest(), so we don't leak
! 	 * memory
! 	 */
! 	oldcxt = MemoryContextSwitchTo(so->tempCxt);
  
! 	while (n >= FirstOffsetNumber && n <= maxoff)
  	{
! 		it = (IndexTuple) PageGetItem(p, PageGetItemId(p, n));
! 		if (gistindex_keytest(it, scan, n))
! 			break;
  
! 		n = OffsetNumberNext(n);
! 	}
  
! 	MemoryContextSwitchTo(oldcxt);
! 	MemoryContextReset(so->tempCxt);
  
! 	/*
! 	 * If we found a matching entry, return its offset; otherwise return
! 	 * InvalidOffsetNumber to inform the caller to go to the next page.
! 	 */
! 	if (n >= FirstOffsetNumber && n <= maxoff)
! 		return n;
  	else
! 		return InvalidOffsetNumber;
  }
--- 17,478 ----
  #include "access/gist_private.h"
  #include "access/relscan.h"
  #include "executor/execdebug.h"
+ #include "catalog/pg_type.h"
  #include "miscadmin.h"
  #include "pgstat.h"
  #include "storage/bufmgr.h"
  #include "utils/memutils.h"
+ #include "utils/builtins.h"
  
! /*
!  * check item tuple by consistent fucntions 
!  */
! static double* 
! processIndexTuple(IndexScanDesc scan, Page page, OffsetNumber i, IndexTuple it, bool *recheck)
  {
! 	GISTScanOpaque		so = (GISTScanOpaque) scan->opaque;
! 	MemoryContext		oldcxt;
! 	int					keySize = scan->numberOfKeys;
! 	ScanKey				key = scan->keyData;
! 
! 	*recheck = false;
! 	memset(so->distances, 0, sizeof(double) * scan->numberOfKeys);
! 
! 	/* 
! 	 * tuple doesn't restore after crash, put it in the begining.
! 	 * Underlying tuple will be ordered by right way
! 	 */
! 	if (!GistPageIsLeaf(page) && GistTupleIsInvalid(it))
! 		return so->distances;
  
! 	/*
! 	 * Count distance, user defined functions are called in short-lived memory context
! 	 */
  
! 	oldcxt = MemoryContextSwitchTo(so->tempCxt);
! 
! 	while (keySize > 0)
  	{
! 		double		keyDistance = 0.0;
! 		Datum		datum;
! 		bool		isNull;
! 		GISTENTRY	de;
  
! 		datum = index_getattr(it,
! 							  key->sk_attno,
! 							  so->giststate->tupdesc,
! 							  &isNull);
! 
! 		if (key->sk_flags & SK_ISNULL)
  		{
! 			if (so->operatorRetType[ key - scan->keyData ] == BOOLOID)
! 			{
! 				/*
! 				 * Ordinary operator, not a KNN-search.
! 				 * On non-leaf page we can't conclude that child hasn't NULL
! 				 * values because of assumption in GiST: union (VAL, NULL) is VAL
! 				 * But if on non-leaf page key IS  NULL then all childs has NULL.
! 				 */
  
! 				if (key->sk_flags & SK_SEARCHNULL)
! 				{
! 					if (GistPageIsLeaf(page) && !isNull)
! 						keyDistance = -1.0;
! 				}
! 				else
! 				{
! 					Assert(key->sk_flags & SK_SEARCHNOTNULL);
! 					if (isNull)
! 						keyDistance = -1.0;
! 				}
! 			}
! 			else if (GistPageIsLeaf(page))
  			{
! 				/* knn-operator, NULL are last  */
! 				Assert( so->isKNNsearch == true );
! 				keyDistance = (isNull) ? 0.0 : get_float8_infinity();
  			}
  		}
! 		else if (isNull)
! 		{
! 			if (so->operatorRetType[ key - scan->keyData ] == BOOLOID)
! 			{
! 				/* ordinary operator */
! 				keyDistance = -1.0;
! 			}
! 			else
! 			{
! 				/* knn-operator, NULL are last */
! 				keyDistance = get_float8_infinity();
! 			}
! 		}
! 		else
! 		{
! 			bool	keyRecheck = true;
! 			Datum	consistentResult;
  
! 			gistdentryinit(so->giststate, key->sk_attno - 1, &de,
! 							datum, scan->indexRelation, page, i,
! 							FALSE, isNull);
! 
! 			/*--------------
! 			 *  Return value for user-defined KNNGiST-style consistent method:
! 			 *  - negative value means that tuple doesn't match by query
! 			 *    (it's the same as false for GiST's consistent)
! 			 *  - 0.0 means one of:
! 			 *    - just a zero distance (exact match)
! 			 *    - just a match (for filtering clauses like a <@ or @> for point)
! 			 *    KNNGist doesn't distinguish filtering clauses and ordering clauses in this 
! 			 *    case, so it believes in user-defined methods
! 			 *  - positive means distance. In this case keyRecheck should be false!
! 			 *    because it's impossible to make right order with values which
! 			 *    shoould be rechecked.
! 			 */
  
! 			consistentResult = FunctionCall5(&key->sk_func,
! 										PointerGetDatum(&de),
! 										key->sk_argument,
! 										Int32GetDatum(key->sk_strategy),
! 										ObjectIdGetDatum(key->sk_subtype),
! 										PointerGetDatum(&keyRecheck));
  
! 			if ( so->consistentRetType[ key->sk_attno - 1 ] == FLOAT8OID )
! 			{
! 				/* KNNGiST - style consistentFn */
! 				keyDistance = DatumGetFloat8(consistentResult);
  
! 				if (keyDistance > 0.0 && keyRecheck == true)
! 					elog(ERROR, "Could not recheck result for distance ordering");
! 			}
! 			else
! 			{
! 				/* GiST - style consistentFn */
! 				if ( DatumGetBool(consistentResult) == false )
! 					keyDistance = -1.0;
! 			}
  
! 			*recheck |= keyRecheck;
! 		}
  
! 		/*
! 		 * negative distance points that tuple isn't matched by query
! 		 */
! 		if (keyDistance < 0)
! 		{
! 			so->distances[0] = -1.0;
! 			break;
! 		}
  
! 		if (is_infinite(keyDistance) != 0)
! 			so->distances[ key - scan->keyData ] = get_float8_infinity();
! 		else if (is_infinite(so->distances[ key->sk_attno - 1 ]) == 0)
! 			so->distances[ key - scan->keyData ] += keyDistance;
  
! 		keySize--;
! 		key++;
! 	}
  
! 	MemoryContextSwitchTo(oldcxt);
! 	MemoryContextReset(so->tempCxt);
! 	
! 	return (so->distances[0] >= 0.0) ? so->distances : NULL;
  }
  
  /*
!  * Put all items in page to stack in ordered manner, if it's 
!  * needed follow right link and process it too
   */
! static void
! processPage(IndexScanDesc scan, DataPointer *data, double *myDistances, TIDBitmap *tbm, int64 *ntids)
  {
! 	GISTScanOpaque		so = (GISTScanOpaque) scan->opaque;
! 	Buffer				buffer;
! 	Page				page;
! 	GISTPageOpaque		opaque;
! 	OffsetNumber		i;
! 	StackElem		   *elem = NULL;
! 
! 	Assert(data->blkno != InvalidBlockNumber);
! 
! 	buffer = ReadBuffer(scan->indexRelation, data->blkno);
! 	LockBuffer(buffer, GIST_SHARE);
! 	gistcheckpage(scan->indexRelation, buffer);
! 	page = BufferGetPage(buffer);
! 	opaque = GistPageGetOpaque(page);
! 
! 	/* check page split, occured since visit to parent */
! 	if ( !XLogRecPtrIsInvalid(data->data.parentlsn) &&
! 		 XLByteLT(data->data.parentlsn, opaque->nsn) &&
! 		 opaque->rightlink != InvalidBlockNumber /* sanity check */ )
  	{
! 		/* detect page split, follow right link to add pages */
! 		DataPointer	   *pointer;
! 		MemoryContext	oldcxt;
  
! 		Assert(myDistances != NULL); 
! 		oldcxt = MemoryContextSwitchTo(so->stackCxt);
  
! 		pointer = palloc(sizeof(DataPointer));
! 		pointer->next = NULL;
! 		pointer->blkno = opaque->rightlink;
! 		pointer->data.parentlsn = data->data.parentlsn;
  
! 		if (elem == NULL)
! 			elem = palloc(SEHDRSZ + sizeof(double) * scan->numberOfKeys);
  
! 		elem->head = elem->tail = pointer;
! 		memcpy( elem->distances, myDistances, sizeof(double) * scan->numberOfKeys);
! 
! 		if ( rb_insert(so->stack, elem) == NULL )
  		{
! 			elem = NULL; /* elem now in the tree, in opposite case it was appended to
! 						  * one of the previuos value */
  		}
+ 		MemoryContextSwitchTo(oldcxt);
  	}
  
+ 	so->nPageData = so->curPageData = 0;
+ 
  	/*
! 	 * check all tuples on page
  	 */
! 	for(i=FirstOffsetNumber; i<=PageGetMaxOffsetNumber(page); i=OffsetNumberNext(i)) 
  	{
+ 		IndexTuple 	it =	(IndexTuple) PageGetItem(page, PageGetItemId(page, i));
+ 		bool		recheck;
+ 		double	   *distances = processIndexTuple(scan, page, i, it, &recheck);
+ 
  		/*
! 		 * non-null pointer contains non-negative distances
  		 */
! 		if (distances)
  		{
! 			if (tbm && GistPageIsLeaf(page))
! 			{
! 				tbm_add_tuples(tbm, &it->t_tid, 1, recheck);
! 				(*ntids)++;
  			}
! 			else if (so->isKNNsearch == false && GistPageIsLeaf(page))
! 			{
! 				so->pageData[ so->nPageData ].heapPtr = it->t_tid;
! 				so->pageData[ so->nPageData ].recheck = recheck;
! 				so->nPageData++;
  			}
! 			else
  			{
! 				/* 
! 				 * We do a KNNsearch or we need to put inner page to the stack
  				 */
! 				DataPointer	   *pointer;
! 				MemoryContext	oldcxt;
  
! 				oldcxt = MemoryContextSwitchTo(so->stackCxt);
  
! 				pointer = palloc(sizeof(DataPointer));
! 				pointer->next = NULL;
  
! 				if (GistPageIsLeaf(page))
  				{
! 					pointer->blkno = InvalidBlockNumber;
! 					pointer->data.heap.ptr = it->t_tid;
! 					pointer->data.heap.recheck = recheck;
! 				}
! 				else
! 				{
! 					pointer->blkno = ItemPointerGetBlockNumber(&it->t_tid);
! 					/* lsn of current page is a lsn of parent page for child */ 
! 					pointer->data.parentlsn = PageGetLSN(page);
  				}
  
! 				if (elem == NULL)
! 					elem = palloc(SEHDRSZ + sizeof(double) * scan->numberOfKeys);
  
! 				elem->head = elem->tail = pointer;
! 				memcpy( elem->distances, distances, sizeof(double) * scan->numberOfKeys);
  
! 				if ( rb_insert(so->stack, elem) == NULL )
  				{
! 					elem = NULL; /* elem now in the tree, in opposite case it was appended to
! 								  * one of the previuos value */
  				}
+ 				MemoryContextSwitchTo(oldcxt);
  			}
! 		}
! 	}
! 
! 	if (elem)
! 		pfree(elem);
! 	UnlockReleaseBuffer(buffer);
! }
  
! /*
!  * returns next pointer from stack
!  */
! static DataPointer*
! getNextDataPointer(GISTScanOpaque so)
! {
! 	DataPointer		*pointer = NULL;
  
! 	for(;;) 
! 	{
! 		if (so->currentPointer == NULL)
! 		{
! 			so->currentPointer = rb_leftmost(so->stack);
  
! 			if (so->currentPointer == NULL)
! 				break;
  		}
+ 
+ 		pointer = so->currentPointer->head;
+ 		if (pointer == NULL)
+ 		{
+ 			pfree(so->currentPointer);
+ 			so->currentPointer = NULL;
+ 			continue;
+ 		}
+ 
+ 		so->currentPointer->head = pointer->next;
+ 		break;
  	}
  
! 	return pointer; 
  }
  
  /*
!  * returns next nearest heap pointer in for KNNsearch
   */
  static bool
! getNextNearest(IndexScanDesc scan, ItemPointerData *iptr, bool *recheck)
  {
! 	GISTScanOpaque		so = (GISTScanOpaque) scan->opaque;
! 	bool				res = false;
  
! 	do
  	{
! 		DataPointer		*pointer = getNextDataPointer(so);
  
! 		if (!pointer)
! 			break;
  
! 		if (pointer->blkno == InvalidBlockNumber)
  		{
! 			/* found a child on currently minimal distance */
! 			*iptr = pointer->data.heap.ptr;
! 			*recheck = pointer->data.heap.recheck;
! 			res = true;
  		}
  		else
  		{
! 			CHECK_FOR_INTERRUPTS();
  
! 			/* childs in pointer are not closer than current distance */
! 			processPage(scan, pointer, so->currentPointer->distances, NULL, NULL);
  		}
  
! 		pfree(pointer);
! 	} while(res == false);
  
! 	return res;
  }
  
  /*
!  * gistgettuple() -- Get the next tuple in the scan
   */
! Datum
! gistgettuple(PG_FUNCTION_ARGS)
  {
! 	IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
! 	GISTScanOpaque		so = (GISTScanOpaque) scan->opaque;
  
! 	if (so->qual_ok == false)
! 		PG_RETURN_BOOL(false);
  
! 	if (so->firstCall == true)
  	{
! 		DataPointer		fakePtr;
  
! 		/* Being asked to fetch the first entry, so start at the root */
  
! 		so->curPageData = so->nPageData = 0;
! 		so->firstCall = false;
! 		so->currentPointer = NULL;
  
! 		fakePtr.blkno = GIST_ROOT_BLKNO;
! 		memset(&fakePtr.data.parentlsn, 0, sizeof(GistNSN));
! 		processPage(scan, &fakePtr, NULL, NULL, NULL);
! 	}
! 
! 	if (so->isKNNsearch)
! 	{
! 		PG_RETURN_BOOL(getNextNearest(scan, &scan->xs_ctup.t_self, &scan->xs_recheck));
! 	}
  	else
! 	{
! 		for(;;)
! 		{
! 			if ( so->curPageData < so->nPageData )
! 			{
! 				scan->xs_ctup.t_self = so->pageData[ so->curPageData ].heapPtr;
! 				scan->xs_recheck = so->pageData[ so->curPageData ].recheck;
! 				so->curPageData++;
! 				PG_RETURN_BOOL(true);
! 			}
! 
! 			do
! 			{
! 				DataPointer		*pointer = getNextDataPointer(so);
! 
! 				if (!pointer)
! 					PG_RETURN_BOOL(false);
! 
! 				CHECK_FOR_INTERRUPTS();
! 
! 				/* processPage puts on stack only inner pages */
! 				Assert(pointer->blkno != InvalidBlockNumber);
! 				processPage(scan, pointer, so->currentPointer->distances, NULL, NULL);
! 
! 				pfree(pointer);
! 			} while( so->nPageData == 0 );
! 		}
! 	}
! 
! 	PG_RETURN_BOOL(false); /* keep compiler quiet */
! }
! 
! Datum
! gistgetbitmap(PG_FUNCTION_ARGS)
! {
! 	IndexScanDesc	 	scan = (IndexScanDesc) PG_GETARG_POINTER(0);
! 	TIDBitmap		   *tbm = (TIDBitmap *) PG_GETARG_POINTER(1);
! 	GISTScanOpaque		so = (GISTScanOpaque) scan->opaque;
! 	int64				ntids = 0;
! 	DataPointer			fakePtr;
! 
! 	if (so->qual_ok == false)
! 		PG_RETURN_INT64(0);
! 
! 	fakePtr.blkno = GIST_ROOT_BLKNO;
! 	memset(&fakePtr.data.parentlsn, 0, sizeof(GistNSN));
! 	processPage(scan, &fakePtr, NULL, tbm, &ntids);
! 
! 	so->currentPointer = NULL;
! 
! 	for(;;)
! 	{
! 		DataPointer		*pointer = getNextDataPointer(so);
! 
! 		if (!pointer)
! 			break;
! 
! 		CHECK_FOR_INTERRUPTS();
! 
! 		/* processPage puts on stack only inner pages */
! 		Assert(pointer->blkno != InvalidBlockNumber);
! 		processPage(scan, pointer, so->currentPointer->distances, tbm, &ntids);
! 
! 		pfree(pointer);
! 	}
! 
! 	PG_RETURN_INT64(ntids);
  }
+ 
diff -c -r -N src.orig/backend/access/gist/gistscan.c src/backend/access/gist/gistscan.c
*** src.orig/backend/access/gist/gistscan.c	2010-01-12 14:51:36.000000000 +0300
--- src/backend/access/gist/gistscan.c	2010-01-12 19:56:13.000000000 +0300
***************
*** 18,27 ****
  #include "access/gist_private.h"
  #include "access/gistscan.h"
  #include "access/relscan.h"
  #include "storage/bufmgr.h"
  #include "utils/memutils.h"
  
! static void gistfreestack(GISTSearchStack *s);
  
  Datum
  gistbeginscan(PG_FUNCTION_ARGS)
--- 18,68 ----
  #include "access/gist_private.h"
  #include "access/gistscan.h"
  #include "access/relscan.h"
+ #include "catalog/pg_type.h"
  #include "storage/bufmgr.h"
+ #include "utils/lsyscache.h"
  #include "utils/memutils.h"
  
! static int
! StackElemComparator(const void *a, const void *b, void *arg)
! {
! 	StackElem      *sa = (StackElem*)a,
! 				   *sb = (StackElem*)b;
! 	IndexScanDesc	scan = (IndexScanDesc)arg;
! 	int i;
! 
! 	for(i=0; i<scan->numberOfKeys; i++)
! 	{
! 		if (sa->distances[i] != sb->distances[i])
! 			return (sa->distances[i] > sb->distances[i]) ? 1 : -1;
! 	}
! 
! 	return 0;
! }
! 
! static void*
! StackElemAppendator(void *current, void *new, void *arg)
! {
! 	StackElem   *scurrent = (StackElem*)current,
! 	*snew = (StackElem*)new;
! 
! 	/*
! 	 * We would like to keep heap pointer in the begining of list
! 	 * to speedup knn-search
! 	 */
! 	if (snew->head->blkno == InvalidBlockNumber)
! 	{
! 		snew->head->next = scurrent->head;
! 		scurrent->head = snew->head;
! 	}
! 	else
! 	{
! 		scurrent->tail->next = snew->head;
! 		scurrent->tail = snew->head;
! 	}
! 
! 	return current;
! }
  
  Datum
  gistbeginscan(PG_FUNCTION_ARGS)
***************
*** 39,82 ****
  Datum
  gistrescan(PG_FUNCTION_ARGS)
  {
! 	IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
! 	ScanKey		key = (ScanKey) PG_GETARG_POINTER(1);
! 	GISTScanOpaque so;
! 	int			i;
  
  	so = (GISTScanOpaque) scan->opaque;
  	if (so != NULL)
  	{
  		/* rescan an existing indexscan --- reset state */
! 		gistfreestack(so->stack);
! 		so->stack = NULL;
! 		/* drop pins on buffers -- no locks held */
! 		if (BufferIsValid(so->curbuf))
! 		{
! 			ReleaseBuffer(so->curbuf);
! 			so->curbuf = InvalidBuffer;
! 		}
  	}
  	else
  	{
  		/* initialize opaque data */
! 		so = (GISTScanOpaque) palloc(sizeof(GISTScanOpaqueData));
! 		so->stack = NULL;
  		so->tempCxt = createTempGistContext();
! 		so->curbuf = InvalidBuffer;
  		so->giststate = (GISTSTATE *) palloc(sizeof(GISTSTATE));
  		initGISTstate(so->giststate, scan->indexRelation);
  
  		scan->opaque = so;
  	}
  
! 	/*
! 	 * Clear all the pointers.
! 	 */
! 	ItemPointerSetInvalid(&so->curpos);
! 	so->nPageData = so->curPageData = 0;
  
  	so->qual_ok = true;
  
  	/* Update scan key, if a new one is given */
  	if (key && scan->numberOfKeys > 0)
--- 80,127 ----
  Datum
  gistrescan(PG_FUNCTION_ARGS)
  {
! 	IndexScanDesc 	scan = (IndexScanDesc) PG_GETARG_POINTER(0);
! 	ScanKey			key = (ScanKey) PG_GETARG_POINTER(1);
! 	GISTScanOpaque 	so;
! 	int				i;
! 	MemoryContext	oldCxt;
  
  	so = (GISTScanOpaque) scan->opaque;
  	if (so != NULL)
  	{
  		/* rescan an existing indexscan --- reset state */
! 		MemoryContextReset(so->stackCxt);
! 		so->currentPointer = NULL;
  	}
  	else
  	{
  		/* initialize opaque data */
! 		so = (GISTScanOpaque) palloc0(sizeof(GISTScanOpaqueData));
  		so->tempCxt = createTempGistContext();
! 		so->stackCxt = AllocSetContextCreate(CurrentMemoryContext,
! 												"GiST stack context",
! 												ALLOCSET_DEFAULT_MINSIZE,
! 												ALLOCSET_DEFAULT_INITSIZE,
! 												ALLOCSET_DEFAULT_MAXSIZE);
  		so->giststate = (GISTSTATE *) palloc(sizeof(GISTSTATE));
  		initGISTstate(so->giststate, scan->indexRelation);
+ 		so->operatorRetType = palloc(sizeof(Oid) * (scan->numberOfKeys + 1));
+ 		so->distances = palloc(sizeof(double) * (scan->numberOfKeys + 1));
  
  		scan->opaque = so;
  	}
  
! 	oldCxt = MemoryContextSwitchTo(so->stackCxt);
! 	so->stack = rb_create(
! 						StackElemComparator,
! 						StackElemAppendator,
! 						NULL, 
! 						scan);
! 	MemoryContextSwitchTo(oldCxt);
  
+ 	so->firstCall = true;
  	so->qual_ok = true;
+ 	so->isKNNsearch = false;
  
  	/* Update scan key, if a new one is given */
  	if (key && scan->numberOfKeys > 0)
***************
*** 97,106 ****
  		 */
  		for (i = 0; i < scan->numberOfKeys; i++)
  		{
! 			ScanKey		skey = &(scan->keyData[i]);
  
  			skey->sk_func = so->giststate->consistentFn[skey->sk_attno - 1];
  
  			if (skey->sk_flags & SK_ISNULL)
  			{
  				if (!(skey->sk_flags & (SK_SEARCHNULL | SK_SEARCHNOTNULL)))
--- 142,162 ----
  		 */
  		for (i = 0; i < scan->numberOfKeys; i++)
  		{
! 			ScanKey     skey = scan->keyData + i;
! 
! 			/* cache retype of operator to distinguish KNN-search and plain search */
! 			if (skey->sk_flags & SK_ISNULL)
! 				so->operatorRetType[ i ] = BOOLOID; /* is NULL clause */
! 			else
! 				so->operatorRetType[ i ] = get_func_rettype(skey->sk_func.fn_oid);
! 			so->isKNNsearch |= (so->operatorRetType[ i ] != BOOLOID);
  
  			skey->sk_func = so->giststate->consistentFn[skey->sk_attno - 1];
  
+ 			/* cache rettype to have ability to distinguish GiST-style consistentFn */
+ 			so->consistentRetType[skey->sk_attno - 1] =
+ 										get_func_rettype(skey->sk_func.fn_oid);
+ 
  			if (skey->sk_flags & SK_ISNULL)
  			{
  				if (!(skey->sk_flags & (SK_SEARCHNULL | SK_SEARCHNOTNULL)))
***************
*** 136,162 ****
  
  	if (so != NULL)
  	{
! 		gistfreestack(so->stack);
  		if (so->giststate != NULL)
  			freeGISTstate(so->giststate);
! 		/* drop pins on buffers -- we aren't holding any locks */
! 		if (BufferIsValid(so->curbuf))
! 			ReleaseBuffer(so->curbuf);
! 		MemoryContextDelete(so->tempCxt);
  		pfree(scan->opaque);
  	}
  
  	PG_RETURN_VOID();
  }
  
- static void
- gistfreestack(GISTSearchStack *s)
- {
- 	while (s != NULL)
- 	{
- 		GISTSearchStack *p = s->next;
- 
- 		pfree(s);
- 		s = p;
- 	}
- }
--- 192,206 ----
  
  	if (so != NULL)
  	{
! 		MemoryContextDelete(so->tempCxt);
! 		MemoryContextDelete(so->stackCxt);
  		if (so->giststate != NULL)
  			freeGISTstate(so->giststate);
! 		pfree(so->operatorRetType);
! 		pfree(so->distances);
  		pfree(scan->opaque);
  	}
  
  	PG_RETURN_VOID();
  }
  
diff -c -r -N src.orig/include/access/gist.h src/include/access/gist.h
*** src.orig/include/access/gist.h	2010-01-12 14:51:41.000000000 +0300
--- src/include/access/gist.h	2010-01-12 19:51:30.000000000 +0300
***************
*** 54,59 ****
--- 54,64 ----
  #define RTOldContainedByStrategyNumber	14		/* for old spelling of <@ */
  
  /*
+  * Strategy number for knn-search
+  */
+ #define KNNSearchStrategyNumber 		15
+ 
+ /*
   * Page opaque data in a GiST index page.
   */
  #define F_LEAF				(1 << 0)
diff -c -r -N src.orig/include/access/gist_private.h src/include/access/gist_private.h
*** src.orig/include/access/gist_private.h	2010-01-12 14:51:41.000000000 +0300
--- src/include/access/gist_private.h	2010-01-12 19:51:30.000000000 +0300
***************
*** 17,49 ****
  #include "access/gist.h"
  #include "access/itup.h"
  #include "storage/bufmgr.h"
  
  #define GIST_UNLOCK BUFFER_LOCK_UNLOCK
  #define GIST_SHARE	BUFFER_LOCK_SHARE
  #define GIST_EXCLUSIVE	BUFFER_LOCK_EXCLUSIVE
  
- 
  /*
!  * XXX old comment!!!
!  * When we descend a tree, we keep a stack of parent pointers. This
!  * allows us to follow a chain of internal node points until we reach
!  * a leaf node, and then back up the stack to re-examine the internal
!  * nodes.
!  *
!  * 'parent' is the previous stack entry -- i.e. the node we arrived
!  * from. 'block' is the node's block number. 'offset' is the offset in
!  * the node's page that we stopped at (i.e. we followed the child
!  * pointer located at the specified offset).
   */
! typedef struct GISTSearchStack
! {
! 	struct GISTSearchStack *next;
! 	BlockNumber block;
! 	/* to identify page changed */
! 	GistNSN		lsn;
! 	/* to recognize split occured */
! 	GistNSN		parentlsn;
! } GISTSearchStack;
  
  typedef struct GISTSTATE
  {
--- 17,51 ----
  #include "access/gist.h"
  #include "access/itup.h"
  #include "storage/bufmgr.h"
+ #include "utils/rbtree.h"
  
  #define GIST_UNLOCK BUFFER_LOCK_UNLOCK
  #define GIST_SHARE	BUFFER_LOCK_SHARE
  #define GIST_EXCLUSIVE	BUFFER_LOCK_EXCLUSIVE
  
  /*
!  * Elements of stack of search tree traversal
   */
! typedef struct DataPointer {
! 	BlockNumber     blkno; /* if blkno is InvalidBlockNumber then data.heap should be used */
! 	struct DataPointer  *next;
! 	union {
! 		/* to recognize split occured */
! 		GistNSN         parentlsn;
! 		struct {
! 			ItemPointerData     ptr;
! 			bool                recheck;
! 		} heap;
! 	} data;
! } DataPointer;
! 
! typedef struct StackElem {
! 	DataPointer *head;
! 	DataPointer *tail;
! 	double       distances[0]; /*distances per key, variable size */ 
! } StackElem;
! 
! #define SEHDRSZ	offsetof(StackElem, distances)
  
  typedef struct GISTSTATE
  {
***************
*** 61,68 ****
  typedef struct ItemResult
  {
  	ItemPointerData heapPtr;
! 	OffsetNumber pageOffset;	/* offset in index page */
! 	bool		recheck;
  } ItemResult;
  
  /*
--- 63,69 ----
  typedef struct ItemResult
  {
  	ItemPointerData heapPtr;
! 	bool			recheck;
  } ItemResult;
  
  /*
***************
*** 71,87 ****
   */
  typedef struct GISTScanOpaqueData
  {
! 	GISTSearchStack *stack;
! 	GISTSearchStack *markstk;
! 	bool		qual_ok;		/* false if qual can never be satisfied */
! 	GISTSTATE  *giststate;
! 	MemoryContext tempCxt;
! 	Buffer		curbuf;
! 	ItemPointerData curpos;
! 
! 	ItemResult	pageData[BLCKSZ / sizeof(IndexTupleData)];
! 	OffsetNumber nPageData;
! 	OffsetNumber curPageData;
  } GISTScanOpaqueData;
  
  typedef GISTScanOpaqueData *GISTScanOpaque;
--- 72,94 ----
   */
  typedef struct GISTScanOpaqueData
  {
!     RBTree          *stack;
! 	GISTSTATE       *giststate;
! 	MemoryContext   tempCxt;
! 	MemoryContext   stackCxt;
! 
! 	Oid             consistentRetType[INDEX_MAX_KEYS];
! 	Oid            *operatorRetType;
! 	double		   *distances;
! 	bool            qual_ok;
! 	bool            firstCall;
! 	bool			isKNNsearch;
! 
! 	StackElem      *currentPointer;
! 
! 	ItemResult  	pageData[BLCKSZ / sizeof(IndexTupleData)];
! 	OffsetNumber 	nPageData;
! 	OffsetNumber 	curPageData;
  } GISTScanOpaqueData;
  
  typedef GISTScanOpaqueData *GISTScanOpaque;
diff -c -r -N src.orig/include/catalog/pg_am.h src/include/catalog/pg_am.h
*** src.orig/include/catalog/pg_am.h	2010-01-12 14:51:40.000000000 +0300
--- src/include/catalog/pg_am.h	2010-01-12 19:51:30.000000000 +0300
***************
*** 49,54 ****
--- 49,55 ----
  	bool		amsearchnulls;	/* can AM search for NULL/NOT NULL entries? */
  	bool		amstorage;		/* can storage type differ from column type? */
  	bool		amclusterable;	/* does AM support cluster command? */
+ 	bool		amcanorderbyop;	/* does AM support ordering operations? */
  	Oid			amkeytype;		/* type of data in index, or InvalidOid */
  	regproc		aminsert;		/* "insert this tuple" function */
  	regproc		ambeginscan;	/* "start new scan" function */
***************
*** 109,124 ****
   * ----------------
   */
  
! DATA(insert OID = 403 (  btree	5 1 t t t t t t t f t 0 btinsert btbeginscan btgettuple btgetbitmap btrescan btendscan btmarkpos btrestrpos btbuild btbulkdelete btvacuumcleanup btcostestimate btoptions ));
  DESCR("b-tree index access method");
  #define BTREE_AM_OID 403
! DATA(insert OID = 405 (  hash	1 1 f t f f f f f f f 23 hashinsert hashbeginscan hashgettuple hashgetbitmap hashrescan hashendscan hashmarkpos hashrestrpos hashbuild hashbulkdelete hashvacuumcleanup hashcostestimate hashoptions ));
  DESCR("hash index access method");
  #define HASH_AM_OID 405
! DATA(insert OID = 783 (  gist	0 7 f f f t t t t t t 0 gistinsert gistbeginscan gistgettuple gistgetbitmap gistrescan gistendscan gistmarkpos gistrestrpos gistbuild gistbulkdelete gistvacuumcleanup gistcostestimate gistoptions ));
  DESCR("GiST index access method");
  #define GIST_AM_OID 783
! DATA(insert OID = 2742 (  gin	0 5 f f f t t f f t f 0 gininsert ginbeginscan - gingetbitmap ginrescan ginendscan ginmarkpos ginrestrpos ginbuild ginbulkdelete ginvacuumcleanup gincostestimate ginoptions ));
  DESCR("GIN index access method");
  #define GIN_AM_OID 2742
  
--- 110,125 ----
   * ----------------
   */
  
! DATA(insert OID = 403 (  btree	5 1 t t t t t t t f t f 0 btinsert btbeginscan btgettuple btgetbitmap btrescan btendscan btmarkpos btrestrpos btbuild btbulkdelete btvacuumcleanup btcostestimate btoptions ));
  DESCR("b-tree index access method");
  #define BTREE_AM_OID 403
! DATA(insert OID = 405 (  hash	1 1 f t f f f f f f f f 23 hashinsert hashbeginscan hashgettuple hashgetbitmap hashrescan hashendscan hashmarkpos hashrestrpos hashbuild hashbulkdelete hashvacuumcleanup hashcostestimate hashoptions ));
  DESCR("hash index access method");
  #define HASH_AM_OID 405
! DATA(insert OID = 783 (  gist	0 7 f f f t t t t t t t 0 gistinsert gistbeginscan gistgettuple gistgetbitmap gistrescan gistendscan gistmarkpos gistrestrpos gistbuild gistbulkdelete gistvacuumcleanup gistcostestimate gistoptions ));
  DESCR("GiST index access method");
  #define GIST_AM_OID 783
! DATA(insert OID = 2742 (  gin	0 5 f f f t t f f t f f 0 gininsert ginbeginscan - gingetbitmap ginrescan ginendscan ginmarkpos ginrestrpos ginbuild ginbulkdelete ginvacuumcleanup gincostestimate ginoptions ));
  DESCR("GIN index access method");
  #define GIN_AM_OID 2742
  
