diff -c -r src.orig/backend/access/gin/ginutil.c src/backend/access/gin/ginutil.c
*** src.orig/backend/access/gin/ginutil.c	2010-09-07 16:07:57.000000000 +0400
--- src/backend/access/gin/ginutil.c	2010-09-07 16:14:04.000000000 +0400
***************
*** 13,22 ****
--- 13,24 ----
   */
  
  #include "postgres.h"
+ 
  #include "access/genam.h"
  #include "access/gin.h"
  #include "access/reloptions.h"
  #include "catalog/pg_type.h"
+ #include "miscadmin.h"
  #include "storage/bufmgr.h"
  #include "storage/freespace.h"
  #include "storage/indexfsm.h"
***************
*** 227,232 ****
--- 229,238 ----
  	metadata->tailFreeSize = 0;
  	metadata->nPendingPages = 0;
  	metadata->nPendingHeapTuples = 0;
+ 	metadata->nTotalPages = 0;
+ 	metadata->nEntryPages = 0;
+ 	metadata->nDataPages = 0;
+ 	metadata->nEntries = 0;
  }
  
  int
***************
*** 354,356 ****
--- 360,441 ----
  
  	PG_RETURN_BYTEA_P(rdopts);
  }
+ 
+ /*
+  * Fetch index's statistical data into *stats
+  *
+  * Note: in the result, nPendingPages can be trusted to be up-to-date,
+  * but the other fields are as of the last VACUUM.
+  */
+ void
+ ginGetStats(Relation index, GinStatsData *stats)
+ {
+ 	Buffer			metabuffer;
+ 	Page			metapage;
+ 	GinMetaPageData	*metadata;
+ 
+ 	metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
+ 	LockBuffer(metabuffer, GIN_SHARE);
+ 	metapage = BufferGetPage(metabuffer);
+ 	metadata = GinPageGetMeta(metapage);
+ 
+ 	stats->nPendingPages = metadata->nPendingPages;
+ 	stats->nTotalPages = metadata->nTotalPages;
+ 	stats->nEntryPages = metadata->nEntryPages;
+ 	stats->nDataPages = metadata->nDataPages;
+ 	stats->nEntries = metadata->nEntries;
+ 
+ 	UnlockReleaseBuffer(metabuffer);
+ }
+ 
+ /*
+  * Write the given statistics to the index's metapage
+  *
+  * Note: nPendingPages is *not* copied over
+  */
+ void
+ ginUpdateStats(Relation index, const GinStatsData *stats)
+ {
+ 	Buffer			metabuffer;
+ 	Page			metapage;
+ 	GinMetaPageData	*metadata;
+ 
+ 	metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
+ 	LockBuffer(metabuffer, GIN_EXCLUSIVE);
+ 	metapage = BufferGetPage(metabuffer);
+ 	metadata = GinPageGetMeta(metapage);
+ 
+ 	START_CRIT_SECTION();
+ 
+ 	metadata->nTotalPages = stats->nTotalPages;
+ 	metadata->nEntryPages = stats->nEntryPages;
+ 	metadata->nDataPages = stats->nDataPages;
+ 	metadata->nEntries = stats->nEntries;
+ 
+ 	MarkBufferDirty(metabuffer);
+ 
+ 	if (!index->rd_istemp)
+ 	{
+ 		XLogRecPtr  		recptr;
+ 		ginxlogUpdateMeta	data;
+ 		XLogRecData			rdata;
+ 
+ 		data.node = index->rd_node;
+ 		data.ntuples = 0;
+ 		data.newRightlink = data.prevTail = InvalidBlockNumber;
+ 		memcpy(&data.metadata, metadata, sizeof(GinMetaPageData));
+ 
+ 		rdata.buffer = InvalidBuffer;
+ 		rdata.data = (char *) &data;
+ 		rdata.len = sizeof(ginxlogUpdateMeta);
+ 		rdata.next = NULL;
+ 
+ 		recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_UPDATE_META_PAGE, &rdata);
+ 		PageSetLSN(metapage, recptr);
+ 		PageSetTLI(metapage, ThisTimeLineID);
+ 	}
+ 
+ 	UnlockReleaseBuffer(metabuffer);
+ 
+ 	END_CRIT_SECTION();
+ }
diff -c -r src.orig/backend/access/gin/ginvacuum.c src/backend/access/gin/ginvacuum.c
*** src.orig/backend/access/gin/ginvacuum.c	2010-09-07 16:07:57.000000000 +0400
--- src/backend/access/gin/ginvacuum.c	2010-09-07 16:14:04.000000000 +0400
***************
*** 707,715 ****
  	BlockNumber npages,
  				blkno;
  	BlockNumber totFreePages;
