Index: src/backend/executor/nodeHash.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/executor/nodeHash.c,v
retrieving revision 1.116
diff -c -r1.116 nodeHash.c
*** src/backend/executor/nodeHash.c	1 Jan 2008 19:45:49 -0000	1.116
--- src/backend/executor/nodeHash.c	29 Dec 2008 07:38:52 -0000
***************
*** 53,58 ****
--- 53,201 ----
  	return NULL;
  }
  
+ /*
+ *	ExecHashGetMCVPartition
+ *
+ *	returns MCV_INVALID_PARTITION if the hashvalue does not correspond
+ *   to any MCV partition or it corresponds to a partition that has been frozen
+ *	or MCVs are not being used
+ *
+ *   otherwise it returns the index of the MCV partition for this hashvalue
+ *
+ *	it is possible for a non-MCV tuple to hash to an MCV partition due to
+ *	the limited number of hash values but it is unlikely and everything
+ *	continues to work even if it does happen. we would accidentally prioritize
+ *	some less optimal tuples in memory but the result would be accurate
+ *
+ *	hashtable->mostCommonTuplePartition is an open addressing hashtable of
+ *	MCV partitions (HashJoinMostCommonValueTuplePartition)
+ */
+ int 
+ ExecHashGetMCVPartition(HashJoinTable hashtable, uint32 hashvalue)
+ {
+ 	int bucket;
+ 
+ 	if (!hashtable->usingMostCommonValues)
+ 		return MCV_INVALID_PARTITION;
+ 	
+ 	/* modulo the hashvalue (using bitmask) to find the MCV partition */
+ 	bucket = hashvalue & (hashtable->nMostCommonTuplePartitionHashBuckets - 1);
+ 
+ 	/*
+ 	 * while we have not hit a hole in the hashtable and have not hit the actual partition
+ 	 * we have collided in the hashtable so try the next partition slot
+ 	 */
+ 	while (hashtable->mostCommonTuplePartition[bucket].hashvalue != 0
+ 		&& hashtable->mostCommonTuplePartition[bucket].hashvalue != hashvalue)
+ 		bucket = (bucket + 1) & (hashtable->nMostCommonTuplePartitionHashBuckets - 1);
+ 
+ 	/* if the partition is not frozen and has been correctly determined return the partition index */
+ 	if (!hashtable->mostCommonTuplePartition[bucket].frozen && hashtable->mostCommonTuplePartition[bucket].hashvalue == hashvalue)
+ 		return bucket;
+ 
+ 	/* must have run into an empty slot which means this is not an MCV*/
+ 	return MCV_INVALID_PARTITION;
+ }
+ 
+ /*
+ *	ExecHashFreezeNextMCVPartition
+ *
+ *	flush the tuples of the next MCV partition by pushing them into the main hashtable
+ */
+ static bool 
+ ExecHashFreezeNextMCVPartition(HashJoinTable hashtable) {
+ 	/*
+ 	 * calculate the flushOrderedMostCommonTuplePartition index of
+ 	 * the partition to flush. not to be confused with the index of
+ 	 * the partition in the MCV partitions hashtable
+ 	 */
+ 	int partitionToFlush = hashtable->nMostCommonTuplePartitions
+ 		- 1 - hashtable->nMostCommonTuplePartitionsFlushed;
+ 	int			bucketno;
+ 	int			batchno;
+ 	uint32		hashvalue;
+ 	HashJoinTuple hashTuple;
+ 	HashJoinTuple nextHashTuple;
+ 	HashJoinMostCommonValueTuplePartition *partition;
+ 	MinimalTuple mintuple;
+ 
+ 	/* if all MCV partitions have already been flushed */
+ 	if (partitionToFlush < 0)
+ 		return false;
+ 
+ 	/* grab a pointer to the actual MCV partition */
+ 	partition = hashtable->flushOrderedMostCommonTuplePartition[partitionToFlush];
+ 	hashvalue = partition->hashvalue;
+ 
+ 	Assert(hashvalue != 0);
+ 
+ 	/* grab a pointer to the first tuple in the soon to be frozen MCV partition */
+ 	hashTuple = partition->tuples;
+ 
+ 	/*
+ 	 * calculate which bucket and batch the tuples belong to in the main
+ 	 * non-MCV hashtable
+ 	 */
+ 	ExecHashGetBucketAndBatch(hashtable, hashvalue,
+ 							  &bucketno, &batchno);
+ 
+ 	/* until we have read all tuples from this partition */
+ 	while (hashTuple != NULL)
+ 	{
+ 		/* decide whether to put the tuple in the hash table or a temp file */
+ 		if (batchno == hashtable->curbatch)
+ 		{
+ 			/* put the tuple in hash table */
+ 			nextHashTuple = hashTuple->next;
+ 			hashTuple->next = hashtable->buckets[bucketno];
+ 			hashtable->buckets[bucketno] = hashTuple;
+ 			
+ 			hashTuple = nextHashTuple;
+ 			hashtable->totalTuples++;
+ 			hashtable->nMostCommonTuplesStored--;
+ 			
+ 			/*
+ 			 * since the tuple is not being removed from memory we may still
+ 			 * be using too much memory.  if this is the first tuple flushed
+ 			 * then we definately must try to free some memory but we must test
+ 			 * again for subsequent tuples in case more memory must be freed.
+ 			 */
+ 			if (hashtable->spaceUsed > hashtable->spaceAllowed)
+ 			{
+ 				ExecHashIncreaseNumBatches(hashtable);
+ 				/* batchno may have changed due to increase in batches */
+ 				ExecHashGetBucketAndBatch(hashtable, hashvalue,
+ 					&bucketno, &batchno);
+ 			}
+ 		}
+ 		else
+ 		{
+ 			/* put the tuples into a temp file for later batches */
+ 			Assert(batchno > hashtable->curbatch);
+ 			mintuple = HJTUPLE_MINTUPLE(hashTuple);
+ 			ExecHashJoinSaveTuple(mintuple,
+ 								  hashvalue,
+ 								  &hashtable->innerBatchFile[batchno]);
+ 			/*
+ 			 * some memory has been freed up. this must be done before we
+ 			 * pfree the hashTuple of we lose access to the tuple size
+ 			 */
+ 			hashtable->spaceUsed -= HJTUPLE_OVERHEAD + mintuple->t_len;
+ 			nextHashTuple = hashTuple->next;
+ 			pfree(hashTuple);
+ 			hashTuple = nextHashTuple;
+ 			hashtable->totalTuples++;
+ 			hashtable->nMostCommonTuplesStored--;
+ 		}
+ 	}
+ 
+ 	partition->frozen = true;
+ 	partition->tuples = NULL;
+ 	hashtable->nMostCommonTuplePartitionsFlushed++;
+ 
+ 	return true;
+ }
+ 
  /* ----------------------------------------------------------------
   *		MultiExecHash
   *
***************
*** 69,74 ****
--- 212,219 ----
  	TupleTableSlot *slot;
  	ExprContext *econtext;
  	uint32		hashvalue;
+ 	MinimalTuple mintuple;
+ 	int partitionNumber;
  
  	/* must provide our own instrumentation support */
  	if (node->ps.instrument)
***************
*** 99,106 ****
  		if (ExecHashGetHashValue(hashtable, econtext, hashkeys, false, false,
  								 &hashvalue))
  		{
! 			ExecHashTableInsert(hashtable, slot, hashvalue);
! 			hashtable->totalTuples += 1;
  		}
  	}
  
--- 244,279 ----
  		if (ExecHashGetHashValue(hashtable, econtext, hashkeys, false, false,
  								 &hashvalue))
  		{
! 			partitionNumber = ExecHashGetMCVPartition(hashtable, hashvalue);
! 
! 			/* if this tuple belongs in an MCV partition */
! 			if (partitionNumber != MCV_INVALID_PARTITION)
! 			{
! 				HashJoinTuple hashTuple;
! 				int			hashTupleSize;
! 				
! 				/* get the HashJoinTuple */
! 				mintuple = ExecFetchSlotMinimalTuple(slot);
! 				hashTupleSize = HJTUPLE_OVERHEAD + mintuple->t_len;
! 				hashTuple = (HashJoinTuple) palloc(hashTupleSize);
! 				hashTuple->hashvalue = hashvalue;
! 				memcpy(HJTUPLE_MINTUPLE(hashTuple), mintuple, mintuple->t_len);
! 
! 				/* push the HashJoinTuple onto the front of the MCV partition tuple list */
! 				hashTuple->next = hashtable->mostCommonTuplePartition[partitionNumber].tuples;
! 				hashtable->mostCommonTuplePartition[partitionNumber].tuples = hashTuple;
! 				
! 				/* move memory is now in use so make sure we are not over memory */
! 				hashtable->spaceUsed += hashTupleSize;
! 				hashtable->nMostCommonTuplesStored++;
! 				while (hashtable->spaceUsed > hashtable->spaceAllowed && ExecHashFreezeNextMCVPartition(hashtable))
! 					;
! 			}
! 			else
! 			{
! 				ExecHashTableInsert(hashtable, slot, hashvalue);
! 				hashtable->totalTuples += 1;
! 			}
  		}
  	}
  