- 	BlockNumber lastBlock = GIN_ROOT_BLKNO,
- 				lastFilledBlock = GIN_ROOT_BLKNO;
  	GinState	ginstate;
  
  	/*
  	 * In an autovacuum analyze, we want to clean up pending insertions.
--- 707,714 ----
  	BlockNumber npages,
  				blkno;
  	BlockNumber totFreePages;
  	GinState	ginstate;
+ 	GinStatsData idxStat;
  
  	/*
  	 * In an autovacuum analyze, we want to clean up pending insertions.
***************
*** 736,741 ****
--- 735,742 ----
  		ginInsertCleanup(index, &ginstate, true, stats);
  	}
  
+ 	memset(&idxStat, 0, sizeof(idxStat));
+ 
  	/*
  	 * XXX we always report the heap tuple count as the number of index
  	 * entries.  This is bogus if the index is partial, but it's real hard to
***************
*** 757,763 ****
  
  	totFreePages = 0;
  
! 	for (blkno = GIN_ROOT_BLKNO + 1; blkno < npages; blkno++)
  	{
  		Buffer		buffer;
  		Page		page;
--- 758,764 ----
  
  	totFreePages = 0;
  
! 	for (blkno = GIN_ROOT_BLKNO; blkno < npages; blkno++)
  	{
  		Buffer		buffer;
  		Page		page;
***************
*** 771,785 ****
  
  		if (GinPageIsDeleted(page))
  		{
  			RecordFreeIndexPage(index, blkno);
  			totFreePages++;
  		}
! 		else
! 			lastFilledBlock = blkno;
  
  		UnlockReleaseBuffer(buffer);
  	}
! 	lastBlock = npages - 1;
  
  	/* Finally, vacuum the FSM */
  	IndexFreeSpaceMapVacuum(info->index);
--- 772,799 ----
  
  		if (GinPageIsDeleted(page))
  		{
+ 			Assert(blkno != GIN_ROOT_BLKNO);
  			RecordFreeIndexPage(index, blkno);
  			totFreePages++;
  		}
! 		else if (GinPageIsData(page))
! 		{
! 			idxStat.nDataPages++;
! 		}
! 		else if (!GinPageIsList(page))
! 		{
! 			idxStat.nEntryPages++;
! 
! 			if ( GinPageIsLeaf(page) )
! 				idxStat.nEntries += PageGetMaxOffsetNumber(page); 
! 		}
  
  		UnlockReleaseBuffer(buffer);
  	}
! 
! 	/* Update the metapage with accurate page and entry counts */
! 	idxStat.nTotalPages = npages;
! 	ginUpdateStats(info->index, &idxStat);
  
  	/* Finally, vacuum the FSM */
  	IndexFreeSpaceMapVacuum(info->index);
diff -c -r src.orig/backend/utils/adt/selfuncs.c src/backend/utils/adt/selfuncs.c
*** src.orig/backend/utils/adt/selfuncs.c	2010-09-07 16:07:59.000000000 +0400
--- src/backend/utils/adt/selfuncs.c	2010-09-07 16:20:07.000000000 +0400
***************
*** 91,96 ****
--- 91,97 ----
  #include <ctype.h>
  #include <math.h>
  
+ #include "access/gin.h"
  #include "access/sysattr.h"
  #include "catalog/index.h"
  #include "catalog/pg_opfamily.h"
***************
*** 6235,6240 ****
--- 6236,6259 ----
  	PG_RETURN_VOID();
  }
  