***************
*** 269,274 ****
--- 442,454 ----
  	hashtable->outerBatchFile = NULL;
  	hashtable->spaceUsed = 0;
  	hashtable->spaceAllowed = work_mem * 1024L;
+ 	/* initialize MCV related hashtable variables */
+ 	hashtable->usingMostCommonValues = false;
+ 	hashtable->nMostCommonTuplePartitions = 0;
+ 	hashtable->nMostCommonTuplePartitionHashBuckets = 0;
+ 	hashtable->nMostCommonTuplesStored = 0;
+ 	hashtable->mostCommonTuplePartition = NULL;
+ 	hashtable->nMostCommonTuplePartitionsFlushed = 0;
  
  	/*
  	 * Get info about the hash functions to be used for each hash key. Also
***************
*** 566,571 ****
--- 746,752 ----
  				ExecHashJoinSaveTuple(HJTUPLE_MINTUPLE(tuple),
  									  tuple->hashvalue,
  									  &hashtable->innerBatchFile[batchno]);
+ 
  				/* and remove from hash table */
  				if (prevtuple)
  					prevtuple->next = nexttuple;
***************
*** 798,803 ****
--- 979,1046 ----
  }
  
  /*
+  * ExecScanHashMostCommonTuples
+  *		scan a MCV partition for matches to the current outer tuple
+  *
+  * The current outer tuple must be stored in econtext->ecxt_outertuple.
+  */
+ HashJoinTuple
+ ExecScanHashMostCommonTuples(HashJoinState *hjstate,
+ 				   ExprContext *econtext)
+ {
+ 	List	   *hjclauses = hjstate->hashclauses;
+ 	HashJoinTable hashtable = hjstate->hj_HashTable;
+ 	HashJoinTuple hashTuple = hjstate->hj_CurTuple;
+ 	uint32		hashvalue = hjstate->hj_CurHashValue;
+ 
+ 	/*
+ 	 * hj_CurTuple is NULL to start scanning a new partition, or the address of
+ 	 * the last tuple returned from the current partition.
+ 	 */
+ 	if (hashTuple == NULL)
+ 	{
+ 		/* painstakingly make sure this is a valid partition index */
+ 		Assert(hjstate->hj_OuterTupleMostCommonValuePartition > MCV_INVALID_PARTITION);
+ 		Assert(hjstate->hj_OuterTupleMostCommonValuePartition < hashtable->nMostCommonTuplePartitionHashBuckets);
+ 		Assert(hashtable->mostCommonTuplePartition[hjstate->hj_OuterTupleMostCommonValuePartition].hashvalue != 0);
+ 
+ 		hashTuple = hashtable->mostCommonTuplePartition[hjstate->hj_OuterTupleMostCommonValuePartition].tuples;
+ 	}
+ 	else
+ 		hashTuple = hashTuple->next;
+ 
+ 	while (hashTuple != NULL)
+ 	{
+ 		if (hashTuple->hashvalue == hashvalue)
+ 		{
+ 			TupleTableSlot *inntuple;
+ 
+ 			/* insert hashtable's tuple into exec slot so ExecQual sees it */
+ 			inntuple = ExecStoreMinimalTuple(HJTUPLE_MINTUPLE(hashTuple),
+ 											 hjstate->hj_HashTupleSlot,
+ 											 false);	/* do not pfree */
+ 			econtext->ecxt_innertuple = inntuple;
+ 
+ 			/* reset temp memory each time to avoid leaks from qual expr */
+ 			ResetExprContext(econtext);
+ 
+ 			if (ExecQual(hjclauses, econtext, false))
+ 			{
+ 				hjstate->hj_CurTuple = hashTuple;
+ 				return hashTuple;
+ 			}
+ 		}
+ 
+ 		hashTuple = hashTuple->next;
+ 	}
+ 
+ 	/*
+ 	 * no match
+ 	 */
+ 	return NULL;
+ }
+ 
+ /*
   * ExecScanHashBucket
   *		scan a hash bucket for matches to the current outer tuple
   *
Index: src/backend/executor/nodeHashjoin.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/executor/nodeHashjoin.c,v
retrieving revision 1.96
diff -c -r1.96 nodeHashjoin.c
*** src/backend/executor/nodeHashjoin.c	23 Oct 2008 14:34:34 -0000	1.96
--- src/backend/executor/nodeHashjoin.c	29 Dec 2008 04:34:46 -0000
***************
*** 20,25 ****
--- 20,30 ----
  #include "executor/nodeHash.h"
  #include "executor/nodeHashjoin.h"
  #include "utils/memutils.h"
+ #include "optimizer/cost.h"
+ #include "utils/syscache.h"
+ #include "utils/lsyscache.h"
+ #include "parser/parsetree.h"
+ #include "catalog/pg_statistic.h"
  
  
  /* Returns true for JOIN_LEFT and JOIN_ANTI jointypes */