+ /* Find the index column matching "op"; return its index, or -1 if no match */
+ static int
+ find_index_column(Node *op, IndexOptInfo *index)
+ {
+ 	int	i;
+ 
+ 	for (i=0; i<index->ncolumns; i++)
+ 	{
+ 		if (match_index_to_operand(op, i, index))
+ 			return i;
+ 	}
+ 
+ 	return -1;
+ }
+ 
+ /*
+  * GIN has search behavior completely different from other index types
+  */
  Datum
  gincostestimate(PG_FUNCTION_ARGS)
  {
***************
*** 6246,6255 ****
  	Cost	   *indexTotalCost = (Cost *) PG_GETARG_POINTER(5);
  	Selectivity *indexSelectivity = (Selectivity *) PG_GETARG_POINTER(6);
  	double	   *indexCorrelation = (double *) PG_GETARG_POINTER(7);
  
! 	genericcostestimate(root, index, indexQuals, outer_rel, 0.0,
! 						indexStartupCost, indexTotalCost,
! 						indexSelectivity, indexCorrelation);
  
  	PG_RETURN_VOID();
  }
--- 6265,6602 ----
  	Cost	   *indexTotalCost = (Cost *) PG_GETARG_POINTER(5);
  	Selectivity *indexSelectivity = (Selectivity *) PG_GETARG_POINTER(6);
  	double	   *indexCorrelation = (double *) PG_GETARG_POINTER(7);