***************
*** 34,39 ****
--- 39,159 ----
  						  TupleTableSlot *tupleSlot);
  static int	ExecHashJoinNewBatch(HashJoinState *hjstate);
  
+ /*
+  *	ExecHashJoinGetMostCommonValues
+  *
+  *	if the MCV statistics can be found for the join attribute of this
+  *	hashjoin 
+  *
+  */
+ static void 
+ ExecHashJoinGetMostCommonValues(EState *estate, HashJoinState *hjstate)
+ {
+ 	HeapTupleData *statsTuple;
+ 	FuncExprState *clause;
+ 	ExprState *argstate;
+ 	Var *variable;
+ 
+ 	Datum	   *values;
+ 	int			nvalues;
+ 	float4	   *numbers;
+ 	int			nnumbers;
+ 
+ 	Oid relid;
+ 	Oid atttype;
+ 
+ 	int i;
+ 
+ 	/* Only use statistics if there is a single join attribute. */
+ 	if (hjstate->hashclauses->length != 1)
+ 		return; /* histojoin is not defined for more than one join key so run away */
+ 
+ 	/* Determine the relation id and attribute id of the single join attribute of the probe relation. */
+ 	
+ 	clause = (FuncExprState *) lfirst(list_head(hjstate->hashclauses));
+ 	argstate = (ExprState *) lfirst(list_head(clause->args));
+ 
+ 	/* Do not try to exploit stats if the join attribute is an expression instead of just a simple attribute. */		
+ 	if (argstate->expr->type != T_Var)
+ 		return;
+ 
+ 	variable = (Var *) argstate->expr;
+ 	relid = getrelid(variable->varnoold, estate->es_range_table);
+ 
+ 	/* grab the necessary properties of the join variable */
+ 	atttype = variable->vartype;
+ 
+ 	statsTuple = SearchSysCache(STATRELATT, ObjectIdGetDatum(relid),
+ 		Int16GetDatum(variable->varoattno), 0, 0);
+ 
+ 	if (!HeapTupleIsValid(statsTuple))
+ 		return;
+ 
+ 	/* if there are MCV statistics for the attribute */
+ 	if (get_attstatsslot(statsTuple,
+ 		atttype, variable->vartypmod,
+ 		STATISTIC_KIND_MCV, InvalidOid,
+ 		&values, &nvalues,
+ 		&numbers, &nnumbers))
+ 	{
+ 		MemoryContext oldcxt;
+ 		HashJoinTable hashtable;
+ 		FmgrInfo   *hashfunctions;
+ 		/* MCV Partitions is an open addressing hashtable with a 
+ 		power of 2 size greater than the number of MCV values. */
+ 		int nbuckets = 2;
+ 
+ 		while (nbuckets <= nvalues)
+ 			nbuckets <<= 1;
+ 		/* use two more bits just to help avoid collisions */
+ 		nbuckets <<= 2;
+ 
+ 		hashtable = hjstate->hj_HashTable;
+ 		hashtable->usingMostCommonValues = true;
+ 		hashtable->nMostCommonTuplePartitionHashBuckets = nbuckets;
+ 
+ 		/* allocate the partition memory in the hashtable's memory context */
+ 		oldcxt = MemoryContextSwitchTo(hashtable->hashCxt);
+ 		hashtable->mostCommonTuplePartition = palloc0(nbuckets * sizeof(HashJoinMostCommonValueTuplePartition));
+ 		hashtable->flushOrderedMostCommonTuplePartition = palloc0(nvalues * sizeof(HashJoinMostCommonValueTuplePartition*));
+ 		MemoryContextSwitchTo(oldcxt);
+ 
+ 		/* grab the hash functions as we will be generating the hashvalues here */
+ 		hashfunctions = hashtable->outer_hashfunctions;
+ 
+ 		/* create the partitions */
+ 		for (i = 0; i < nvalues; i++)
+ 		{
+ 			uint32 hashvalue = DatumGetUInt32(FunctionCall1(&hashfunctions[0], values[i]));
+ 			int bucket = hashvalue & (nbuckets - 1);
+ 
+ 			/*
+ 			 * while we have not hit a hole in the hashtable and have not hit a partition
+ 			 * with the same hashvalue we have collided in the hashtable so try the next
+ 			 * partition slot (remember it is an open addressing hashtable)
+ 			 */
+ 			while (hashtable->mostCommonTuplePartition[bucket].hashvalue != 0
+ 				&& hashtable->mostCommonTuplePartition[bucket].hashvalue != hashvalue)
+ 				bucket = (bucket + 1) & (nbuckets - 1);
+ 
+ 			/*
+ 			 * leave partition alone if it has the same hashvalue as current MCV.  
+ 			 * we only want one partition per hashvalue. even if two MCV values
+ 			 * hash to the same partition we are fine
+ 			 */
+ 			if (hashtable->mostCommonTuplePartition[bucket].hashvalue != hashvalue)
+ 			{
+ 				hashtable->mostCommonTuplePartition[bucket].hashvalue = hashvalue;
+ 				hashtable->flushOrderedMostCommonTuplePartition[hashtable->nMostCommonTuplePartitions] = &hashtable->mostCommonTuplePartition[bucket];
+ 				hashtable->nMostCommonTuplePartitions++;
+ 			}
+ 		}
+ 
+ 		free_attstatsslot(atttype, values, nvalues, numbers, nnumbers);
+ 	}
+ 
+ 	ReleaseSysCache(statsTuple);
+ }
  
  /* ----------------------------------------------------------------
   *		ExecHashJoin
***************
*** 147,152 ****
--- 267,276 ----
  										node->hj_HashOperators);
  		node->hj_HashTable = hashtable;
  
+ 		/* we dont bother exploiting MCVs if we can do the entire join in memory */
+ 		if (hashtable->nbatch > 1)
+ 			ExecHashJoinGetMostCommonValues(estate, node);
+ 
  		/*
  		 * execute the Hash node, to build the hash table
  		 */