+ 	ListCell	   *l;
+ 	int32		   nfullscan = 0;
+ 	List		   *selectivityQuals;
+ 	double		   numPages = index->pages,
+ 				   numTuples = index->tuples;
+ 	double		   numEntryPages,
+ 				   numDataPages,
+ 				   numPendingPages,
+ 				   numEntries;
+ 	double		   partialEntriesInQuals = 0.0;
+ 	double		   searchEntriesInQuals = 0.0;
+ 	double		   exactEntriesInQuals = 0.0;
+ 	double		   entryPagesFetched,
+ 				   dataPagesFetched,
+ 				   dataPagesFetchedBySel;
+ 	double		   qual_op_cost,
+ 				   qual_arg_cost,
+ 				   spc_random_page_cost,
+ 				   num_scans;
+ 	QualCost	   index_qual_cost;
+ 	Relation	   indexRel;
+ 	GinStatsData   ginStats;
+ 
+ 	/*
+ 	 * Obtain statistic information from the meta page
+ 	 */
+ 	indexRel = index_open(index->indexoid, AccessShareLock);
+ 	ginGetStats(indexRel, &ginStats);
+ 	index_close(indexRel, AccessShareLock);
+ 
+ 	numEntryPages = ginStats.nEntryPages;
+ 	numDataPages = ginStats.nDataPages;
+ 	numPendingPages = ginStats.nPendingPages;
+ 	numEntries = ginStats.nEntries;
+ 
+ 	/*
+ 	 * nPendingPages can be trusted, but the other fields are as of the
+ 	 * last VACUUM.  Scale them by the ratio numPages / nTotalPages to
+ 	 * account for growth since then.  If the fields are zero (implying
+ 	 * no VACUUM at all), assume all pages are entry pages.
+ 	 */
+ 	if (ginStats.nTotalPages == 0 || ginStats.nEntryPages == 0)
+ 	{
+ 		numEntryPages = numPages;
+ 		numDataPages = 0;
+ 		numEntries = numTuples;
+ 	}
+ 	else
+ 	{
+ 		double	scale = numPages / ginStats.nTotalPages;
  
! 		numEntryPages = ceil(numEntryPages * scale);
! 		numDataPages = ceil(numDataPages * scale);
! 		numEntries = ceil(numEntries * scale);
! 		/* ensure we didn't round up too much */
! 		numEntryPages = Min(numEntryPages, numPages);
! 		numDataPages = Min(numDataPages, numPages - numEntryPages);
! 	}
! 
! 	/*
! 	 * Include predicate in selectivityQuals (should match genericcostestimate)
! 	 */
! 	if (index->indpred != NIL)
! 	{
! 		List	   *predExtraQuals = NIL;
! 
! 		foreach(l, index->indpred)
! 		{
! 			Node	   *predQual = (Node *) lfirst(l);
! 			List	   *oneQual = list_make1(predQual);
! 
! 			if (!predicate_implied_by(oneQual, indexQuals))
! 				predExtraQuals = list_concat(predExtraQuals, oneQual);
! 		}
! 		/* list_concat avoids modifying the passed-in indexQuals list */
! 		selectivityQuals = list_concat(predExtraQuals, indexQuals);
! 	}
! 	else
! 		selectivityQuals = indexQuals;
! 
! 	/* Estimate the fraction of main-table tuples that will be visited */
! 	*indexSelectivity = clauselist_selectivity(root, selectivityQuals,
! 												index->rel->relid,
! 												JOIN_INNER,
! 												NULL);
! 
! 	/* fetch estimated page cost for schema containing index */
!     get_tablespace_page_costs(index->reltablespace,
! 							  &spc_random_page_cost,
! 							  NULL);
! 
! 	/*
! 	 * Generic assumption about index correlation: there isn't any.
! 	 */
! 	*indexCorrelation = 0.0;
! 
! 	/*
! 	 * Examine quals to estimate number of search entries & partial matches
! 	 */
! 	foreach(l, indexQuals)
! 	{
! 		RestrictInfo   	*rinfo = (RestrictInfo *) lfirst(l);
! 		Expr       	   	*clause;
! 		Node           	*leftop,
! 					   	*rightop,
! 					  	*operand;
! 		Oid				extractProcOid;
! 		Oid				clause_op;
! 		int				strategy_op;
! 		Oid				lefttype,
! 						righttype;
! 		int32			nentries = 0;
! 		bool			*partial_matches = NULL;
! 		Pointer			*extra_data = NULL;
! 		int				indexcol;
! 
! 		Assert(IsA(rinfo, RestrictInfo));
! 		clause = rinfo->clause;
! 		Assert(IsA(clause, OpExpr));
! 		leftop = get_leftop(clause);
! 		rightop = get_rightop(clause);
! 		clause_op = ((OpExpr *) clause)->opno;
! 
! 		if ((indexcol = find_index_column(leftop, index)) >= 0)
! 		{
! 			operand = rightop;
! 		}
! 		else if ((indexcol = find_index_column(rightop, index)) >= 0)
! 		{
! 			operand = leftop;
! 			clause_op = get_commutator(clause_op);
! 		}
! 		else
! 		{
! 			elog(ERROR, "Could not match index to operand");
! 			operand = NULL; /* keep compiler quiet */
! 		}
! 
! 		if (IsA(operand, RelabelType))
! 			operand = (Node *) ((RelabelType *) operand)->arg;
! 
! 		/*
! 		 * It's impossible to call extractQuery method for unknown operand.
! 		 * So unless operand is a Const we can't do much; just assume there
! 		 * will be one ordinary search entry from the operand at runtime.
! 		 */
! 		if (!IsA(operand, Const))
! 		{
! 			searchEntriesInQuals++;
! 		 	continue;
! 		}
! 
! 		/* If Const is null, there can be no matches */
! 		if (((Const*) operand)->constisnull)
! 		{
! 			*indexStartupCost = 0;
! 			*indexTotalCost = 0;
! 			*indexSelectivity = 0;
! 			PG_RETURN_VOID();
! 		}
! 
! 		/*
! 		 * Get the operator's strategy number and declared input data types
! 		 * within the index opfamily.
! 		 */
! 		get_op_opfamily_properties(clause_op, index->opfamily[indexcol],
! 								   &strategy_op, &lefttype, &righttype);
! 
! 		/*
! 		 * GIN (like GiST) always has lefttype == righttype in pg_amproc
! 		 * and they are equal to type Oid on which index was created/designed
! 		 */
! 		extractProcOid = get_opfamily_proc(index->opfamily[indexcol],
! 										   lefttype, lefttype,
! 										   GIN_EXTRACTQUERY_PROC);
! 
! 		if (!OidIsValid(extractProcOid))
! 		{
! 			/* probably shouldn't happen, but cope sanely if so */
! 			searchEntriesInQuals++;
! 			continue;
! 		}
! 
! 		OidFunctionCall5(extractProcOid,
! 						 ((Const*)operand)->constvalue,
! 						 PointerGetDatum(&nentries),
! 						 UInt16GetDatum(strategy_op),
! 						 PointerGetDatum(&partial_matches),
! 						 PointerGetDatum(&extra_data));
! 
! 		if (nentries == 0)
! 		{
! 			nfullscan++;
! 		}
! 		else if (nentries < 0)
! 		{
! 			/*
! 			 * GIN_EXTRACTQUERY_PROC guarantees that nothing will be found
! 			 */
! 			*indexStartupCost = 0;
! 			*indexTotalCost = 0;
! 			*indexSelectivity = 0;
! 			PG_RETURN_VOID();
! 		}
! 		else
! 		{
! 			int		i;
! 
! 			for (i=0; i<nentries; i++)
! 			{
! 				/*
! 				 * For partial match we haven't any information to estimate
! 				 * number of matched entries in index, so, we just estimate it
! 				 * as 100
! 				 */
! 				if (partial_matches && partial_matches[i])
! 					partialEntriesInQuals += 100;
! 				else
! 					exactEntriesInQuals++;
! 
! 				searchEntriesInQuals++;
! 			}
! 		}
! 	}
! 
! 	if (nfullscan == list_length(indexQuals))
! 		searchEntriesInQuals = numEntries;
! 
! 	/* Will we have more than one iteration of a nestloop scan? */
! 	if (outer_rel != NULL && outer_rel->rows > 1)
! 		num_scans = outer_rel->rows;
! 	else
! 		num_scans = 1;
! 
! 	/*
! 	 * cost to begin scan, first of all, pay attention to
! 	 * pending list.
! 	 */
! 	entryPagesFetched = numPendingPages;
! 
! 	/*
! 	 * Estimate number of entry pages read.  We need to do
! 	 * searchEntriesInQuals searches.  Use a power function as it should be,
! 	 * but tuples on leaf pages usually is much greater.
! 	 * Here we include all searches in entry tree, including 
! 	 * search of first entry in partial match algorithm
! 	 */
! 	entryPagesFetched += ceil(searchEntriesInQuals * rint(pow(numEntryPages, 0.15)));
! 
! 	/*
! 	 * Calc entry pages readed by partial match algorithm, 
! 	 * it's a scan over leaf pages in entry tree.
! 	 * We havn't any useful stats here, so, estimate it as 
! 	 * proportion
! 	 */
! 	entryPagesFetched += ceil(numEntryPages * partialEntriesInQuals / numEntries);
! 
! 	/*
! 	 * Partial match algorithm reads all data pages before
! 	 * doing actual scan, so it's a startup cost. Again,
! 	 * we havn't any useful stats here, so, estimate it as 
! 	 * proportion
! 	 */
! 	dataPagesFetched = ceil(numDataPages * partialEntriesInQuals / numEntries); 
! 
! 	/* calculate cache effects */
! 	if (num_scans>1 || searchEntriesInQuals>1)
! 	{
! 		entryPagesFetched = index_pages_fetched(entryPagesFetched,
! 										   (BlockNumber) numEntryPages,
! 										   numEntryPages, root);
! 		dataPagesFetched = index_pages_fetched(dataPagesFetched,
! 										   (BlockNumber) numDataPages,
! 										   numDataPages, root);
! 	}
! 
! 	/* 
! 	 * use random cost because logically close pages could 
! 	 * really far on disk
! 	 */
! 	*indexStartupCost = (entryPagesFetched + dataPagesFetched) * spc_random_page_cost;
! 
! 	elog(DEBUG1, "Startup cost estimate: entryPagesFetched: %f; dataPagesFetched: %f; indexStartupCost: %.02f",
! 				entryPagesFetched, dataPagesFetched, *indexStartupCost);
! 
! 	/* cost to scan data pages for each exact (non-partial) matched entry */
! 	dataPagesFetched = ceil(numDataPages * exactEntriesInQuals / numEntries);
! 
! 	/*
! 	 * Count data pages readed by selectivity estimation and 
! 	 * capacity of data page. 
! 	 */
! 	dataPagesFetchedBySel = ceil(*indexSelectivity * (numTuples / (BLCKSZ/SizeOfIptrData)));
! 
! 	if (dataPagesFetchedBySel > dataPagesFetched)
! 	{
! 		/*
! 		 * At least one of entries is very frequent and, unfortunately,
! 		 * we couldn't get statistic about entries (only tsvector has
! 		 * such statistics). So, we obviously have too small estimation of
! 		 * pages fetched from data tree. Re-estimate it from known
! 		 * capacity of data pages
! 		 */
! 
! 		dataPagesFetched = dataPagesFetchedBySel;
! 	}
! 
! 	if (num_scans>1)
! 		dataPagesFetched = index_pages_fetched(dataPagesFetched,
! 										   (BlockNumber)numDataPages,
! 										   numDataPages, root);
! 	*indexTotalCost = *indexStartupCost + dataPagesFetched * spc_random_page_cost;
! 
! 	elog(DEBUG1, "Total cost estimate: dataPagesFetched: %f; dataPagesFetchedBySel: %f; indexTotalCost: %.02f",
! 				dataPagesFetched, dataPagesFetchedBySel, *indexTotalCost);
! 
! 	/*
! 	 * Add on index qual eval costs, much as in genericcostestimate
! 	 */
! 	cost_qual_eval(&index_qual_cost, indexQuals, root);
! 	qual_op_cost = cpu_operator_cost * list_length(indexQuals);
! 	qual_arg_cost = index_qual_cost.startup +
! 		index_qual_cost.per_tuple - qual_op_cost;
! 	if (qual_arg_cost < 0)      /* just in case... */
! 		qual_arg_cost = 0;
! 
! 	*indexStartupCost += qual_arg_cost;
! 	*indexTotalCost += qual_arg_cost;
! 	*indexTotalCost += ( numTuples * *indexSelectivity ) * (cpu_index_tuple_cost + qual_op_cost);
! 
! 	elog(DEBUG1, "Final cost estimate: indexStartupCost: %.02f; indexTotalCost: %.02f",
! 					*indexStartupCost, *indexTotalCost);
  
  	PG_RETURN_VOID();
  }