***************
*** 157,163 ****
  		 * If the inner relation is completely empty, and we're not doing an
  		 * outer join, we can quit without scanning the outer relation.
  		 */
! 		if (hashtable->totalTuples == 0 && !HASHJOIN_IS_OUTER(node))
  			return NULL;
  
  		/*
--- 281,287 ----
  		 * If the inner relation is completely empty, and we're not doing an
  		 * outer join, we can quit without scanning the outer relation.
  		 */
! 		if (hashtable->totalTuples == 0 && hashtable->nMostCommonTuplesStored == 0 && !HASHJOIN_IS_OUTER(node))
  			return NULL;
  
  		/*
***************
*** 205,227 ****
  			ExecHashGetBucketAndBatch(hashtable, hashvalue,
  									  &node->hj_CurBucketNo, &batchno);
  			node->hj_CurTuple = NULL;
  
! 			/*
! 			 * Now we've got an outer tuple and the corresponding hash bucket,
! 			 * but this tuple may not belong to the current batch.
! 			 */
! 			if (batchno != hashtable->curbatch)
  			{
  				/*
! 				 * Need to postpone this outer tuple to a later batch. Save it
! 				 * in the corresponding outer-batch file.
  				 */
! 				Assert(batchno > hashtable->curbatch);
! 				ExecHashJoinSaveTuple(ExecFetchSlotMinimalTuple(outerTupleSlot),
! 									  hashvalue,
! 									  &hashtable->outerBatchFile[batchno]);
! 				node->hj_NeedNewOuter = true;
! 				continue;		/* loop around for a new outer tuple */
  			}
  		}
  
--- 329,356 ----
  			ExecHashGetBucketAndBatch(hashtable, hashvalue,
  									  &node->hj_CurBucketNo, &batchno);
  			node->hj_CurTuple = NULL;
+ 			
+ 			node->hj_OuterTupleMostCommonValuePartition = ExecHashGetMCVPartition(hashtable, hashvalue);
  
! 			if (node->hj_OuterTupleMostCommonValuePartition == MCV_INVALID_PARTITION)
  			{
  				/*
! 				 * Now we've got an outer tuple and the corresponding hash bucket,
! 				 * but this tuple may not belong to the current batch.
  				 */
! 				if (batchno != hashtable->curbatch)
! 				{
! 					/*
! 					 * Need to postpone this outer tuple to a later batch. Save it
! 					 * in the corresponding outer-batch file.
! 					 */
! 					Assert(batchno > hashtable->curbatch);
! 					ExecHashJoinSaveTuple(ExecFetchSlotMinimalTuple(outerTupleSlot),
! 										  hashvalue,
! 										  &hashtable->outerBatchFile[batchno]);
! 					node->hj_NeedNewOuter = true;
! 					continue;		/* loop around for a new outer tuple */
! 				}
  			}
  		}
  
***************
*** 230,236 ****
  		 */
  		for (;;)
  		{
! 			curtuple = ExecScanHashBucket(node, econtext);
  			if (curtuple == NULL)
  				break;			/* out of matches */
  
--- 359,373 ----
  		 */
  		for (;;)
  		{
! 			/* if the tuple hashed to an MCV partition then scan the MCV tuples */
! 			if (node->hj_OuterTupleMostCommonValuePartition != MCV_INVALID_PARTITION)
! 			{
! 				curtuple = ExecScanHashMostCommonTuples(node, econtext);
! 			}
! 			else /* otherwise scan the standard hashtable buckets */
! 			{
! 				curtuple = ExecScanHashBucket(node, econtext);
! 			}
  			if (curtuple == NULL)
  				break;			/* out of matches */
  
Index: src/include/executor/hashjoin.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/executor/hashjoin.h,v
retrieving revision 1.48
diff -c -r1.48 hashjoin.h
*** src/include/executor/hashjoin.h	1 Jan 2008 19:45:57 -0000	1.48
--- src/include/executor/hashjoin.h	29 Dec 2008 01:39:50 -0000
***************
*** 72,77 ****
--- 72,85 ----
  #define HJTUPLE_MINTUPLE(hjtup)  \
  	((MinimalTuple) ((char *) (hjtup) + HJTUPLE_OVERHEAD))
  
+ typedef struct HashJoinMostCommonValueTuplePartition
+ {
+ 	uint32 hashvalue;
+ 	bool frozen;
+ 	HashJoinTuple tuples;
+ } HashJoinMostCommonValueTuplePartition;
+ 
+ #define MCV_INVALID_PARTITION -1
  
  typedef struct HashJoinTableData
  {
***************
*** 116,121 ****
--- 124,141 ----
  
  	MemoryContext hashCxt;		/* context for whole-hash-join storage */
  	MemoryContext batchCxt;		/* context for this-batch-only storage */
+ 	
+ 	bool usingMostCommonValues;	/* will the join use MCV partitions */
+ 	HashJoinMostCommonValueTuplePartition *mostCommonTuplePartition; /* hashtable of MCV partitions */
+ 	/*
+ 	 * array of pointers to the MCV partitions hashtable buckets in the opposite order that
+ 	 * they would be flushed to disk
+ 	 */
+ 	HashJoinMostCommonValueTuplePartition **flushOrderedMostCommonTuplePartition;
+ 	int nMostCommonTuplePartitionHashBuckets; /* # of buckets in the MCV partitions hashtable */
+ 	int nMostCommonTuplePartitions; /* # of actual partitions in the MCV partitions hashtable */
+ 	int nMostCommonTuplePartitionsFlushed; /* # of MCV partitions that have already been flushed to disk */
+ 	uint32 nMostCommonTuplesStored; /* total # of build tuples currently stored in the MCV partitions */
  } HashJoinTableData;
  
  #endif   /* HASHJOIN_H */
Index: src/include/executor/nodeHash.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/executor/nodeHash.h,v
retrieving revision 1.45
diff -c -r1.45 nodeHash.h
*** src/include/executor/nodeHash.h	1 Jan 2008 19:45:57 -0000	1.45
--- src/include/executor/nodeHash.h	23 Dec 2008 07:54:03 -0000
***************
*** 44,48 ****
--- 44,51 ----
  extern void ExecChooseHashTableSize(double ntuples, int tupwidth,
  						int *numbuckets,
  						int *numbatches);
+ 						
+ extern HashJoinTuple ExecScanHashMostCommonTuples(HashJoinState *hjstate, ExprContext *econtext);
+ extern int ExecHashGetMCVPartition(HashJoinTable hashtable, uint32 hashvalue);
  
  #endif   /* NODEHASH_H */
Index: src/include/nodes/execnodes.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/nodes/execnodes.h,v
retrieving revision 1.197
diff -c -r1.197 execnodes.h
*** src/include/nodes/execnodes.h	28 Dec 2008 18:54:00 -0000	1.197
--- src/include/nodes/execnodes.h	29 Dec 2008 04:40:02 -0000
***************
*** 1381,1386 ****
--- 1381,1387 ----
   *		hj_NeedNewOuter			true if need new outer tuple on next call
   *		hj_MatchedOuter			true if found a join match for current outer
   *		hj_OuterNotEmpty		true if outer relation known not empty
+  *		hj_OuterTupleMostCommonValuePartition	partition# for the current outer tuple
   * ----------------
   */
  
***************
*** 1406,1411 ****
--- 1407,1413 ----
  	bool		hj_NeedNewOuter;
  	bool		hj_MatchedOuter;
  	bool		hj_OuterNotEmpty;
+ 	int		hj_OuterTupleMostCommonValuePartition;
  } HashJoinState;
  
  