diff -c -r src.orig/include/access/gin.h src/include/access/gin.h
*** src.orig/include/access/gin.h	2010-09-07 16:08:05.000000000 +0400
--- src/include/access/gin.h	2010-09-07 16:14:04.000000000 +0400
***************
*** 79,84 ****
--- 79,92 ----
  	 */
  	BlockNumber nPendingPages;
  	int64		nPendingHeapTuples;
+ 
+ 	/*
+ 	 * Statistics for planner use (accurate as of last VACUUM)
+ 	 */
+ 	BlockNumber	nTotalPages;
+ 	BlockNumber	nEntryPages;
+ 	BlockNumber	nDataPages;
+ 	int64		nEntries;
  } GinMetaPageData;
  
  #define GinPageGetMeta(p) \
***************
*** 94,99 ****
--- 102,109 ----
  #define GinPageSetNonLeaf(page)    ( GinPageGetOpaque(page)->flags &= ~GIN_LEAF )
  #define GinPageIsData(page)    ( GinPageGetOpaque(page)->flags & GIN_DATA )
  #define GinPageSetData(page)   ( GinPageGetOpaque(page)->flags |= GIN_DATA )
+ #define GinPageIsList(page)    ( GinPageGetOpaque(page)->flags & GIN_LIST )
+ #define GinPageSetList(page)   ( GinPageGetOpaque(page)->flags |= GIN_LIST )
  #define GinPageHasFullRow(page)    ( GinPageGetOpaque(page)->flags & GIN_LIST_FULLROW )
  #define GinPageSetFullRow(page)   ( GinPageGetOpaque(page)->flags |= GIN_LIST_FULLROW )
  
***************
*** 362,367 ****
--- 372,392 ----
  extern Datum gin_index_getattr(GinState *ginstate, IndexTuple tuple);
  extern OffsetNumber gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple);
  
+ /*
+  * GinStatsData represents stats data for planner use
+  */
+ typedef struct GinStatsData
+ {
+ 	BlockNumber nPendingPages;
+ 	BlockNumber	nTotalPages;
+ 	BlockNumber	nEntryPages;
+ 	BlockNumber	nDataPages;
+ 	int64		nEntries;
+ } GinStatsData;
+ 
+ extern void ginGetStats(Relation index, GinStatsData *stats);
+ extern void ginUpdateStats(Relation index, const GinStatsData *stats);
+ 
  /* gininsert.c */
  extern Datum ginbuild(PG_FUNCTION_ARGS);
  extern Datum gininsert(PG_FUNCTION_ARGS);
