extra-statistics-v16-ctxdiff.patch

application/octet-stream

Filename: extra-statistics-v16-ctxdiff.patch
Type: application/octet-stream
Part: 0
Message: WIP: cross column stats revisited ...

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: context
Series: patch v16
File+
src/backend/access/common/tupdesc.c 0 3
src/backend/access/nbtree/nbtcompare.c 24 0
src/backend/bootstrap/bootstrap.c 0 1
src/backend/catalog/heap.c 221 24
src/backend/catalog/index.c 1 3
src/backend/catalog/system_views.sql 1 1
src/backend/commands/analyze.c 823 243
src/backend/commands/tablecmds.c 45 10
src/backend/executor/nodeHash.c 16 1
src/backend/nodes/copyfuncs.c 16 0
src/backend/nodes/equalfuncs.c 15 0
src/backend/parser/gram.y 73 2
src/backend/parser/parse_utilcmd.c 93 0
src/backend/tcop/utility.c 20 0
src/backend/tsearch/ts_typanalyze.c 34 28
src/backend/utils/adt/int.c 41 9
src/backend/utils/adt/selfuncs.c 34 25
src/backend/utils/cache/lsyscache.c 8 3
src/backend/utils/cache/syscache.c 2 2
src/include/catalog/heap.h 6 1
src/include/catalog/indexing.h 2 2
src/include/catalog/pg_amop.h 10 0
src/include/catalog/pg_amproc.h 1 0
src/include/catalog/pg_attribute.h 18 28
src/include/catalog/pg_class.h 1 1
src/include/catalog/pg_opclass.h 1 0
src/include/catalog/pg_operator.h 12 1
src/include/catalog/pg_opfamily.h 1 0
src/include/catalog/pg_proc.h 10 1
src/include/catalog/pg_statistic.h 40 24
src/include/commands/defrem.h 3 0
src/include/commands/vacuum.h 43 27
src/include/nodes/nodes.h 1 0
src/include/nodes/parsenodes.h 14 0
src/include/parser/parse_utilcmd.h 3 0
src/include/utils/builtins.h 6 0
src/include/utils/selfuncs.h 4 0
src/test/regress/expected/rules.out 1 1
src/test/regress/expected/type_sanity.out 2 1
src/test/regress/sql/type_sanity.sql 2 1
diff -dcrpN postgresql.orig/src/backend/access/common/tupdesc.c postgresql/src/backend/access/common/tupdesc.c
*** postgresql.orig/src/backend/access/common/tupdesc.c	2011-08-07 11:29:16.011256533 +0200
--- postgresql/src/backend/access/common/tupdesc.c	2011-09-12 10:01:16.383515838 +0200
*************** equalTupleDescs(TupleDesc tupdesc1, Tupl
*** 337,344 ****
  			return false;
  		if (attr1->atttypid != attr2->atttypid)
  			return false;
- 		if (attr1->attstattarget != attr2->attstattarget)
- 			return false;
  		if (attr1->attlen != attr2->attlen)
  			return false;
  		if (attr1->attndims != attr2->attndims)
--- 337,342 ----
*************** TupleDescInitEntry(TupleDesc desc,
*** 471,477 ****
  	else
  		MemSet(NameStr(att->attname), 0, NAMEDATALEN);
  
- 	att->attstattarget = -1;
  	att->attcacheoff = -1;
  	att->atttypmod = typmod;
  
--- 469,474 ----
diff -dcrpN postgresql.orig/src/backend/access/nbtree/nbtcompare.c postgresql/src/backend/access/nbtree/nbtcompare.c
*** postgresql.orig/src/backend/access/nbtree/nbtcompare.c	2011-01-04 15:13:15.816567224 +0100
--- postgresql/src/backend/access/nbtree/nbtcompare.c	2011-09-12 10:01:16.384515791 +0200
*************** btoidvectorcmp(PG_FUNCTION_ARGS)
*** 220,225 ****
--- 220,249 ----
  }
  
  Datum
+ btint2vectorcmp(PG_FUNCTION_ARGS)
+ {
+ 	int2vector  *a = (int2vector *) PG_GETARG_POINTER(0);
+ 	int2vector  *b = (int2vector *) PG_GETARG_POINTER(1);
+ 	int			i;
+ 
+ 	/* We arbitrarily choose to sort first by vector length */
+ 	if (a->dim1 != b->dim1)
+ 		PG_RETURN_INT32(a->dim1 - b->dim1);
+ 
+ 	for (i = 0; i < a->dim1; i++)
+ 	{
+ 		if (a->values[i] != b->values[i])
+ 		{
+ 			if (a->values[i] > b->values[i])
+ 				PG_RETURN_INT32(1);
+ 			else
+ 				PG_RETURN_INT32(-1);
+ 		}
+ 	}
+ 	PG_RETURN_INT32(0);
+ }
+ 
+ Datum
  btcharcmp(PG_FUNCTION_ARGS)
  {
  	char		a = PG_GETARG_CHAR(0);
diff -dcrpN postgresql.orig/src/backend/bootstrap/bootstrap.c postgresql/src/backend/bootstrap/bootstrap.c
*** postgresql.orig/src/backend/bootstrap/bootstrap.c	2011-09-12 09:54:31.169314275 +0200
--- postgresql/src/backend/bootstrap/bootstrap.c	2011-09-12 10:01:16.408514672 +0200
*************** DefineAttr(char *name, char *type, int a
*** 728,734 ****
  			attrtypes[attnum]->attndims = 0;
  	}
  
- 	attrtypes[attnum]->attstattarget = -1;
  	attrtypes[attnum]->attcacheoff = -1;
  	attrtypes[attnum]->atttypmod = -1;
  	attrtypes[attnum]->attislocal = true;
--- 728,733 ----
diff -dcrpN postgresql.orig/src/backend/catalog/heap.c postgresql/src/backend/catalog/heap.c
*** postgresql.orig/src/backend/catalog/heap.c	2011-09-02 12:52:41.334576388 +0200
--- postgresql/src/backend/catalog/heap.c	2011-09-12 10:01:16.429513691 +0200
*************** static List *insert_ordered_unique_oid(L
*** 126,162 ****
   */
  
  static FormData_pg_attribute a1 = {
! 	0, {"ctid"}, TIDOID, 0, sizeof(ItemPointerData),
  	SelfItemPointerAttributeNumber, 0, -1, -1,
  	false, 'p', 's', true, false, false, true, 0
  };
  
  static FormData_pg_attribute a2 = {
! 	0, {"oid"}, OIDOID, 0, sizeof(Oid),
  	ObjectIdAttributeNumber, 0, -1, -1,
  	true, 'p', 'i', true, false, false, true, 0
  };
  
  static FormData_pg_attribute a3 = {
! 	0, {"xmin"}, XIDOID, 0, sizeof(TransactionId),
  	MinTransactionIdAttributeNumber, 0, -1, -1,
  	true, 'p', 'i', true, false, false, true, 0
  };
  
  static FormData_pg_attribute a4 = {
! 	0, {"cmin"}, CIDOID, 0, sizeof(CommandId),
  	MinCommandIdAttributeNumber, 0, -1, -1,
  	true, 'p', 'i', true, false, false, true, 0
  };
  
  static FormData_pg_attribute a5 = {
! 	0, {"xmax"}, XIDOID, 0, sizeof(TransactionId),
  	MaxTransactionIdAttributeNumber, 0, -1, -1,
  	true, 'p', 'i', true, false, false, true, 0
  };
  
  static FormData_pg_attribute a6 = {
! 	0, {"cmax"}, CIDOID, 0, sizeof(CommandId),
  	MaxCommandIdAttributeNumber, 0, -1, -1,
  	true, 'p', 'i', true, false, false, true, 0
  };
--- 126,162 ----
   */
  
  static FormData_pg_attribute a1 = {
! 	0, {"ctid"}, TIDOID, sizeof(ItemPointerData),
  	SelfItemPointerAttributeNumber, 0, -1, -1,
  	false, 'p', 's', true, false, false, true, 0
  };
  
  static FormData_pg_attribute a2 = {
! 	0, {"oid"}, OIDOID, sizeof(Oid),
  	ObjectIdAttributeNumber, 0, -1, -1,
  	true, 'p', 'i', true, false, false, true, 0
  };
  
  static FormData_pg_attribute a3 = {
! 	0, {"xmin"}, XIDOID, sizeof(TransactionId),
  	MinTransactionIdAttributeNumber, 0, -1, -1,
  	true, 'p', 'i', true, false, false, true, 0
  };
  
  static FormData_pg_attribute a4 = {
! 	0, {"cmin"}, CIDOID, sizeof(CommandId),
  	MinCommandIdAttributeNumber, 0, -1, -1,
  	true, 'p', 'i', true, false, false, true, 0
  };
  
  static FormData_pg_attribute a5 = {
! 	0, {"xmax"}, XIDOID, sizeof(TransactionId),
  	MaxTransactionIdAttributeNumber, 0, -1, -1,
  	true, 'p', 'i', true, false, false, true, 0
  };
  
  static FormData_pg_attribute a6 = {
! 	0, {"cmax"}, CIDOID, sizeof(CommandId),
  	MaxCommandIdAttributeNumber, 0, -1, -1,
  	true, 'p', 'i', true, false, false, true, 0
  };
*************** static FormData_pg_attribute a6 = {
*** 168,174 ****
   * used in SQL.
   */
  static FormData_pg_attribute a7 = {
! 	0, {"tableoid"}, OIDOID, 0, sizeof(Oid),
  	TableOidAttributeNumber, 0, -1, -1,
  	true, 'p', 'i', true, false, false, true, 0
  };
--- 168,174 ----
   * used in SQL.
   */
  static FormData_pg_attribute a7 = {
! 	0, {"tableoid"}, OIDOID, sizeof(Oid),
  	TableOidAttributeNumber, 0, -1, -1,
  	true, 'p', 'i', true, false, false, true, 0
  };
*************** InsertPgAttributeTuple(Relation pg_attri
*** 595,601 ****
  	values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_attribute->attrelid);
  	values[Anum_pg_attribute_attname - 1] = NameGetDatum(&new_attribute->attname);
  	values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attribute->atttypid);
- 	values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attribute->attstattarget);
  	values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attribute->attlen);
  	values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attribute->attnum);
  	values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attribute->attndims);
--- 595,600 ----
*************** AddNewAttributeTuples(Oid new_rel_oid,
*** 667,678 ****
  		attr = tupdesc->attrs[i];
  		/* Fill in the correct relation OID */
  		attr->attrelid = new_rel_oid;
! 		/* Make sure these are OK, too */
! 		attr->attstattarget = -1;
  		attr->attcacheoff = -1;
  
  		InsertPgAttributeTuple(rel, attr, indstate);
  
  		/* Add dependency info */
  		myself.classId = RelationRelationId;
  		myself.objectId = new_rel_oid;
--- 666,678 ----
  		attr = tupdesc->attrs[i];
  		/* Fill in the correct relation OID */
  		attr->attrelid = new_rel_oid;
! 		/* Make sure this is OK, too */
  		attr->attcacheoff = -1;
  
  		InsertPgAttributeTuple(rel, attr, indstate);
  
+ 		AddStatistics(new_rel_oid, &attr->attnum, 1, (oidinhcount > 0), -1);
+ 
  		/* Add dependency info */
  		myself.classId = RelationRelationId;
  		myself.objectId = new_rel_oid;
*************** RemoveAttributeById(Oid relid, AttrNumbe
*** 1486,1494 ****
  		/* Remove any NOT NULL constraint the column may have */
  		attStruct->attnotnull = false;
  
- 		/* We don't want to keep stats for it anymore */
- 		attStruct->attstattarget = 0;
- 
  		/*
  		 * Change the column name to something that isn't likely to conflict
  		 */
--- 1486,1491 ----
*************** RemoveAttributeById(Oid relid, AttrNumbe
*** 1510,1517 ****
  
  	heap_close(attr_rel, RowExclusiveLock);
  
  	if (attnum > 0)
! 		RemoveStatistics(relid, attnum);
  
  	relation_close(rel, NoLock);
  }
--- 1507,1515 ----
  
  	heap_close(attr_rel, RowExclusiveLock);
  
+ 	/* Only drop pg_statistic entries for non system columns. */
  	if (attnum > 0)
! 		RemoveStatistics(relid, &attnum, 1);
  
  	relation_close(rel, NoLock);
  }
*************** heap_drop_with_catalog(Oid relid)
*** 1735,1741 ****
  	/*
  	 * delete statistics
  	 */
! 	RemoveStatistics(relid, 0);
  
  	/*
  	 * delete attribute tuples
--- 1733,1739 ----
  	/*
  	 * delete statistics
  	 */
! 	RemoveStatistics(relid, NULL, 0);
  
  	/*
  	 * delete attribute tuples
*************** cookConstraint(ParseState *pstate,
*** 2516,2534 ****
  
  
  /*
!  * RemoveStatistics --- remove entries in pg_statistic for a rel or column
   *
!  * If attnum is zero, remove all entries for rel; else remove only the one(s)
!  * for that column.
   */
  void
! RemoveStatistics(Oid relid, AttrNumber attnum)
  {
  	Relation	pgstatistic;
  	SysScanDesc scan;
  	ScanKeyData key[2];
  	int			nkeys;
  	HeapTuple	tuple;
  
  	pgstatistic = heap_open(StatisticRelationId, RowExclusiveLock);
  
--- 2514,2727 ----
  
  
  /*
!  * AddStatistics --- add an entry in pg_statistic
   *
!  * attnums		- an ordered array of AttrNumbers
!  * n_attnum		- number of elements in the array
!  * statistics_target	- the sampling size for this statistics
!  *
!  * Entries in pg_statistic are used by the planner to collect selectivity values.
!  * This function is called when a new relation is created or a new column is added
!  * to a relation. It is therefore ensured that every column has an entry during the
!  * lifetime of the relation since its creation. There is one exception from under
!  * this rule: thus function is a no-op during bootstrapping to avoid a catch-22
!  * situation where a pg_statistic entry would be created when pg_statistic itself
!  * doesn't exist yet. pg_statistic entries for system tables will be created by
!  * ANALYZE as before. The entry is created as invalid (stavalid == false) and
!  * the histogram columns are NULLs. This will also be fixed by ANALYZE.
   */
  void
! AddStatistics(Oid relid, AttrNumber *attnums, int n_attnums, bool inherited, int statistics_target)
! {
! 	Relation	rel;
! 	ScanKeyData	scanKey[2];
! 	SysScanDesc	scan;
! 	int2vector *attnumvector;
! 	HeapTuple	tuple;
! 	TupleDesc	tupDesc;
! 	Datum		values[Natts_pg_statistic];
! 	bool		nulls[Natts_pg_statistic];
! 	int		i, j;
! 
! 	if (IsBootstrapProcessingMode())
! 		return;
! 
! 	Assert(attnums != NULL);
! 	Assert(n_attnums > 0);
! 
! 	attnumvector = buildint2vector(attnums, n_attnums);
! 
! 	rel = heap_open(StatisticRelationId, RowExclusiveLock);
! 
! 	ScanKeyInit(&scanKey[0],
! 					Anum_pg_statistic_starelid,
! 					BTEqualStrategyNumber, F_OIDEQ,
! 					ObjectIdGetDatum(relid));
! 	ScanKeyInit(&scanKey[1],
! 					Anum_pg_statistic_staattnums,
! 					BTEqualStrategyNumber, F_ARRAY_EQ,
! 					PointerGetDatum(attnumvector));
! 
! 	scan = systable_beginscan(rel, StatisticRelidAttnumsInhIndexId, true,
! 									SnapshotNow, 2, scanKey);
! 
! 	tuple = systable_getnext(scan);
! 	if (HeapTupleIsValid(tuple))
! 	{
! 		systable_endscan(scan);
! 		elog(ERROR, "pg_statistic entry already exists for this table and set of columns");
! 	}
! 
! 	systable_endscan(scan);
! 
! 	for (i = 0; i < Natts_pg_statistic; i++)
! 		nulls[i] = true;
! 
! 	i = 0;
! 	values[i] = ObjectIdGetDatum(relid);		nulls[i++] = false;	/* starelid */
! 	values[i] = BoolGetDatum(inherited);		nulls[i++] = false;	/* stainherit */
! 	values[i] = BoolGetDatum(false);		nulls[i++] = false;	/* stavalid */
! 	values[i] = Int32GetDatum(statistics_target);	nulls[i++] = false;	/* statarget */
! 	values[i] = Float4GetDatum(0);			nulls[i++] = false;	/* stanullfrac */
! 	values[i] = Int32GetDatum(0);			nulls[i++] = false;	/* stawidth */
! 	values[i] = Float4GetDatum(0);			nulls[i++] = false;	/* stadistinct */
! 	for (j = 0; j < STATISTIC_NUM_SLOTS; j++)
! 	{
! 		values[i] = Int16GetDatum(0);		nulls[i++] = false;	/* stakindN */
! 	}
! 	for (j = 0; j < STATISTIC_NUM_SLOTS; j++)
! 	{
! 		values[i] = ObjectIdGetDatum(0);	nulls[i++] = false;	/* staopN */
! 	}
! 	values[i] = PointerGetDatum(attnumvector);	nulls[i++] = false;	/* stainherit */
! 
! 	tupDesc = RelationGetDescr(rel);
! 
! 	tuple = heap_form_tuple(tupDesc, values, nulls);
! 
! 	simple_heap_insert(rel, tuple);
! 
! 	CatalogUpdateIndexes(rel, tuple);
! 
! 	pfree(attnumvector);
! 
! 	relation_close(rel, RowExclusiveLock);
! }
! 
! typedef struct invalidate_stats {
! 	HeapTuple	tuple;
! 	struct invalidate_stats *next;
! } invalidate_stats;
! 
! /*
!  * InvalidateStatistics --- invalidate all pg_statistic entries of this attnum
!  *
!  * We need to collect copies of tuples that need invalidating in order not to
!  * conflict with the system table scan.
!  */
! void
! InvalidateStatistics(Oid relid, AttrNumber attnum)
! {
! 	Relation		rel;
! 	ScanKeyData		scanKey;
! 	SysScanDesc		scan;
! 	HeapTuple		tuple;
! 	TupleDesc		tupDesc;
! 	Form_pg_statistic	stattuple;
! 	invalidate_stats   *tupptr;
! 	invalidate_stats   *tupptr_next;
! 	int			i;
! 
! 	if (IsBootstrapProcessingMode())
! 		return;
! 
! 	rel = heap_open(StatisticRelationId, RowExclusiveLock);
! 	tupDesc = RelationGetDescr(rel);
! 
! 	ScanKeyInit(&scanKey,
! 					Anum_pg_statistic_starelid,
! 					BTEqualStrategyNumber, F_OIDEQ,
! 					ObjectIdGetDatum(relid));
! 
! 	scan = systable_beginscan(rel, StatisticRelidAttnumsInhIndexId, true,
! 									SnapshotNow, 1, &scanKey);
! 
! 
! 	tupptr = tupptr_next = NULL;
! 	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
! 	{
! 		bool	isnull;
! 		Datum	attnvec;
! 		int2vector *attnumvector;
! 
! 		attnvec = heap_getattr(tuple, Anum_pg_statistic_staattnums, tupDesc, &isnull);
! 
! 		Assert(!isnull);
! 
! 		attnumvector = (int2vector *) DatumGetPointer(attnvec);
! 
! 		for (i = 0; i < attnumvector->dim1; i++)
! 		{
! 			if (attnumvector->values[i] == attnum)
! 			{
! 				invalidate_stats *tmp;
! 
! 
! 				tmp = palloc(sizeof(invalidate_stats));
! 				tmp->tuple = heap_copytuple(tuple);
! 				tmp->next = NULL;
! 
! 				if (tupptr == NULL)
! 					tupptr = tupptr_next = tmp;
! 				else
! 				{
! 					tupptr_next->next = tmp;
! 					tupptr_next = tmp;
! 				}
! 
! 				break; /* find next tuple */
! 			}
! 		}
! 	}
! 
! 	systable_endscan(scan);
! 
! 	while (tupptr)
! 	{
! 		stattuple = (Form_pg_statistic) GETSTRUCT(tupptr->tuple);
! 
! 		stattuple->stavalid = false;
! 
! 		simple_heap_update(rel, &tupptr->tuple->t_self, tupptr->tuple);
! 
! 		CatalogUpdateIndexes(rel, tupptr->tuple);
! 
! 		tupptr_next = tupptr->next;
! 
! 		heap_freetuple(tupptr->tuple);
! 		pfree(tupptr);
! 
! 		tupptr = tupptr_next;
! 	}
! 
! 	relation_close(rel, RowExclusiveLock);
! }
! 
! /*
!  * RemoveStatistics --- remove entries in pg_statistic for a rel's set of columns
!  *
!  * If attnums is NULL, remove all entries for rel; else remove only the one
!  * for that set of column(s).
!  */
! void
! RemoveStatistics(Oid relid, AttrNumber *attnums, int n_attnums)
  {
  	Relation	pgstatistic;
  	SysScanDesc scan;
  	ScanKeyData key[2];
  	int			nkeys;
  	HeapTuple	tuple;
+ 	int2vector *attnumvector = NULL;
  
  	pgstatistic = heap_open(StatisticRelationId, RowExclusiveLock);
  
*************** RemoveStatistics(Oid relid, AttrNumber a
*** 2537,2554 ****
  				BTEqualStrategyNumber, F_OIDEQ,
  				ObjectIdGetDatum(relid));
  
! 	if (attnum == 0)
  		nkeys = 1;
  	else
  	{
  		ScanKeyInit(&key[1],
! 					Anum_pg_statistic_staattnum,
! 					BTEqualStrategyNumber, F_INT2EQ,
! 					Int16GetDatum(attnum));
  		nkeys = 2;
  	}
  
! 	scan = systable_beginscan(pgstatistic, StatisticRelidAttnumInhIndexId, true,
  							  SnapshotNow, nkeys, key);
  
  	/* we must loop even when attnum != 0, in case of inherited stats */
--- 2730,2748 ----
  				BTEqualStrategyNumber, F_OIDEQ,
  				ObjectIdGetDatum(relid));
  
! 	if (attnums == NULL)
  		nkeys = 1;
  	else
  	{
+ 		attnumvector = buildint2vector(attnums, n_attnums);
  		ScanKeyInit(&key[1],
! 					Anum_pg_statistic_staattnums,
! 					BTEqualStrategyNumber, F_INT2VECTOREQ,
! 					PointerGetDatum(attnumvector));
  		nkeys = 2;
  	}
  
! 	scan = systable_beginscan(pgstatistic, StatisticRelidAttnumsInhIndexId, true,
  							  SnapshotNow, nkeys, key);
  
  	/* we must loop even when attnum != 0, in case of inherited stats */
*************** RemoveStatistics(Oid relid, AttrNumber a
*** 2557,2562 ****
--- 2751,2759 ----
  
  	systable_endscan(scan);
  
+ 	if (attnumvector)
+ 		pfree(attnumvector);
+ 
  	heap_close(pgstatistic, RowExclusiveLock);
  }
  
diff -dcrpN postgresql.orig/src/backend/catalog/index.c postgresql/src/backend/catalog/index.c
*** postgresql.orig/src/backend/catalog/index.c	2011-09-05 15:08:06.485569429 +0200
--- postgresql/src/backend/catalog/index.c	2011-09-12 10:01:16.461512196 +0200
*************** ConstructTupleDescriptor(Relation heapRe
*** 342,348 ****
  			 */
  			to->attnum = i + 1;
  
- 			to->attstattarget = -1;
  			to->attcacheoff = -1;
  			to->attnotnull = false;
  			to->atthasdef = false;
--- 342,347 ----
*************** ConstructTupleDescriptor(Relation heapRe
*** 380,386 ****
  			to->attbyval = typeTup->typbyval;
  			to->attstorage = typeTup->typstorage;
  			to->attalign = typeTup->typalign;
- 			to->attstattarget = -1;
  			to->attcacheoff = -1;
  			to->atttypmod = -1;
  			to->attislocal = true;
--- 379,384 ----
*************** index_drop(Oid indexId)
*** 1352,1358 ****
  	 * them.
  	 */
  	if (hasexprs)
! 		RemoveStatistics(indexId, 0);
  
  	/*
  	 * fix ATTRIBUTE relation
--- 1350,1356 ----
  	 * them.
  	 */
  	if (hasexprs)
! 		RemoveStatistics(indexId, NULL, 0);
  
  	/*
  	 * fix ATTRIBUTE relation
diff -dcrpN postgresql.orig/src/backend/catalog/system_views.sql postgresql/src/backend/catalog/system_views.sql
*** postgresql.orig/src/backend/catalog/system_views.sql	2011-07-24 18:16:45.262679185 +0200
--- postgresql/src/backend/catalog/system_views.sql	2011-09-12 10:01:16.495510607 +0200
*************** CREATE VIEW pg_stats AS
*** 141,147 ****
              WHEN stakind4 = 3 THEN stanumbers4[1]
          END AS correlation
      FROM pg_statistic s JOIN pg_class c ON (c.oid = s.starelid)
!          JOIN pg_attribute a ON (c.oid = attrelid AND attnum = s.staattnum)
           LEFT JOIN pg_namespace n ON (n.oid = c.relnamespace)
      WHERE NOT attisdropped AND has_column_privilege(c.oid, a.attnum, 'select');
  
--- 141,147 ----
              WHEN stakind4 = 3 THEN stanumbers4[1]
          END AS correlation
      FROM pg_statistic s JOIN pg_class c ON (c.oid = s.starelid)
!          JOIN pg_attribute a ON (c.oid = attrelid AND array_length(s.staattnums, 1) = 1 AND attnum = s.staattnums[0])
           LEFT JOIN pg_namespace n ON (n.oid = c.relnamespace)
      WHERE NOT attisdropped AND has_column_privilege(c.oid, a.attnum, 'select');
  
diff -dcrpN postgresql.orig/src/backend/commands/analyze.c postgresql/src/backend/commands/analyze.c
*** postgresql.orig/src/backend/commands/analyze.c	2011-09-12 09:54:31.169314275 +0200
--- postgresql/src/backend/commands/analyze.c	2011-09-13 11:15:14.977046160 +0200
***************
*** 20,31 ****
--- 20,34 ----
  #include "access/tupconvert.h"
  #include "access/tuptoaster.h"
  #include "access/xact.h"
+ #include "catalog/heap.h"
  #include "catalog/index.h"
  #include "catalog/indexing.h"
+ #include "catalog/namespace.h"
  #include "catalog/pg_collation.h"
  #include "catalog/pg_inherits_fn.h"
  #include "catalog/pg_namespace.h"
  #include "commands/dbcommands.h"
+ #include "commands/defrem.h"
  #include "commands/tablecmds.h"
  #include "commands/vacuum.h"
  #include "executor/executor.h"
***************
*** 41,47 ****
--- 44,52 ----
  #include "storage/procarray.h"
  #include "utils/acl.h"
  #include "utils/attoptcache.h"
+ #include "utils/builtins.h"
  #include "utils/datum.h"
+ #include "utils/fmgroids.h"
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
*************** typedef struct AnlIndexData
*** 68,75 ****
  {
  	IndexInfo  *indexInfo;		/* BuildIndexInfo result */
  	double		tupleFract;		/* fraction of rows for partial index */
! 	VacAttrStats **vacattrstats;	/* index attrs to analyze */
! 	int			attr_cnt;
  } AnlIndexData;
  
  
--- 73,82 ----
  {
  	IndexInfo  *indexInfo;		/* BuildIndexInfo result */
  	double		tupleFract;		/* fraction of rows for partial index */
! 	VacStats **vacstats;		/* index attrs to analyze */
! 	int			stats_cnt;	/* this many stats we need to compute - single and cross-column stats included */
! 	int			attr_cnt;	/* the relation has attr_cnt number of columns */
! 	bool			has_xcol_stats;
  } AnlIndexData;
  
  
*************** static void compute_index_stats(Relation
*** 93,100 ****
  					AnlIndexData *indexdata, int nindexes,
  					HeapTuple *rows, int numrows,
  					MemoryContext col_context);
! static VacAttrStats *examine_attribute(Relation onerel, int attnum,
! 				  Node *index_expr);
  static int acquire_sample_rows(Relation onerel, HeapTuple *rows,
  					int targrows, double *totalrows, double *totaldeadrows);
  static double random_fract(void);
--- 100,107 ----
  					AnlIndexData *indexdata, int nindexes,
  					HeapTuple *rows, int numrows,
  					MemoryContext col_context);
! static VacStats *examine_attribute(Relation onerel, int2vector *attnums, bool inh,
! 				  Node **index_expr);
  static int acquire_sample_rows(Relation onerel, HeapTuple *rows,
  					int targrows, double *totalrows, double *totaldeadrows);
  static double random_fract(void);
*************** static int acquire_inherited_sample_rows
*** 105,116 ****
  							  HeapTuple *rows, int targrows,
  							  double *totalrows, double *totaldeadrows);
  static void update_attstats(Oid relid, bool inh,
! 				int natts, VacAttrStats **vacattrstats);
! static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
! static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
  
! static bool std_typanalyze(VacAttrStats *stats);
  
  
  /*
   *	analyze_rel() -- analyze one relation
--- 112,129 ----
  							  HeapTuple *rows, int targrows,
  							  double *totalrows, double *totaldeadrows);
  static void update_attstats(Oid relid, bool inh,
! 				int natts, VacStats **vacstats);
! static Datum std_fetch_func(VacStatsP stats, int rownum, AttrNumber tupattnum,
! 							  bool *isNull);
! static Datum ind_fetch_func(VacStatsP stats, int rownum, AttrNumber tupattnum,
! 							  bool *isNull);
  
! static bool std_typanalyze(VacStats *stats, int index);
  
+ static int ordered_findval(VacStatsP stats, int rownum, int index, AnalyzeAttrFetchFunc fetchfunc, void *arg);
+ static int unordered_findval(VacStatsP stats, int rownum, int index, AnalyzeAttrFetchFunc fetchfunc, void *arg);
+ static void compute_cross_column_stats(VacStatsP stats, AnalyzeAttrFetchFunc fetchfunc,
+ 							  int numrows);
  
  /*
   *	analyze_rel() -- analyze one relation
*************** analyze_rel(Oid relid, VacuumStmt *vacst
*** 253,272 ****
  	LWLockRelease(ProcArrayLock);
  }
  
  /*
   *	do_analyze_rel() -- analyze one relation, recursively or not
   */
  static void
  do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh)
  {
! 	int			attr_cnt,
  				tcnt,
  				i,
  				ind;
  	Relation   *Irel;
  	int			nindexes;
  	bool		hasindex;
! 	VacAttrStats **vacattrstats;
  	AnlIndexData *indexdata;
  	int			targrows,
  				numrows;
--- 266,343 ----
  	LWLockRelease(ProcArrayLock);
  }
  
+ static int
+ get_extrastats_count(Oid relid, int2vector ***out_attnumvectors)
+ {
+ 	Relation	pgstatistic;
+ 	TupleDesc	tupDesc;
+ 	SysScanDesc	scan;
+ 	ScanKeyData	key;
+ 	HeapTuple	tuple;
+ 	int		count = 0;
+ 	bool		isnull;
+ 	int2vector *attnumvector = NULL;
+ 	int2vector **attnumvectors = NULL;
+ 
+ 	pgstatistic = heap_open(StatisticRelationId, RowExclusiveLock);
+ 	tupDesc = RelationGetDescr(pgstatistic);
+ 
+ 	ScanKeyInit(&key,
+ 				Anum_pg_statistic_starelid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(relid));
+ 
+ 	scan = systable_beginscan(pgstatistic, StatisticRelidAttnumsInhIndexId, true,
+ 								SnapshotNow, 1, &key);
+ 
+ 	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ 	{
+ 		Datum	attnvec = heap_getattr(tuple, Anum_pg_statistic_staattnums, tupDesc, &isnull);
+ 
+ 		Assert(!isnull);
+ 
+ 		attnumvector = (int2vector *) DatumGetPointer(attnvec);
+ 
+ 		if (attnumvector->dim1 > 1)
+ 		{
+ 			count++;
+ 			if (out_attnumvectors)
+ 			{
+ 				if (attnumvectors == NULL)
+ 					attnumvectors = palloc(count * sizeof(int2vector *));
+ 				else
+ 					attnumvectors = repalloc(attnumvectors, count * sizeof(int2vector *));
+ 				attnumvectors[count - 1] = (int2vector *)datumCopy(attnvec, false, -1);
+ 			}
+ 		}
+ 	}
+ 
+ 	systable_endscan(scan);
+ 	                                                                                                  
+ 	heap_close(pgstatistic, RowExclusiveLock);
+ 
+ 	if (out_attnumvectors)
+ 		*out_attnumvectors = attnumvectors;
+ 
+ 	return count;
+ }
+ 
  /*
   *	do_analyze_rel() -- analyze one relation, recursively or not
   */
  static void
  do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh)
  {
! 	int			stats_cnt,
! 				extrastats_cnt,
  				tcnt,
  				i,
  				ind;
  	Relation   *Irel;
  	int			nindexes;
  	bool		hasindex;
! 	VacStats **vacstats;
! 	int2vector   **extrastats_attnums;
  	AnlIndexData *indexdata;
  	int			targrows,
  				numrows;
*************** do_analyze_rel(Relation onerel, VacuumSt
*** 329,366 ****
  	{
  		ListCell   *le;
  
! 		vacattrstats = (VacAttrStats **) palloc(list_length(vacstmt->va_cols) *
! 												sizeof(VacAttrStats *));
  		tcnt = 0;
  		foreach(le, vacstmt->va_cols)
  		{
  			char	   *col = strVal(lfirst(le));
  
! 			i = attnameAttNum(onerel, col, false);
! 			if (i == InvalidAttrNumber)
  				ereport(ERROR,
  						(errcode(ERRCODE_UNDEFINED_COLUMN),
  					errmsg("column \"%s\" of relation \"%s\" does not exist",
  						   col, RelationGetRelationName(onerel))));
! 			vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
! 			if (vacattrstats[tcnt] != NULL)
  				tcnt++;
  		}
! 		attr_cnt = tcnt;
  	}
  	else
  	{
! 		attr_cnt = onerel->rd_att->natts;
! 		vacattrstats = (VacAttrStats **)
! 			palloc(attr_cnt * sizeof(VacAttrStats *));
  		tcnt = 0;
! 		for (i = 1; i <= attr_cnt; i++)
  		{
! 			vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
! 			if (vacattrstats[tcnt] != NULL)
  				tcnt++;
  		}
! 		attr_cnt = tcnt;
  	}
  
  	/*
--- 400,474 ----
  	{
  		ListCell   *le;
  
! 		vacstats = (VacStats **) palloc(list_length(vacstmt->va_cols) *
! 												sizeof(VacStats *));
  		tcnt = 0;
  		foreach(le, vacstmt->va_cols)
  		{
  			char	   *col = strVal(lfirst(le));
+ 			AttrNumber attnum;
+ 			int2vector *attnums;
  
! 			attnum = attnameAttNum(onerel, col, false);
! 			if (attnum == InvalidAttrNumber)
  				ereport(ERROR,
  						(errcode(ERRCODE_UNDEFINED_COLUMN),
  					errmsg("column \"%s\" of relation \"%s\" does not exist",
  						   col, RelationGetRelationName(onerel))));
! 			attnums = buildint2vector(&attnum, 1);
! 			vacstats[tcnt] = examine_attribute(onerel, attnums, inh, NULL);
! 			if (vacstats[tcnt] != NULL)
  				tcnt++;
+ 			else
+ 				pfree(attnums);
  		}
! 		stats_cnt = tcnt;
! 		extrastats_cnt = 0;
  	}
  	else
  	{
! 		/*
! 		 * Process all attributes of the relation unconditionally
! 		 * as system tables may not have pg_statistic entries at
! 		 * this point, they will be created in update_attstats().
! 		 */
! 		stats_cnt = onerel->rd_att->natts;
! 
! 		extrastats_cnt = get_extrastats_count(onerel->rd_id, &extrastats_attnums);
! 
! 		vacstats = (VacStats **)
! 			palloc((stats_cnt + extrastats_cnt) * sizeof(VacStats *));
  		tcnt = 0;
! 		for (i = 1; i <= stats_cnt; i++)
  		{
! 			AttrNumber attnum = i;
! 			int2vector *attnums;
! 
! 			attnums = buildint2vector(&attnum, 1);
! 
! 			vacstats[tcnt] = examine_attribute(onerel, attnums, inh, NULL);
! 			if (vacstats[tcnt] != NULL)
  				tcnt++;
+ 			else
+ 				pfree(attnums);
  		}
! 
! 		/* Now process the extra statistics */
! 		if (extrastats_attnums)
! 		{
! 			for (i = 0; extrastats_attnums && i < extrastats_cnt; i++)
! 			{
! 				vacstats[tcnt] = examine_attribute(onerel, extrastats_attnums[i], inh, NULL);
! 				if (vacstats[tcnt] != NULL)
! 					tcnt++;
! 				else
! 					pfree(extrastats_attnums[i]);
! 			}
! 			pfree(extrastats_attnums);
! 			extrastats_attnums = NULL;
! 		}
! 
! 		stats_cnt = tcnt;
  	}
  
  	/*
*************** do_analyze_rel(Relation onerel, VacuumSt
*** 389,401 ****
  
  			thisdata->indexInfo = indexInfo = BuildIndexInfo(Irel[ind]);
  			thisdata->tupleFract = 1.0; /* fix later if partial */
  			if (indexInfo->ii_Expressions != NIL && vacstmt->va_cols == NIL)
  			{
  				ListCell   *indexpr_item = list_head(indexInfo->ii_Expressions);
  
! 				thisdata->vacattrstats = (VacAttrStats **)
! 					palloc(indexInfo->ii_NumIndexAttrs * sizeof(VacAttrStats *));
! 				tcnt = 0;
  				for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
  				{
  					int			keycol = indexInfo->ii_KeyAttrNumbers[i];
--- 497,516 ----
  
  			thisdata->indexInfo = indexInfo = BuildIndexInfo(Irel[ind]);
  			thisdata->tupleFract = 1.0; /* fix later if partial */
+ 
+ 			tcnt = 0;
+ 			extrastats_cnt = get_extrastats_count(Irel[ind]->rd_id, &extrastats_attnums);
+ 
+ 			/*
+ 			 * Process expression index single-attribute statistics.
+ 			 */
  			if (indexInfo->ii_Expressions != NIL && vacstmt->va_cols == NIL)
  			{
  				ListCell   *indexpr_item = list_head(indexInfo->ii_Expressions);
  
! 				thisdata->vacstats = (VacStats **)
! 					palloc((indexInfo->ii_NumIndexAttrs + extrastats_cnt) * sizeof(VacStats *));
! 
  				for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
  				{
  					int			keycol = indexInfo->ii_KeyAttrNumbers[i];
*************** do_analyze_rel(Relation onerel, VacuumSt
*** 404,422 ****
  					{
  						/* Found an index expression */
  						Node	   *indexkey;
  
  						if (indexpr_item == NULL)		/* shouldn't happen */
  							elog(ERROR, "too few entries in indexprs list");
  						indexkey = (Node *) lfirst(indexpr_item);
  						indexpr_item = lnext(indexpr_item);
! 						thisdata->vacattrstats[tcnt] =
! 							examine_attribute(Irel[ind], i + 1, indexkey);
! 						if (thisdata->vacattrstats[tcnt] != NULL)
  							tcnt++;
  					}
  				}
! 				thisdata->attr_cnt = tcnt;
  			}
  		}
  	}
  
--- 519,592 ----
  					{
  						/* Found an index expression */
  						Node	   *indexkey;
+ 						AttrNumber	attnum;
+ 						int2vector *attnums;
  
  						if (indexpr_item == NULL)		/* shouldn't happen */
  							elog(ERROR, "too few entries in indexprs list");
  						indexkey = (Node *) lfirst(indexpr_item);
  						indexpr_item = lnext(indexpr_item);
! 
! 						attnum = i + 1;
! 						attnums = buildint2vector(&attnum, 1);
! 
! 						thisdata->vacstats[tcnt] =
! 							examine_attribute(Irel[ind], attnums, inh, &indexkey);
! 						if (thisdata->vacstats[tcnt] != NULL)
  							tcnt++;
+ 						else
+ 							pfree(attnums);
  					}
  				}
! 
  			}
+ 
+ 			/*
+ 			 * Process cross-column statistics. There can only be at most one
+ 			 * multi-column statistics for an index that covers every attributes.
+ 			 */
+ 			if (extrastats_attnums && vacstmt->va_cols == NIL)
+ 			{
+ 				Node	   *indexkeys[STATISTIC_NUM_SLOTS];
+ 
+ 				if (thisdata->vacstats == NULL)
+ 					thisdata->vacstats = (VacStats **) palloc(sizeof(VacStats *));
+ 
+ 				/*
+ 				 * Don't process indexes with too many attributes.
+ 				 * CREATE EXTRA STATISTICS bails out for such a case
+ 				 * but better safe than sorry.
+ 				 */
+ 				if (indexInfo->ii_NumIndexAttrs <= STATISTIC_NUM_SLOTS)
+ 				{
+ 					ListCell   *indexpr_item = list_head(indexInfo->ii_Expressions);
+ 
+ 					for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
+ 					{
+ 						int	keycol = indexInfo->ii_KeyAttrNumbers[i];
+ 						if (keycol == 0)
+ 						{
+ 							indexkeys[i] = (Node *) lfirst(indexpr_item);
+ 							indexpr_item = lnext(indexpr_item);
+ 						}
+ 						else
+ 							indexkeys[i] = NULL;
+ 					}
+ 
+ 					thisdata->vacstats[tcnt] = examine_attribute(onerel, extrastats_attnums[0], inh, indexkeys);
+ 					if (thisdata->vacstats[tcnt] != NULL)
+ 						tcnt++;
+ 					else
+ 						pfree(extrastats_attnums[i]);
+ 				}
+ 				pfree(extrastats_attnums);
+ 				extrastats_attnums = NULL;
+ 
+ 				thisdata->has_xcol_stats = true;
+ 			}
+ 
+ 			thisdata->stats_cnt = tcnt;
+ 			thisdata->attr_cnt = indexInfo->ii_NumIndexAttrs;
  		}
  	}
  
*************** do_analyze_rel(Relation onerel, VacuumSt
*** 427,445 ****
  	 * the target in the corner case where there are no analyzable columns.)
  	 */
  	targrows = 100;
! 	for (i = 0; i < attr_cnt; i++)
  	{
! 		if (targrows < vacattrstats[i]->minrows)
! 			targrows = vacattrstats[i]->minrows;
  	}
  	for (ind = 0; ind < nindexes; ind++)
  	{
  		AnlIndexData *thisdata = &indexdata[ind];
  
! 		for (i = 0; i < thisdata->attr_cnt; i++)
  		{
! 			if (targrows < thisdata->vacattrstats[i]->minrows)
! 				targrows = thisdata->vacattrstats[i]->minrows;
  		}
  	}
  
--- 597,615 ----
  	 * the target in the corner case where there are no analyzable columns.)
  	 */
  	targrows = 100;
! 	for (i = 0; i < stats_cnt; i++)
  	{
! 		if (targrows < vacstats[i]->minrows)
! 			targrows = vacstats[i]->minrows;
  	}
  	for (ind = 0; ind < nindexes; ind++)
  	{
  		AnlIndexData *thisdata = &indexdata[ind];
  
! 		for (i = 0; i < thisdata->stats_cnt; i++)
  		{
! 			if (targrows < thisdata->vacstats[i]->minrows)
! 				targrows = thisdata->vacstats[i]->minrows;
  		}
  	}
  
*************** do_analyze_rel(Relation onerel, VacuumSt
*** 457,463 ****
  	/*
  	 * Compute the statistics.	Temporary results during the calculations for
  	 * each column are stored in a child context.  The calc routines are
! 	 * responsible to make sure that whatever they store into the VacAttrStats
  	 * structure is allocated in anl_context.
  	 */
  	if (numrows > 0)
--- 627,633 ----
  	/*
  	 * Compute the statistics.	Temporary results during the calculations for
  	 * each column are stored in a child context.  The calc routines are
! 	 * responsible to make sure that whatever they store into the VacStats
  	 * structure is allocated in anl_context.
  	 */
  	if (numrows > 0)
*************** do_analyze_rel(Relation onerel, VacuumSt
*** 472,504 ****
  											ALLOCSET_DEFAULT_MAXSIZE);
  		old_context = MemoryContextSwitchTo(col_context);
  
! 		for (i = 0; i < attr_cnt; i++)
  		{
! 			VacAttrStats *stats = vacattrstats[i];
! 			AttributeOpts *aopt =
! 			get_attribute_options(onerel->rd_id, stats->attr->attnum);
  
  			stats->rows = rows;
  			stats->tupDesc = onerel->rd_att;
! 			(*stats->compute_stats) (stats,
  									 std_fetch_func,
  									 numrows,
  									 totalrows);
  
! 			/*
! 			 * If the appropriate flavor of the n_distinct option is
! 			 * specified, override with the corresponding value.
! 			 */
! 			if (aopt != NULL)
! 			{
! 				float8		n_distinct =
! 				inh ? aopt->n_distinct_inherited : aopt->n_distinct;
  
! 				if (n_distinct != 0.0)
! 					stats->stadistinct = n_distinct;
  			}
  
  			MemoryContextResetAndDeleteChildren(col_context);
  		}
  
  		if (hasindex)
--- 642,683 ----
  											ALLOCSET_DEFAULT_MAXSIZE);
  		old_context = MemoryContextSwitchTo(col_context);
  
! 		for (i = 0; i < stats_cnt; i++)
  		{
! 			VacStats *stats = vacstats[i];
! 			AttributeOpts *aopt;
! 			int		j;
  
  			stats->rows = rows;
  			stats->tupDesc = onerel->rd_att;
! 
! 			for (j = 0; j < stats->attnums->dim1; j++)
! 			{
! 				aopt = get_attribute_options(onerel->rd_id, stats->attnums->values[j]);
! 
! 				(*stats->statfuncs[j].compute_func_ptr) (stats,
! 									 j, 
  									 std_fetch_func,
  									 numrows,
  									 totalrows);
  
! 				/*
! 				 * If the appropriate flavor of the n_distinct option is
! 				 * specified, override with the corresponding value.
! 				 */
! 				if (aopt != NULL)
! 				{
! 					float8		n_distinct =
! 					inh ? aopt->n_distinct_inherited : aopt->n_distinct;
  
! 					if (n_distinct != 0.0)
! 						stats->stadistinct[j] = n_distinct;
! 				}
  			}
  
  			MemoryContextResetAndDeleteChildren(col_context);
+ 
+ 			compute_cross_column_stats(stats, std_fetch_func, numrows);
  		}
  
  		if (hasindex)
*************** do_analyze_rel(Relation onerel, VacuumSt
*** 516,529 ****
  		 * pg_statistic for columns we didn't process, we leave them alone.)
  		 */
  		update_attstats(RelationGetRelid(onerel), inh,
! 						attr_cnt, vacattrstats);
  
  		for (ind = 0; ind < nindexes; ind++)
  		{
  			AnlIndexData *thisdata = &indexdata[ind];
  
  			update_attstats(RelationGetRelid(Irel[ind]), false,
! 							thisdata->attr_cnt, thisdata->vacattrstats);
  		}
  	}
  
--- 695,711 ----
  		 * pg_statistic for columns we didn't process, we leave them alone.)
  		 */
  		update_attstats(RelationGetRelid(onerel), inh,
! 						stats_cnt, vacstats);
  
  		for (ind = 0; ind < nindexes; ind++)
  		{
  			AnlIndexData *thisdata = &indexdata[ind];
  
+ 			for (i = 0; i < thisdata->stats_cnt; i++)
+ 				compute_cross_column_stats(thisdata->vacstats[i], ind_fetch_func, numrows);
+ 
  			update_attstats(RelationGetRelid(Irel[ind]), false,
! 							thisdata->stats_cnt, thisdata->vacstats);
  		}
  	}
  
*************** compute_index_stats(Relation onerel, dou
*** 627,632 ****
--- 809,816 ----
  				old_context;
  	Datum		values[INDEX_MAX_KEYS];
  	bool		isnull[INDEX_MAX_KEYS];
+ 	bool		typbyval[INDEX_MAX_KEYS];
+ 	int16		typlen[INDEX_MAX_KEYS];
  	int			ind,
  				i;
  
*************** compute_index_stats(Relation onerel, dou
*** 641,647 ****
  	{
  		AnlIndexData *thisdata = &indexdata[ind];
  		IndexInfo  *indexInfo = thisdata->indexInfo;
! 		int			attr_cnt = thisdata->attr_cnt;
  		TupleTableSlot *slot;
  		EState	   *estate;
  		ExprContext *econtext;
--- 825,831 ----
  	{
  		AnlIndexData *thisdata = &indexdata[ind];
  		IndexInfo  *indexInfo = thisdata->indexInfo;
! 		int			stats_cnt = thisdata->stats_cnt;
  		TupleTableSlot *slot;
  		EState	   *estate;
  		ExprContext *econtext;
*************** compute_index_stats(Relation onerel, dou
*** 652,662 ****
  					tcnt,
  					rowno;
  		double		totalindexrows;
  
  		/* Ignore index if no columns to analyze and not partial */
! 		if (attr_cnt == 0 && indexInfo->ii_Predicate == NIL)
  			continue;
  
  		/*
  		 * Need an EState for evaluation of index expressions and
  		 * partial-index predicates.  Create it in the per-index context to be
--- 836,865 ----
  					tcnt,
  					rowno;
  		double		totalindexrows;
+ 		ListCell   *indexpr_item = list_head(indexInfo->ii_Expressions);
  
  		/* Ignore index if no columns to analyze and not partial */
! 		if (stats_cnt == 0 && indexInfo->ii_Predicate == NIL)
  			continue;
  
+ 		for (i = 0; i < thisdata->attr_cnt; i++)
+ 		{
+ 			int	keycol = indexInfo->ii_KeyAttrNumbers[i];
+ 
+ 			if (keycol == 0)
+ 			{
+ 				Node *indexkey = (Node *) lfirst(indexpr_item);
+ 
+ 				get_typlenbyval(exprType(indexkey), &typlen[i], &typbyval[i]);
+ 				indexpr_item = lnext(indexpr_item);
+ 			}
+ 			else
+ 			{
+ 				typbyval[i] = onerel->rd_att->attrs[keycol - 1]->attbyval;
+ 				typlen[i] = onerel->rd_att->attrs[keycol - 1]->attlen;
+ 			}
+ 		}
+ 
  		/*
  		 * Need an EState for evaluation of index expressions and
  		 * partial-index predicates.  Create it in the per-index context to be
*************** compute_index_stats(Relation onerel, dou
*** 676,683 ****
  							estate);
  
  		/* Compute and save index expression values */
! 		exprvals = (Datum *) palloc(numrows * attr_cnt * sizeof(Datum));
! 		exprnulls = (bool *) palloc(numrows * attr_cnt * sizeof(bool));
  		numindexrows = 0;
  		tcnt = 0;
  		for (rowno = 0; rowno < numrows; rowno++)
--- 879,886 ----
  							estate);
  
  		/* Compute and save index expression values */
! 		exprvals = (Datum *) palloc(numrows * thisdata->attr_cnt * sizeof(Datum));
! 		exprnulls = (bool *) palloc(numrows * thisdata->attr_cnt * sizeof(bool));
  		numindexrows = 0;
  		tcnt = 0;
  		for (rowno = 0; rowno < numrows; rowno++)
*************** compute_index_stats(Relation onerel, dou
*** 701,707 ****
  			}
  			numindexrows++;
  
! 			if (attr_cnt > 0)
  			{
  				/*
  				 * Evaluate the index row to compute expression values. We
--- 904,910 ----
  			}
  			numindexrows++;
  
! 			if (stats_cnt > 0)
  			{
  				/*
  				 * Evaluate the index row to compute expression values. We
*************** compute_index_stats(Relation onerel, dou
*** 714,737 ****
  							   isnull);
  
  				/*
! 				 * Save just the columns we care about.  We copy the values
! 				 * into ind_context from the estate's per-tuple context.
  				 */
! 				for (i = 0; i < attr_cnt; i++)
  				{
! 					VacAttrStats *stats = thisdata->vacattrstats[i];
! 					int			attnum = stats->attr->attnum;
! 
! 					if (isnull[attnum - 1])
  					{
  						exprvals[tcnt] = (Datum) 0;
  						exprnulls[tcnt] = true;
  					}
  					else
  					{
! 						exprvals[tcnt] = datumCopy(values[attnum - 1],
! 												   stats->attrtype->typbyval,
! 												   stats->attrtype->typlen);
  						exprnulls[tcnt] = false;
  					}
  					tcnt++;
--- 917,934 ----
  							   isnull);
  
  				/*
! 				 * We copy the values * into ind_context from the estate's per-tuple context.
  				 */
! 				for (i = 0; i < thisdata->attr_cnt; i++)
  				{
! 					if (isnull[i])
  					{
  						exprvals[tcnt] = (Datum) 0;
  						exprnulls[tcnt] = true;
  					}
  					else
  					{
! 						exprvals[tcnt] = datumCopy(values[i], typbyval[i], typlen[i]);
  						exprnulls[tcnt] = false;
  					}
  					tcnt++;
*************** compute_index_stats(Relation onerel, dou
*** 752,779 ****
  		if (numindexrows > 0)
  		{
  			MemoryContextSwitchTo(col_context);
! 			for (i = 0; i < attr_cnt; i++)
  			{
! 				VacAttrStats *stats = thisdata->vacattrstats[i];
! 				AttributeOpts *aopt =
! 				get_attribute_options(stats->attr->attrelid,
! 									  stats->attr->attnum);
  
! 				stats->exprvals = exprvals + i;
! 				stats->exprnulls = exprnulls + i;
! 				stats->rowstride = attr_cnt;
! 				(*stats->compute_stats) (stats,
! 										 ind_fetch_func,
! 										 numindexrows,
! 										 totalindexrows);
  
! 				/*
! 				 * If the n_distinct option is specified, it overrides the
! 				 * above computation.  For indices, we always use just
! 				 * n_distinct, not n_distinct_inherited.
! 				 */
! 				if (aopt != NULL && aopt->n_distinct != 0.0)
! 					stats->stadistinct = aopt->n_distinct;
  
  				MemoryContextResetAndDeleteChildren(col_context);
  			}
--- 949,985 ----
  		if (numindexrows > 0)
  		{
  			MemoryContextSwitchTo(col_context);
! 			for (i = 0; i < stats_cnt; i++)
  			{
! 				VacStats *stats = thisdata->vacstats[i];
! 				AttributeOpts *aopt;
! 				int		j;
  
! 				for (j = 0; j < stats->attnums->dim1; j++)
! 				{
! 					if (stats->attnums->dim1 == 1)
! 						aopt = get_attribute_options(stats->attrs[j]->attrelid,
! 										  stats->attrs[j]->attnum);
! 					else
! 						aopt = NULL;
  
! 					stats->exprvals = exprvals;
! 					stats->exprnulls = exprnulls;
! 					stats->rowstride = thisdata->attr_cnt;
! 					(*stats->statfuncs[j].compute_func_ptr) (stats,
! 											 j,
! 											 ind_fetch_func,
! 											 numindexrows,
! 											 totalindexrows);
! 
! 					/*
! 					 * If the n_distinct option is specified, it overrides the
! 					 * above computation.  For indices, we always use just
! 					 * n_distinct, not n_distinct_inherited.
! 					 */
! 					if (aopt != NULL && aopt->n_distinct != 0.0)
! 						stats->stadistinct[j] = aopt->n_distinct;
! 				}
  
  				MemoryContextResetAndDeleteChildren(col_context);
  			}
*************** compute_index_stats(Relation onerel, dou
*** 792,829 ****
  }
  
  /*
!  * examine_attribute -- pre-analysis of a single column
   *
   * Determine whether the column is analyzable; if so, create and initialize
!  * a VacAttrStats struct for it.  If not, return NULL.
   *
   * If index_expr isn't NULL, then we're trying to analyze an expression index,
   * and index_expr is the expression tree representing the column's data.
   */
! static VacAttrStats *
! examine_attribute(Relation onerel, int attnum, Node *index_expr)
  {
- 	Form_pg_attribute attr = onerel->rd_att->attrs[attnum - 1];
  	HeapTuple	typtuple;
! 	VacAttrStats *stats;
! 	int			i;
  	bool		ok;
  
! 	/* Never analyze dropped columns */
! 	if (attr->attisdropped)
! 		return NULL;
  
  	/* Don't analyze column if user has specified not to */
! 	if (attr->attstattarget == 0)
  		return NULL;
  
  	/*
! 	 * Create the VacAttrStats struct.	Note that we only have a copy of the
  	 * fixed fields of the pg_attribute tuple.
  	 */
! 	stats = (VacAttrStats *) palloc0(sizeof(VacAttrStats));
! 	stats->attr = (Form_pg_attribute) palloc(ATTRIBUTE_FIXED_PART_SIZE);
! 	memcpy(stats->attr, attr, ATTRIBUTE_FIXED_PART_SIZE);
  
  	/*
  	 * When analyzing an expression index, believe the expression tree's type
--- 998,1074 ----
  }
  
  /*
!  * statistics_target -- returns pg_statistic.statarget
!  */
! static int4
! statistics_target(Oid relid, int2vector *attnums, bool inherited)
! {
! 	HeapTuple		tuple;
! 	int4			statarget = -1; /* default */
! 
! 	tuple = SearchSysCache3(STATRELATTINH,
! 								ObjectIdGetDatum(relid),
! 								PointerGetDatum(attnums),
! 								BoolGetDatum(inherited));
! 	if (HeapTupleIsValid(tuple))
! 	{
! 		Form_pg_statistic stats = (Form_pg_statistic) GETSTRUCT(tuple);
! 
! 		statarget = stats->statarget;
! 
! 		ReleaseSysCache(tuple);
! 	}
! 
! 	return statarget;
! }
! 
! 
! /*
!  * examine_attribute -- pre-analysis of a single column or a set of columns
   *
   * Determine whether the column is analyzable; if so, create and initialize
!  * a VacStats struct for it.  If not, return NULL.
   *
   * If index_expr isn't NULL, then we're trying to analyze an expression index,
   * and index_expr is the expression tree representing the column's data.
   */
! static VacStats *
! examine_attribute(Relation onerel, int2vector *attnums, bool inh, Node **index_exprs)
  {
  	HeapTuple	typtuple;
! 	int4		statarget;
! 	VacStats *stats;
! 	int			i, j;
  	bool		ok;
  
! 	for (i = 0; i < attnums->dim1; i++)
! 	{
! 		Form_pg_attribute attr = onerel->rd_att->attrs[attnums->values[i] - 1];
! 
! 		/* Never analyze dropped columns */
! 		if (attr->attisdropped)
! 			return NULL;
! 	}
  
  	/* Don't analyze column if user has specified not to */
! 	statarget = statistics_target(onerel->rd_id, attnums, inh);
! 	if (statarget == 0)
  		return NULL;
  
  	/*
! 	 * Create the VacStats struct.	Note that we only have a copy of the
  	 * fixed fields of the pg_attribute tuple.
  	 */
! 	stats = (VacStats *) palloc0(sizeof(VacStats));
! 	for (i = 0; i < attnums->dim1; i++)
! 	{
! 		Form_pg_attribute attr = onerel->rd_att->attrs[attnums->values[i] - 1];
! 		stats->attrs[i] = (Form_pg_attribute) palloc(ATTRIBUTE_FIXED_PART_SIZE);
! 		memcpy(stats->attrs[i], attr, ATTRIBUTE_FIXED_PART_SIZE);
! 	}
! 
! 	stats->attnums = attnums;
! 	stats->statarget = statarget;
  
  	/*
  	 * When analyzing an expression index, believe the expression tree's type
*************** examine_attribute(Relation onerel, int a
*** 834,887 ****
  	 * not a problem.)	It's not clear whether anyone will care about the
  	 * typmod, but we store that too just in case.
  	 */
! 	if (index_expr)
! 	{
! 		stats->attrtypid = exprType(index_expr);
! 		stats->attrtypmod = exprTypmod(index_expr);
! 	}
! 	else
  	{
! 		stats->attrtypid = attr->atttypid;
! 		stats->attrtypmod = attr->atttypmod;
  	}
  
- 	typtuple = SearchSysCacheCopy1(TYPEOID,
- 								   ObjectIdGetDatum(stats->attrtypid));
- 	if (!HeapTupleIsValid(typtuple))
- 		elog(ERROR, "cache lookup failed for type %u", stats->attrtypid);
- 	stats->attrtype = (Form_pg_type) GETSTRUCT(typtuple);
  	stats->anl_context = anl_context;
- 	stats->tupattnum = attnum;
  
  	/*
  	 * The fields describing the stats->stavalues[n] element types default to
  	 * the type of the data being analyzed, but the type-specific typanalyze
  	 * function can change them if it wants to store something else.
  	 */
! 	for (i = 0; i < STATISTIC_NUM_SLOTS; i++)
  	{
! 		stats->statypid[i] = stats->attrtypid;
! 		stats->statyplen[i] = stats->attrtype->typlen;
! 		stats->statypbyval[i] = stats->attrtype->typbyval;
! 		stats->statypalign[i] = stats->attrtype->typalign;
  	}
  
  	/*
! 	 * Call the type-specific typanalyze function.	If none is specified, use
! 	 * std_typanalyze().
  	 */
! 	if (OidIsValid(stats->attrtype->typanalyze))
! 		ok = DatumGetBool(OidFunctionCall1(stats->attrtype->typanalyze,
! 										   PointerGetDatum(stats)));
! 	else
! 		ok = std_typanalyze(stats);
! 
! 	if (!ok || stats->compute_stats == NULL || stats->minrows <= 0)
  	{
! 		heap_freetuple(typtuple);
! 		pfree(stats->attr);
! 		pfree(stats);
! 		return NULL;
  	}
  
  	return stats;
--- 1079,1164 ----
  	 * not a problem.)	It's not clear whether anyone will care about the
  	 * typmod, but we store that too just in case.
  	 */
! 
! 	for (i = 0; i < attnums->dim1; i++)
  	{
! 		if (index_exprs && index_exprs[i])
! 		{
! 			stats->attrtypids[i] = exprType(index_exprs[i]);
! 			stats->attrtypmods[i] = exprTypmod(index_exprs[i]);
! 		}
! 		else
! 		{
! 			stats->attrtypids[i] = stats->attrs[i]->atttypid;
! 			stats->attrtypmods[i] = stats->attrs[i]->atttypmod;
! 		}
! 
! 		typtuple = SearchSysCacheCopy1(TYPEOID,
! 									   ObjectIdGetDatum(stats->attrtypids[i]));
! 		if (!HeapTupleIsValid(typtuple))
! 			elog(ERROR, "cache lookup failed for type %u", stats->attrtypids[i]);
! 		stats->attrtypes[i] = (Form_pg_type) GETSTRUCT(typtuple);
  	}
  
  	stats->anl_context = anl_context;
  
  	/*
  	 * The fields describing the stats->stavalues[n] element types default to
  	 * the type of the data being analyzed, but the type-specific typanalyze
  	 * function can change them if it wants to store something else.
  	 */
! 	for (i = 0; i < attnums->dim1; i++)
  	{
! 		for (j = 0; j < STATISTIC_NUM_SLOTS; j++)
! 		{
! 			stats->statypid[i][j] = stats->attrtypids[i];
! 			stats->statyplen[i][j] = stats->attrtypes[i]->typlen;
! 			stats->statypbyval[i][j] = stats->attrtypes[i]->typbyval;
! 			stats->statypalign[i][j] = stats->attrtypes[i]->typalign;
! 		}
! 
! 		/*
! 		 * Call the type-specific typanalyze function.	If none is specified, use
! 		 * std_typanalyze().
! 		 */
! 		if (OidIsValid(stats->attrtypes[i]->typanalyze))
! 			ok = DatumGetBool(OidFunctionCall2(stats->attrtypes[i]->typanalyze,
! 										   PointerGetDatum(stats),
! 										   Int32GetDatum(i)));
! 		else
! 			ok = std_typanalyze(stats, i);
! 
! 		if (!ok ||
! 			stats->statfuncs[i].compute_func_ptr == NULL ||
! 			/* intentionally don't check stats->statfuncs[i].findval_func_ptr == NULL */
! 			stats->minrows <= 0)
! 		{
! 			for (; i >= 0; i--)
! 			{
! 				heap_freetuple((HeapTuple) stats->attrtypes[i]);
! 				pfree(stats->attrs[i]);
! 			}
! 			pfree(stats);
! 
! 			return NULL;
! 		}
  	}
  
  	/*
! 	 * If we have a cross-column statistics, compute the cube size for
! 	 * the histogram so pg_statistic.statarget really means the number of
! 	 * cells in the multi-dimensional cube.
  	 */
! 	if (stats->attnums->dim1 > 1)
  	{
! 		double		x, y, n;
! 
! 		y = stats->statarget;
! 		n = stats->attnums->dim1;
! 		x = pow(M_E, (log(y) / n));
! 
! 		/* On some systems, ceil() returns surprising result. */
! 		stats->statarget = trunc(x + 0.5);
  	}
  
  	return stats;
*************** acquire_inherited_sample_rows(Relation o
*** 1555,1561 ****
   *		by taking a self-exclusive lock on the relation in analyze_rel().
   */
  static void
! update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
  {
  	Relation	sd;
  	int			attno;
--- 1832,1838 ----
   *		by taking a self-exclusive lock on the relation in analyze_rel().
   */
  static void
! update_attstats(Oid relid, bool inh, int natts, VacStats **vacstats)
  {
  	Relation	sd;
  	int			attno;
*************** update_attstats(Oid relid, bool inh, int
*** 1567,1573 ****
  
  	for (attno = 0; attno < natts; attno++)
  	{
! 		VacAttrStats *stats = vacattrstats[attno];
  		HeapTuple	stup,
  					oldtup;
  		int			i,
--- 1844,1850 ----
  
  	for (attno = 0; attno < natts; attno++)
  	{
! 		VacStats *stats = vacstats[attno];
  		HeapTuple	stup,
  					oldtup;
  		int			i,
*************** update_attstats(Oid relid, bool inh, int
*** 1590,1615 ****
  			replaces[i] = true;
  		}
  
  		values[Anum_pg_statistic_starelid - 1] = ObjectIdGetDatum(relid);
- 		values[Anum_pg_statistic_staattnum - 1] = Int16GetDatum(stats->attr->attnum);
  		values[Anum_pg_statistic_stainherit - 1] = BoolGetDatum(inh);
! 		values[Anum_pg_statistic_stanullfrac - 1] = Float4GetDatum(stats->stanullfrac);
! 		values[Anum_pg_statistic_stawidth - 1] = Int32GetDatum(stats->stawidth);
! 		values[Anum_pg_statistic_stadistinct - 1] = Float4GetDatum(stats->stadistinct);
  		i = Anum_pg_statistic_stakind1 - 1;
  		for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
  		{
! 			values[i++] = Int16GetDatum(stats->stakind[k]);		/* stakindN */
  		}
  		i = Anum_pg_statistic_staop1 - 1;
  		for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
  		{
! 			values[i++] = ObjectIdGetDatum(stats->staop[k]);	/* staopN */
  		}
  		i = Anum_pg_statistic_stanumbers1 - 1;
  		for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
  		{
! 			int			nnum = stats->numnumbers[k];
  
  			if (nnum > 0)
  			{
--- 1867,1901 ----
  			replaces[i] = true;
  		}
  
+ 		/*
+ 		 * We use statarget = -1 here, so if the pg_statistic entry is new,
+ 		 * we use the default_statistics_target for it. If there's a pg_statistic
+ 		 * row for this statistics, we will set the "replaces" flag to false.
+ 		 */
+ 		values[Anum_pg_statistic_statarget - 1] = Int32GetDatum(-1);
  		values[Anum_pg_statistic_starelid - 1] = ObjectIdGetDatum(relid);
  		values[Anum_pg_statistic_stainherit - 1] = BoolGetDatum(inh);
! 		values[Anum_pg_statistic_stavalid - 1] = BoolGetDatum(true);
! 		values[Anum_pg_statistic_stanullfrac - 1] = Float4GetDatum(stats->stanullfrac[0]);
! 		values[Anum_pg_statistic_stawidth - 1] = Int32GetDatum(stats->stawidth[0]);
! 		values[Anum_pg_statistic_stadistinct - 1] = Float4GetDatum(stats->stadistinct[0]);
  		i = Anum_pg_statistic_stakind1 - 1;
  		for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
  		{
! 			values[i++] = Int16GetDatum(stats->stakind[0][k]);	/* stakindN */
  		}
  		i = Anum_pg_statistic_staop1 - 1;
  		for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
  		{
! 			values[i++] = ObjectIdGetDatum(stats->staop[0][k]);	/* staopN */
  		}
+ 
+ 		values[Anum_pg_statistic_staattnums - 1] = PointerGetDatum(stats->attnums);	/* staattnums */
+ 
  		i = Anum_pg_statistic_stanumbers1 - 1;
  		for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
  		{
! 			int			nnum = stats->numnumbers[0][k];
  
  			if (nnum > 0)
  			{
*************** update_attstats(Oid relid, bool inh, int
*** 1617,1623 ****
  				ArrayType  *arry;
  
  				for (n = 0; n < nnum; n++)
! 					numdatums[n] = Float4GetDatum(stats->stanumbers[k][n]);
  				/* XXX knows more than it should about type float4: */
  				arry = construct_array(numdatums, nnum,
  									   FLOAT4OID,
--- 1903,1909 ----
  				ArrayType  *arry;
  
  				for (n = 0; n < nnum; n++)
! 					numdatums[n] = Float4GetDatum(stats->stanumbers[0][k][n]);
  				/* XXX knows more than it should about type float4: */
  				arry = construct_array(numdatums, nnum,
  									   FLOAT4OID,
*************** update_attstats(Oid relid, bool inh, int
*** 1633,1648 ****
  		i = Anum_pg_statistic_stavalues1 - 1;
  		for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
  		{
! 			if (stats->numvalues[k] > 0)
  			{
  				ArrayType  *arry;
  
! 				arry = construct_array(stats->stavalues[k],
! 									   stats->numvalues[k],
! 									   stats->statypid[k],
! 									   stats->statyplen[k],
! 									   stats->statypbyval[k],
! 									   stats->statypalign[k]);
  				values[i++] = PointerGetDatum(arry);	/* stavaluesN */
  			}
  			else
--- 1919,1947 ----
  		i = Anum_pg_statistic_stavalues1 - 1;
  		for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
  		{
! 			int	idx1, idx2;
! 
! 			if (stats->attnums->dim1 > 1 && k < stats->attnums->dim1)
! 			{
! 				idx1 = k;
! 				idx2 = 0;
! 			}
! 			else
! 			{
! 				idx1 = 0;
! 				idx2 = k;
! 			}
! 
! 			if (stats->numvalues[idx1][idx2] > 0)
  			{
  				ArrayType  *arry;
  
! 				arry = construct_array(stats->stavalues[idx1][idx2],
! 									   stats->numvalues[idx1][idx2],
! 									   stats->statypid[idx1][idx2],
! 									   stats->statyplen[idx1][idx2],
! 									   stats->statypbyval[idx1][idx2],
! 									   stats->statypalign[idx1][idx2]);
  				values[i++] = PointerGetDatum(arry);	/* stavaluesN */
  			}
  			else
*************** update_attstats(Oid relid, bool inh, int
*** 1655,1665 ****
  		/* Is there already a pg_statistic tuple for this attribute? */
  		oldtup = SearchSysCache3(STATRELATTINH,
  								 ObjectIdGetDatum(relid),
! 								 Int16GetDatum(stats->attr->attnum),
  								 BoolGetDatum(inh));
  
  		if (HeapTupleIsValid(oldtup))
  		{
  			/* Yes, replace it */
  			stup = heap_modify_tuple(oldtup,
  									 RelationGetDescr(sd),
--- 1954,1967 ----
  		/* Is there already a pg_statistic tuple for this attribute? */
  		oldtup = SearchSysCache3(STATRELATTINH,
  								 ObjectIdGetDatum(relid),
! 								 PointerGetDatum(stats->attnums),
  								 BoolGetDatum(inh));
  
  		if (HeapTupleIsValid(oldtup))
  		{
+ 			/* Skip replacing the statarget value, so we remember its value. */
+ 			replaces[Anum_pg_statistic_statarget - 1] = false;
+ 
  			/* Yes, replace it */
  			stup = heap_modify_tuple(oldtup,
  									 RelationGetDescr(sd),
*************** update_attstats(Oid relid, bool inh, int
*** 1692,1704 ****
   * and the actual storage of the sample data.
   */
  static Datum
! std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull)
  {
- 	int			attnum = stats->tupattnum;
  	HeapTuple	tuple = stats->rows[rownum];
  	TupleDesc	tupDesc = stats->tupDesc;
  
! 	return heap_getattr(tuple, attnum, tupDesc, isNull);
  }
  
  /*
--- 1994,2005 ----
   * and the actual storage of the sample data.
   */
  static Datum
! std_fetch_func(VacStatsP stats, int rownum, AttrNumber tupattnum, bool *isNull)
  {
  	HeapTuple	tuple = stats->rows[rownum];
  	TupleDesc	tupDesc = stats->tupDesc;
  
! 	return heap_getattr(tuple, tupattnum, tupDesc, isNull);
  }
  
  /*
*************** std_fetch_func(VacAttrStatsP stats, int 
*** 1708,1719 ****
   * just in Datum arrays.
   */
  static Datum
! ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull)
  {
  	int			i;
  
! 	/* exprvals and exprnulls are already offset for proper column */
! 	i = rownum * stats->rowstride;
  	*isNull = stats->exprnulls[i];
  	return stats->exprvals[i];
  }
--- 2009,2019 ----
   * just in Datum arrays.
   */
  static Datum
! ind_fetch_func(VacStatsP stats, int rownum, AttrNumber tupattnum, bool *isNull)
  {
  	int			i;
  
! 	i = rownum * stats->rowstride + tupattnum - 1;
  	*isNull = stats->exprnulls[i];
  	return stats->exprvals[i];
  }
*************** typedef struct
*** 1772,1783 ****
  	int		   *tupnoLink;
  } CompareScalarsContext;
  
  
! static void compute_minimal_stats(VacAttrStatsP stats,
  					  AnalyzeAttrFetchFunc fetchfunc,
  					  int samplerows,
  					  double totalrows);
! static void compute_scalar_stats(VacAttrStatsP stats,
  					 AnalyzeAttrFetchFunc fetchfunc,
  					 int samplerows,
  					 double totalrows);
--- 2072,2090 ----
  	int		   *tupnoLink;
  } CompareScalarsContext;
  
+ typedef struct
+ {
+ 	FmgrInfo		cmpFn;
+ 	int			cmpFlags;
+ } CompareDatumsContext;
  
! static void compute_minimal_stats(VacStatsP stats,
! 					  int index,
  					  AnalyzeAttrFetchFunc fetchfunc,
  					  int samplerows,
  					  double totalrows);
! static void compute_scalar_stats(VacStatsP stats,
! 					 int index,
  					 AnalyzeAttrFetchFunc fetchfunc,
  					 int samplerows,
  					 double totalrows);
*************** static int	compare_mcvs(const void *a, c
*** 1789,1808 ****
   * std_typanalyze -- the default type-specific typanalyze function
   */
  static bool
! std_typanalyze(VacAttrStats *stats)
  {
- 	Form_pg_attribute attr = stats->attr;
  	Oid			ltopr;
  	Oid			eqopr;
  	StdAnalyzeData *mystats;
  
! 	/* If the attstattarget column is negative, use the default value */
  	/* NB: it is okay to scribble on stats->attr since it's a copy */
! 	if (attr->attstattarget < 0)
! 		attr->attstattarget = default_statistics_target;
  
  	/* Look for default "<" and "=" operators for column's type */
! 	get_sort_group_operators(stats->attrtypid,
  							 false, false, false,
  							 &ltopr, &eqopr, NULL,
  							 NULL);
--- 2096,2114 ----
   * std_typanalyze -- the default type-specific typanalyze function
   */
  static bool
! std_typanalyze(VacStats *stats, int index)
  {
  	Oid			ltopr;
  	Oid			eqopr;
  	StdAnalyzeData *mystats;
  
! 	/* If the statarget column is negative, use the default value */
  	/* NB: it is okay to scribble on stats->attr since it's a copy */
! 	if (stats->statarget < 0)
! 		stats->statarget = default_statistics_target;
  
  	/* Look for default "<" and "=" operators for column's type */
! 	get_sort_group_operators(stats->attrtypids[index],
  							 false, false, false,
  							 &ltopr, &eqopr, NULL,
  							 NULL);
*************** std_typanalyze(VacAttrStats *stats)
*** 1816,1822 ****
  	mystats->eqopr = eqopr;
  	mystats->eqfunc = get_opcode(eqopr);
  	mystats->ltopr = ltopr;
! 	stats->extra_data = mystats;
  
  	/*
  	 * Determine which standard statistics algorithm to use
--- 2122,2128 ----
  	mystats->eqopr = eqopr;
  	mystats->eqfunc = get_opcode(eqopr);
  	mystats->ltopr = ltopr;
! 	stats->extra_data[index] = mystats;
  
  	/*
  	 * Determine which standard statistics algorithm to use
*************** std_typanalyze(VacAttrStats *stats)
*** 1824,1830 ****
  	if (OidIsValid(ltopr))
  	{
  		/* Seems to be a scalar datatype */
! 		stats->compute_stats = compute_scalar_stats;
  		/*--------------------
  		 * The following choice of minrows is based on the paper
  		 * "Random sampling for histogram construction: how much is enough?"
--- 2130,2137 ----
  	if (OidIsValid(ltopr))
  	{
  		/* Seems to be a scalar datatype */
! 		stats->statfuncs[index].compute_func_ptr = compute_scalar_stats;
! 		stats->statfuncs[index].findval_func_ptr = ordered_findval;
  		/*--------------------
  		 * The following choice of minrows is based on the paper
  		 * "Random sampling for histogram construction: how much is enough?"
*************** std_typanalyze(VacAttrStats *stats)
*** 1844,1857 ****
  		 * know it at this point.
  		 *--------------------
  		 */
! 		stats->minrows = 300 * attr->attstattarget;
  	}
  	else
  	{
  		/* Can't do much but the minimal stuff */
! 		stats->compute_stats = compute_minimal_stats;
  		/* Might as well use the same minrows as above */
! 		stats->minrows = 300 * attr->attstattarget;
  	}
  
  	return true;
--- 2151,2165 ----
  		 * know it at this point.
  		 *--------------------
  		 */
! 		stats->minrows = 300 * stats->statarget;
  	}
  	else
  	{
  		/* Can't do much but the minimal stuff */
! 		stats->statfuncs[index].compute_func_ptr = compute_minimal_stats;
! 		stats->statfuncs[index].findval_func_ptr = unordered_findval;
  		/* Might as well use the same minrows as above */
! 		stats->minrows = 300 * stats->statarget;
  	}
  
  	return true;
*************** std_typanalyze(VacAttrStats *stats)
*** 1873,1879 ****
   *	depend mainly on the length of the list we are willing to keep.
   */
  static void
! compute_minimal_stats(VacAttrStatsP stats,
  					  AnalyzeAttrFetchFunc fetchfunc,
  					  int samplerows,
  					  double totalrows)
--- 2181,2188 ----
   *	depend mainly on the length of the list we are willing to keep.
   */
  static void
! compute_minimal_stats(VacStatsP stats,
! 					  int index,
  					  AnalyzeAttrFetchFunc fetchfunc,
  					  int samplerows,
  					  double totalrows)
*************** compute_minimal_stats(VacAttrStatsP stat
*** 1883,1892 ****
  	int			nonnull_cnt = 0;
  	int			toowide_cnt = 0;
  	double		total_width = 0;
! 	bool		is_varlena = (!stats->attrtype->typbyval &&
! 							  stats->attrtype->typlen == -1);
! 	bool		is_varwidth = (!stats->attrtype->typbyval &&
! 							   stats->attrtype->typlen < 0);
  	FmgrInfo	f_cmpeq;
  	typedef struct
  	{
--- 2192,2201 ----
  	int			nonnull_cnt = 0;
  	int			toowide_cnt = 0;
  	double		total_width = 0;
! 	bool		is_varlena = (!stats->attrtypes[index]->typbyval &&
! 							  stats->attrtypes[index]->typlen == -1);
! 	bool		is_varwidth = (!stats->attrtypes[index]->typbyval &&
! 							   stats->attrtypes[index]->typlen < 0);
  	FmgrInfo	f_cmpeq;
  	typedef struct
  	{
*************** compute_minimal_stats(VacAttrStatsP stat
*** 1896,1903 ****
  	TrackItem  *track;
  	int			track_cnt,
  				track_max;
! 	int			num_mcv = stats->attr->attstattarget;
! 	StdAnalyzeData *mystats = (StdAnalyzeData *) stats->extra_data;
  
  	/*
  	 * We track up to 2*n values for an n-element MCV list; but at least 10
--- 2205,2212 ----
  	TrackItem  *track;
  	int			track_cnt,
  				track_max;
! 	int			num_mcv = stats->statarget;
! 	StdAnalyzeData *mystats = (StdAnalyzeData *) stats->extra_data[index];
  
  	/*
  	 * We track up to 2*n values for an n-element MCV list; but at least 10
*************** compute_minimal_stats(VacAttrStatsP stat
*** 1920,1926 ****
  
  		vacuum_delay_point();
  
! 		value = fetchfunc(stats, i, &isnull);
  
  		/* Check for null/nonnull */
  		if (isnull)
--- 2229,2235 ----
  
  		vacuum_delay_point();
  
! 		value = fetchfunc(stats, i, stats->attnums->values[index], &isnull);
  
  		/* Check for null/nonnull */
  		if (isnull)
*************** compute_minimal_stats(VacAttrStatsP stat
*** 2017,2027 ****
  
  		stats->stats_valid = true;
  		/* Do the simple null-frac and width stats */
! 		stats->stanullfrac = (double) null_cnt / (double) samplerows;
  		if (is_varwidth)
! 			stats->stawidth = total_width / (double) nonnull_cnt;
  		else
! 			stats->stawidth = stats->attrtype->typlen;
  
  		/* Count the number of values we found multiple times */
  		summultiple = 0;
--- 2326,2336 ----
  
  		stats->stats_valid = true;
  		/* Do the simple null-frac and width stats */
! 		stats->stanullfrac[index] = (double) null_cnt / (double) samplerows;
  		if (is_varwidth)
! 			stats->stawidth[index] = total_width / (double) nonnull_cnt;
  		else
! 			stats->stawidth[index] = stats->attrtypes[index]->typlen;
  
  		/* Count the number of values we found multiple times */
  		summultiple = 0;
*************** compute_minimal_stats(VacAttrStatsP stat
*** 2035,2041 ****
  		if (nmultiple == 0)
  		{
  			/* If we found no repeated values, assume it's a unique column */
! 			stats->stadistinct = -1.0;
  		}
  		else if (track_cnt < track_max && toowide_cnt == 0 &&
  				 nmultiple == track_cnt)
--- 2344,2350 ----
  		if (nmultiple == 0)
  		{
  			/* If we found no repeated values, assume it's a unique column */
! 			stats->stadistinct[index] = -1.0;
  		}
  		else if (track_cnt < track_max && toowide_cnt == 0 &&
  				 nmultiple == track_cnt)
*************** compute_minimal_stats(VacAttrStatsP stat
*** 2045,2051 ****
  			 * value appeared more than once.  Assume the column has just
  			 * these values.
  			 */
! 			stats->stadistinct = track_cnt;
  		}
  		else
  		{
--- 2354,2360 ----
  			 * value appeared more than once.  Assume the column has just
  			 * these values.
  			 */
! 			stats->stadistinct[index] = track_cnt;
  		}
  		else
  		{
*************** compute_minimal_stats(VacAttrStatsP stat
*** 2084,2090 ****
  				stadistinct = (double) d;
  			if (stadistinct > totalrows)
  				stadistinct = totalrows;
! 			stats->stadistinct = floor(stadistinct + 0.5);
  		}
  
  		/*
--- 2393,2399 ----
  				stadistinct = (double) d;
  			if (stadistinct > totalrows)
  				stadistinct = totalrows;
! 			stats->stadistinct[index] = floor(stadistinct + 0.5);
  		}
  
  		/*
*************** compute_minimal_stats(VacAttrStatsP stat
*** 2093,2100 ****
  		 * stadistinct should scale with the row count rather than be a fixed
  		 * value.
  		 */
! 		if (stats->stadistinct > 0.1 * totalrows)
! 			stats->stadistinct = -(stats->stadistinct / totalrows);
  
  		/*
  		 * Decide how many values are worth storing as most-common values. If
--- 2402,2409 ----
  		 * stadistinct should scale with the row count rather than be a fixed
  		 * value.
  		 */
! 		if (stats->stadistinct[index] > 0.1 * totalrows)
! 			stats->stadistinct[index] = -(stats->stadistinct[index] / totalrows);
  
  		/*
  		 * Decide how many values are worth storing as most-common values. If
*************** compute_minimal_stats(VacAttrStatsP stat
*** 2114,2120 ****
  		}
  		else
  		{
! 			double		ndistinct = stats->stadistinct;
  			double		avgcount,
  						mincount;
  
--- 2423,2429 ----
  		}
  		else
  		{
! 			double		ndistinct = stats->stadistinct[index];
  			double		avgcount,
  						mincount;
  
*************** compute_minimal_stats(VacAttrStatsP stat
*** 2152,2169 ****
  			for (i = 0; i < num_mcv; i++)
  			{
  				mcv_values[i] = datumCopy(track[i].value,
! 										  stats->attrtype->typbyval,
! 										  stats->attrtype->typlen);
  				mcv_freqs[i] = (double) track[i].count / (double) samplerows;
  			}
  			MemoryContextSwitchTo(old_context);
  
! 			stats->stakind[0] = STATISTIC_KIND_MCV;
! 			stats->staop[0] = mystats->eqopr;
! 			stats->stanumbers[0] = mcv_freqs;
! 			stats->numnumbers[0] = num_mcv;
! 			stats->stavalues[0] = mcv_values;
! 			stats->numvalues[0] = num_mcv;
  
  			/*
  			 * Accept the defaults for stats->statypid and others. They have
--- 2461,2478 ----
  			for (i = 0; i < num_mcv; i++)
  			{
  				mcv_values[i] = datumCopy(track[i].value,
! 										  stats->attrtypes[index]->typbyval,
! 										  stats->attrtypes[index]->typlen);
  				mcv_freqs[i] = (double) track[i].count / (double) samplerows;
  			}
  			MemoryContextSwitchTo(old_context);
  
! 			stats->stakind[index][0] = STATISTIC_KIND_MCV;
! 			stats->staop[index][0] = mystats->eqopr;
! 			stats->stanumbers[index][0] = mcv_freqs;
! 			stats->numnumbers[index][0] = num_mcv;
! 			stats->stavalues[index][0] = mcv_values;
! 			stats->numvalues[index][0] = num_mcv;
  
  			/*
  			 * Accept the defaults for stats->statypid and others. They have
*************** compute_minimal_stats(VacAttrStatsP stat
*** 2175,2186 ****
  	{
  		/* We found only nulls; assume the column is entirely null */
  		stats->stats_valid = true;
! 		stats->stanullfrac = 1.0;
  		if (is_varwidth)
! 			stats->stawidth = 0;	/* "unknown" */
  		else
! 			stats->stawidth = stats->attrtype->typlen;
! 		stats->stadistinct = 0.0;		/* "unknown" */
  	}
  
  	/* We don't need to bother cleaning up any of our temporary palloc's */
--- 2484,2495 ----
  	{
  		/* We found only nulls; assume the column is entirely null */
  		stats->stats_valid = true;
! 		stats->stanullfrac[index] = 1.0;
  		if (is_varwidth)
! 			stats->stawidth[index] = 0;	/* "unknown" */
  		else
! 			stats->stawidth[index] = stats->attrtypes[index]->typlen;
! 		stats->stadistinct[index] = 0.0;	/* "unknown" */
  	}
  
  	/* We don't need to bother cleaning up any of our temporary palloc's */
*************** compute_minimal_stats(VacAttrStatsP stat
*** 2200,2206 ****
   *	data values into order.
   */
  static void
! compute_scalar_stats(VacAttrStatsP stats,
  					 AnalyzeAttrFetchFunc fetchfunc,
  					 int samplerows,
  					 double totalrows)
--- 2509,2516 ----
   *	data values into order.
   */
  static void
! compute_scalar_stats(VacStatsP stats,
! 					 int index,
  					 AnalyzeAttrFetchFunc fetchfunc,
  					 int samplerows,
  					 double totalrows)
*************** compute_scalar_stats(VacAttrStatsP stats
*** 2210,2219 ****
  	int			nonnull_cnt = 0;
  	int			toowide_cnt = 0;
  	double		total_width = 0;
! 	bool		is_varlena = (!stats->attrtype->typbyval &&
! 							  stats->attrtype->typlen == -1);
! 	bool		is_varwidth = (!stats->attrtype->typbyval &&
! 							   stats->attrtype->typlen < 0);
  	double		corr_xysum;
  	Oid			cmpFn;
  	int			cmpFlags;
--- 2520,2529 ----
  	int			nonnull_cnt = 0;
  	int			toowide_cnt = 0;
  	double		total_width = 0;
! 	bool		is_varlena = (!stats->attrtypes[index]->typbyval &&
! 							  stats->attrtypes[index]->typlen == -1);
! 	bool		is_varwidth = (!stats->attrtypes[index]->typbyval &&
! 							   stats->attrtypes[index]->typlen < 0);
  	double		corr_xysum;
  	Oid			cmpFn;
  	int			cmpFlags;
*************** compute_scalar_stats(VacAttrStatsP stats
*** 2223,2231 ****
  	int		   *tupnoLink;
  	ScalarMCVItem *track;
  	int			track_cnt = 0;
! 	int			num_mcv = stats->attr->attstattarget;
! 	int			num_bins = stats->attr->attstattarget;
! 	StdAnalyzeData *mystats = (StdAnalyzeData *) stats->extra_data;
  
  	values = (ScalarItem *) palloc(samplerows * sizeof(ScalarItem));
  	tupnoLink = (int *) palloc(samplerows * sizeof(int));
--- 2533,2541 ----
  	int		   *tupnoLink;
  	ScalarMCVItem *track;
  	int			track_cnt = 0;
! 	int			num_mcv = stats->statarget;
! 	int			num_bins = stats->statarget;
! 	StdAnalyzeData *mystats = (StdAnalyzeData *) stats->extra_data[index];
  
  	values = (ScalarItem *) palloc(samplerows * sizeof(ScalarItem));
  	tupnoLink = (int *) palloc(samplerows * sizeof(int));
*************** compute_scalar_stats(VacAttrStatsP stats
*** 2242,2248 ****
  
  		vacuum_delay_point();
  
! 		value = fetchfunc(stats, i, &isnull);
  
  		/* Check for null/nonnull */
  		if (isnull)
--- 2552,2558 ----
  
  		vacuum_delay_point();
  
! 		value = fetchfunc(stats, i, stats->attnums->values[index], &isnull);
  
  		/* Check for null/nonnull */
  		if (isnull)
*************** compute_scalar_stats(VacAttrStatsP stats
*** 2372,2387 ****
  
  		stats->stats_valid = true;
  		/* Do the simple null-frac and width stats */
! 		stats->stanullfrac = (double) null_cnt / (double) samplerows;
  		if (is_varwidth)
! 			stats->stawidth = total_width / (double) nonnull_cnt;
  		else
! 			stats->stawidth = stats->attrtype->typlen;
  
  		if (nmultiple == 0)
  		{
  			/* If we found no repeated values, assume it's a unique column */
! 			stats->stadistinct = -1.0;
  		}
  		else if (toowide_cnt == 0 && nmultiple == ndistinct)
  		{
--- 2682,2697 ----
  
  		stats->stats_valid = true;
  		/* Do the simple null-frac and width stats */
! 		stats->stanullfrac[index] = (double) null_cnt / (double) samplerows;
  		if (is_varwidth)
! 			stats->stawidth[index] = total_width / (double) nonnull_cnt;
  		else
! 			stats->stawidth[index] = stats->attrtypes[index]->typlen;
  
  		if (nmultiple == 0)
  		{
  			/* If we found no repeated values, assume it's a unique column */
! 			stats->stadistinct[index] = -1.0;
  		}
  		else if (toowide_cnt == 0 && nmultiple == ndistinct)
  		{
*************** compute_scalar_stats(VacAttrStatsP stats
*** 2389,2395 ****
  			 * Every value in the sample appeared more than once.  Assume the
  			 * column has just these values.
  			 */
! 			stats->stadistinct = ndistinct;
  		}
  		else
  		{
--- 2699,2705 ----
  			 * Every value in the sample appeared more than once.  Assume the
  			 * column has just these values.
  			 */
! 			stats->stadistinct[index] = ndistinct;
  		}
  		else
  		{
*************** compute_scalar_stats(VacAttrStatsP stats
*** 2424,2430 ****
  				stadistinct = (double) d;
  			if (stadistinct > totalrows)
  				stadistinct = totalrows;
! 			stats->stadistinct = floor(stadistinct + 0.5);
  		}
  
  		/*
--- 2734,2740 ----
  				stadistinct = (double) d;
  			if (stadistinct > totalrows)
  				stadistinct = totalrows;
! 			stats->stadistinct[index] = floor(stadistinct + 0.5);
  		}
  
  		/*
*************** compute_scalar_stats(VacAttrStatsP stats
*** 2433,2440 ****
  		 * stadistinct should scale with the row count rather than be a fixed
  		 * value.
  		 */
! 		if (stats->stadistinct > 0.1 * totalrows)
! 			stats->stadistinct = -(stats->stadistinct / totalrows);
  
  		/*
  		 * Decide how many values are worth storing as most-common values. If
--- 2743,2750 ----
  		 * stadistinct should scale with the row count rather than be a fixed
  		 * value.
  		 */
! 		if (stats->stadistinct[index] > 0.1 * totalrows)
! 			stats->stadistinct[index] = -(stats->stadistinct[index] / totalrows);
  
  		/*
  		 * Decide how many values are worth storing as most-common values. If
*************** compute_scalar_stats(VacAttrStatsP stats
*** 2451,2457 ****
  		 * but we prefer to treat such values as MCVs if at all possible.)
  		 */
  		if (track_cnt == ndistinct && toowide_cnt == 0 &&
! 			stats->stadistinct > 0 &&
  			track_cnt <= num_mcv)
  		{
  			/* Track list includes all values seen, and all will fit */
--- 2761,2767 ----
  		 * but we prefer to treat such values as MCVs if at all possible.)
  		 */
  		if (track_cnt == ndistinct && toowide_cnt == 0 &&
! 			stats->stadistinct[index] > 0 &&
  			track_cnt <= num_mcv)
  		{
  			/* Track list includes all values seen, and all will fit */
*************** compute_scalar_stats(VacAttrStatsP stats
*** 2459,2465 ****
  		}
  		else
  		{
! 			double		ndistinct = stats->stadistinct;
  			double		avgcount,
  						mincount,
  						maxmincount;
--- 2769,2775 ----
  		}
  		else
  		{
! 			double		ndistinct = stats->stadistinct[index];
  			double		avgcount,
  						mincount,
  						maxmincount;
*************** compute_scalar_stats(VacAttrStatsP stats
*** 2502,2519 ****
  			for (i = 0; i < num_mcv; i++)
  			{
  				mcv_values[i] = datumCopy(values[track[i].first].value,
! 										  stats->attrtype->typbyval,
! 										  stats->attrtype->typlen);
  				mcv_freqs[i] = (double) track[i].count / (double) samplerows;
  			}
  			MemoryContextSwitchTo(old_context);
  
! 			stats->stakind[slot_idx] = STATISTIC_KIND_MCV;
! 			stats->staop[slot_idx] = mystats->eqopr;
! 			stats->stanumbers[slot_idx] = mcv_freqs;
! 			stats->numnumbers[slot_idx] = num_mcv;
! 			stats->stavalues[slot_idx] = mcv_values;
! 			stats->numvalues[slot_idx] = num_mcv;
  
  			/*
  			 * Accept the defaults for stats->statypid and others. They have
--- 2812,2829 ----
  			for (i = 0; i < num_mcv; i++)
  			{
  				mcv_values[i] = datumCopy(values[track[i].first].value,
! 										  stats->attrtypes[index]->typbyval,
! 										  stats->attrtypes[index]->typlen);
  				mcv_freqs[i] = (double) track[i].count / (double) samplerows;
  			}
  			MemoryContextSwitchTo(old_context);
  
! 			stats->stakind[index][slot_idx] = STATISTIC_KIND_MCV;
! 			stats->staop[index][slot_idx] = mystats->eqopr;
! 			stats->stanumbers[index][slot_idx] = mcv_freqs;
! 			stats->numnumbers[index][slot_idx] = num_mcv;
! 			stats->stavalues[index][slot_idx] = mcv_values;
! 			stats->numvalues[index][slot_idx] = num_mcv;
  
  			/*
  			 * Accept the defaults for stats->statypid and others. They have
*************** compute_scalar_stats(VacAttrStatsP stats
*** 2609,2616 ****
  			for (i = 0; i < num_hist; i++)
  			{
  				hist_values[i] = datumCopy(values[pos].value,
! 										   stats->attrtype->typbyval,
! 										   stats->attrtype->typlen);
  				pos += delta;
  				posfrac += deltafrac;
  				if (posfrac >= (num_hist - 1))
--- 2919,2926 ----
  			for (i = 0; i < num_hist; i++)
  			{
  				hist_values[i] = datumCopy(values[pos].value,
! 										   stats->attrtypes[index]->typbyval,
! 										   stats->attrtypes[index]->typlen);
  				pos += delta;
  				posfrac += deltafrac;
  				if (posfrac >= (num_hist - 1))
*************** compute_scalar_stats(VacAttrStatsP stats
*** 2623,2632 ****
  
  			MemoryContextSwitchTo(old_context);
  
! 			stats->stakind[slot_idx] = STATISTIC_KIND_HISTOGRAM;
! 			stats->staop[slot_idx] = mystats->ltopr;
! 			stats->stavalues[slot_idx] = hist_values;
! 			stats->numvalues[slot_idx] = num_hist;
  
  			/*
  			 * Accept the defaults for stats->statypid and others. They have
--- 2933,2942 ----
  
  			MemoryContextSwitchTo(old_context);
  
! 			stats->stakind[index][slot_idx] = STATISTIC_KIND_HISTOGRAM;
! 			stats->staop[index][slot_idx] = mystats->ltopr;
! 			stats->stavalues[index][slot_idx] = hist_values;
! 			stats->numvalues[index][slot_idx] = num_hist;
  
  			/*
  			 * Accept the defaults for stats->statypid and others. They have
*************** compute_scalar_stats(VacAttrStatsP stats
*** 2666,2675 ****
  			corrs[0] = (values_cnt * corr_xysum - corr_xsum * corr_xsum) /
  				(values_cnt * corr_x2sum - corr_xsum * corr_xsum);
  
! 			stats->stakind[slot_idx] = STATISTIC_KIND_CORRELATION;
! 			stats->staop[slot_idx] = mystats->ltopr;
! 			stats->stanumbers[slot_idx] = corrs;
! 			stats->numnumbers[slot_idx] = 1;
  			slot_idx++;
  		}
  	}
--- 2976,2985 ----
  			corrs[0] = (values_cnt * corr_xysum - corr_xsum * corr_xsum) /
  				(values_cnt * corr_x2sum - corr_xsum * corr_xsum);
  
! 			stats->stakind[index][slot_idx] = STATISTIC_KIND_CORRELATION;
! 			stats->staop[index][slot_idx] = mystats->ltopr;
! 			stats->stanumbers[index][slot_idx] = corrs;
! 			stats->numnumbers[index][slot_idx] = 1;
  			slot_idx++;
  		}
  	}
*************** compute_scalar_stats(VacAttrStatsP stats
*** 2677,2688 ****
  	{
  		/* We found only nulls; assume the column is entirely null */
  		stats->stats_valid = true;
! 		stats->stanullfrac = 1.0;
  		if (is_varwidth)
! 			stats->stawidth = 0;	/* "unknown" */
  		else
! 			stats->stawidth = stats->attrtype->typlen;
! 		stats->stadistinct = 0.0;		/* "unknown" */
  	}
  
  	/* We don't need to bother cleaning up any of our temporary palloc's */
--- 2987,2998 ----
  	{
  		/* We found only nulls; assume the column is entirely null */
  		stats->stats_valid = true;
! 		stats->stanullfrac[index] = 1.0;
  		if (is_varwidth)
! 			stats->stawidth[index] = 0;	/* "unknown" */
  		else
! 			stats->stawidth[index] = stats->attrtypes[index]->typlen;
! 		stats->stadistinct[index] = 0.0;	/* "unknown" */
  	}
  
  	/* We don't need to bother cleaning up any of our temporary palloc's */
*************** compare_scalars(const void *a, const voi
*** 2729,2734 ****
--- 3039,3243 ----
  }
  
  /*
+  * qsort_arg comparator for cross column statistics
+  * The dimension axes need to be properly ordered.
+  */
+ static int
+ compare_datums(const void *a, const void *b, void *arg)
+ {
+ 	Datum		da = *((Datum *) a);
+ 	Datum		db = *((Datum *) b);
+ 	CompareDatumsContext *cxt = (CompareDatumsContext *) arg;
+ 
+ 	return ApplySortFunction(&(cxt->cmpFn), cxt->cmpFlags,
+ 								DEFAULT_COLLATION_OID,
+ 								da, false, db, false);
+ }
+ 
+ /*
+  * Find the index in stavalues for a Datum with ordering operators.
+  * Because it's ordered, we can use the ordering operator for binary searching.
+  * Returns: 0 for a NULL value, >= 1 for a valid index.
+  */
+ static int
+ ordered_findval(VacStatsP stats, int rownum, int index, AnalyzeAttrFetchFunc fetchfunc, void *arg)
+ {
+ 	Datum		value;
+ 	bool		isnull;
+ 	int		idx_lo, idx_hi, idx_mid;
+ 	int32		compare;
+ 	CompareDatumsContext *ctx = (CompareDatumsContext *) arg;
+ 
+ 	value = fetchfunc(stats, rownum, stats->attnums->values[index], &isnull);
+ 	if (isnull)
+ 		return 0;
+ 
+ 	/* Clamp values smaller than the first bucket to the first bucket */
+ 	idx_lo = 0;
+ 	compare = ApplySortFunction(&(ctx->cmpFn), ctx->cmpFlags,
+ 								DEFAULT_COLLATION_OID,
+ 								value, false, stats->stavalues[index][0][idx_lo], false);
+ 	if (compare < 0)
+ 		return idx_lo + 1;
+ 
+ 	/*
+ 	 * Buckets have values greater or equal than the bucket's stavalue
+ 	 * but less than the next bucket's stavalue. There's no next bucket
+ 	 * for the last one, so check it here.
+ 	 */
+ 	idx_hi = stats->numvalues[index][0] - 1;
+ 	compare = ApplySortFunction(&(ctx->cmpFn), ctx->cmpFlags,
+ 								DEFAULT_COLLATION_OID,
+ 								value, false, stats->stavalues[index][0][idx_hi], false);
+ 	if (compare >= 0)
+ 		return idx_hi + 1;
+ 
+ 	idx_mid = (idx_lo + idx_hi) / 2;
+ 
+ 	while (idx_lo < idx_hi && idx_mid > idx_lo)
+ 	{
+ 		compare = ApplySortFunction(&(ctx->cmpFn), ctx->cmpFlags,
+ 								DEFAULT_COLLATION_OID,
+ 								value, false, stats->stavalues[index][0][idx_mid], false);
+ 		if (compare < 0)
+ 			idx_hi = idx_mid - 1;
+ 		else /* if (compare >= 0) */
+ 			idx_lo = idx_mid;
+ 
+ 		idx_mid = (idx_lo + idx_hi) / 2;
+ 	}
+ 
+ 	return idx_lo + 1;
+ }
+ 
+ /*
+  * Find the index in stavalues for a Datum with equality operator only.
+  * Byte the bullet and loop through the stavalues array.
+  * Returns: 0 for NULL value, >= 1 for a valid index and -1 for a Datum
+  * that isn't in stavalues (ignored value).
+  */
+ static int
+ unordered_findval(VacStatsP stats, int rownum, int index, AnalyzeAttrFetchFunc fetchfunc, void *arg)
+ {
+ 	CompareDatumsContext *ctx = (CompareDatumsContext *) arg;
+ 	Datum		value;
+ 	bool		isnull;
+ 	int		i;
+ 
+ 	value = fetchfunc(stats, rownum, stats->attnums->values[index], &isnull);
+ 	if (isnull)
+ 		return 0;
+ 
+ 	for (i = 0; i < stats->numvalues[index][0]; i++)
+ 	{
+ 		if (DatumGetBool(FunctionCall2Coll(&(ctx->cmpFn),
+ 								DEFAULT_COLLATION_OID,
+ 								value, stats->stavalues[index][0][i])))
+ 			return i + 1;
+ 	}
+ 
+ 	return -1;
+ }
+ 
+ /*
+  * Compute a cross-column statistics.
+  */
+ static void
+ compute_cross_column_stats(VacStatsP stats, AnalyzeAttrFetchFunc fetchfunc, int numrows)
+ {
+ 	int			i, j, numvalues;
+ 	int			dimstride[STATISTIC_NUM_SLOTS];
+ 	MemoryContext		old_context;
+ 	CompareDatumsContext	ctx[STATISTIC_NUM_SLOTS];
+ 	float4		   *hist;
+ 
+ 	/* Don't touch single column statistics. */
+ 	if (stats->attnums->dim1 == 1)
+ 		return;
+ 
+ 	/*
+ 	 * Don't compute cross-column statistics if there is no way to find
+ 	 * a Datum's index in the stavalues[] array
+ 	 */
+ 	for (i = 0; i < stats->attnums->dim1; i++)
+ 		if (stats->statfuncs[i].findval_func_ptr == NULL)
+ 			return;
+ 
+ 	/* We need ordered stavalues arrays whenever possible. */
+ 	for (i = 0; i < stats->attnums->dim1; i++)
+ 	{
+ 		StdAnalyzeData *mystats = (StdAnalyzeData *) stats->extra_data[i];
+ 
+ 		if (OidIsValid(mystats->ltopr))
+ 		{
+ 			Oid	cmpFn;
+ 
+ 			SelectSortFunction(mystats->ltopr, false, &cmpFn, &(ctx[i].cmpFlags));
+ 			fmgr_info(cmpFn, &(ctx[i].cmpFn));
+ 
+ 			qsort_arg((void *) stats->stavalues[i][0], stats->numvalues[i][0], sizeof(Datum),
+ 										  compare_datums, (void *) &ctx[i]);
+ 		}
+ 		else
+ 			fmgr_info(mystats->eqfunc, &(ctx[i].cmpFn));
+ 	}
+ 
+ 	/* Free up previously computed stanumbers arrays. */
+ 	for (i = 0; i < STATISTIC_NUM_SLOTS; i++)
+ 		for (j = 0; j < STATISTIC_NUM_SLOTS; j++)
+ 		{
+ 			if (stats->stanumbers[i][j])
+ 			{
+ 				pfree(stats->stanumbers[i][j]);
+ 				stats->stanumbers[i][j] = NULL;
+ 			}
+ 			stats->numnumbers[i][j] = 0;
+ 		}
+ 
+ 	/*
+ 	 * Compute the size of the N-dimension histogram and the dimension stride values.
+ 	 * The histogram cells at the 0th index of a dimension is the nullfrac value,
+ 	 * hence the + 1.
+ 	 */
+ 	for (i = 0, numvalues = 1; i < stats->attnums->dim1; i++)
+ 	{
+ 		dimstride[stats->attnums->dim1 - i - 1] = numvalues;
+ 		numvalues *= stats->numvalues[i][0] + 1;
+ 	}
+ 
+ 	old_context = MemoryContextSwitchTo(stats->anl_context);
+ 
+ 	hist = (float *) palloc(numvalues * sizeof(float));
+ 	for (i = 0; i < numvalues; i++)
+ 		hist[i] = 0.0;
+ 
+ 	MemoryContextSwitchTo(old_context);
+ 
+ 	for (i = 0; i < numrows; i++)
+ 	{
+ 		int	idx, idx_tmp;
+ 
+ 		idx = 0;
+ 		for (j = 0; j < stats->attnums->dim1; j++)
+ 		{
+ 			idx_tmp = stats->statfuncs[j].findval_func_ptr(stats, i, j, fetchfunc, &ctx[j]);
+ 			if (idx_tmp < 0)
+ 				continue;
+ 
+ 			idx += idx_tmp * dimstride[j];
+ 		}
+ 		hist[idx] = hist[idx] + 1.0;
+ 	}
+ 
+ 	/* Store selectivity instead of actual counts. */
+ 	for (i = 0; i < numvalues; i ++)
+ 		hist[i] /= numrows;
+ 
+ 	stats->numnumbers[0][0] = numvalues;
+ 	stats->stanumbers[0][0] = hist;
+ }
+ 
+ /*
   * qsort comparator for sorting ScalarMCVItems by position
   */
  static int
*************** compare_mcvs(const void *a, const void *
*** 2739,2741 ****
--- 3248,3321 ----
  
  	return da - db;
  }
+ 
+ /*
+  * ExtraColStat
+  *     Add or remove one extra entry in pg_statistics
+  */
+ void ExtraStatistics(ExtraStatStmt *stmt)
+ {
+ 	Oid		relId;
+ 	int		len, i, j;
+ 	bool		differ = false;
+ 	AttrNumber	   *attnums;
+ 	AttrNumber	   *sorted_attnums;
+ 	ListCell	   *l;
+ 
+ 	relId = RangeVarGetRelid(stmt->relation, AccessExclusiveLock, false, false);
+ 
+ 	len = list_length(stmt->columns);
+ 	if (len < 2)
+ 		elog(ERROR, "cross column statistics need at least two columns");
+ 	if (len > STATISTIC_NUM_SLOTS)
+ 		elog(ERROR, "cross column statistics can cover at most %d columns", STATISTIC_NUM_SLOTS);
+ 
+ 	attnums = (int2 *)palloc(len * sizeof(AttrNumber));
+ 	sorted_attnums = (int2 *)palloc(len * sizeof(AttrNumber));
+ 
+ 	i = 0;
+ 	foreach(l, stmt->columns)
+ 	{
+ 		Node	   *node = (Node *) lfirst(l);
+ 		Var	   *var;
+ 
+ 		if (!IsA(node, Var))
+ 			elog(ERROR, "not a column reference");
+ 
+ 		var = (Var *) node;
+ 
+ 		if (var->varattno == 0)
+ 			elog(ERROR, "row expansion via \"*\" is not supported here");
+ 
+ 		sorted_attnums[i] = attnums[i] = var->varattno;
+ 
+ 		i++;
+ 	}
+ 
+ 	for (i = 0;  i < len - 1; i++)
+ 		for (j = i+1; j < len; j++)
+ 			if (sorted_attnums[i] > sorted_attnums[j])
+ 			{
+ 				AttrNumber	tmp = sorted_attnums[i];
+ 
+ 				sorted_attnums[i] = sorted_attnums[j];
+ 				sorted_attnums[j] = tmp;
+ 			}
+ 
+ 	for (i = 0; i < len; i++)
+ 	{
+ 		if (!differ && attnums[i] != sorted_attnums[i])
+ 			differ = true;
+ 
+ 		if ((i < len - 1) && sorted_attnums[i] == sorted_attnums[i+1])
+ 			elog(ERROR, "column list must contain every column exactly once");
+ 
+ 	}
+ 	if (differ)
+ 		elog(WARNING, "the column list was reordered in the order of table attributes");
+ 
+ 	if (stmt->create)
+ 		AddStatistics(relId, sorted_attnums, len, false, stmt->statistics_target);
+ 	else
+ 		RemoveStatistics(relId, sorted_attnums, len);
+ }
diff -dcrpN postgresql.orig/src/backend/commands/tablecmds.c postgresql/src/backend/commands/tablecmds.c
*** postgresql.orig/src/backend/commands/tablecmds.c	2011-09-05 15:08:06.505567943 +0200
--- postgresql/src/backend/commands/tablecmds.c	2011-09-12 10:01:16.543508362 +0200
***************
*** 35,40 ****
--- 35,41 ----
  #include "catalog/pg_inherits_fn.h"
  #include "catalog/pg_namespace.h"
  #include "catalog/pg_opclass.h"
+ #include "catalog/pg_statistic.h"
  #include "catalog/pg_tablespace.h"
  #include "catalog/pg_trigger.h"
  #include "catalog/pg_type.h"
*************** ATExecAddColumn(List **wqueue, AlteredTa
*** 4341,4347 ****
  	attribute.attrelid = myrelid;
  	namestrcpy(&(attribute.attname), colDef->colname);
  	attribute.atttypid = typeOid;
- 	attribute.attstattarget = (newattnum > 0) ? -1 : 0;
  	attribute.attlen = tform->typlen;
  	attribute.attcacheoff = -1;
  	attribute.atttypmod = typmod;
--- 4342,4347 ----
*************** ATExecAddColumn(List **wqueue, AlteredTa
*** 4503,4508 ****
--- 4503,4510 ----
  	add_column_datatype_dependency(myrelid, newattnum, attribute.atttypid);
  	add_column_collation_dependency(myrelid, newattnum, attribute.attcollation);
  
+ 	AddStatistics(myrelid, &attribute.attnum, 1, attribute.attinhcount, (newattnum > 0) ? -1 : 0);
+ 
  	/*
  	 * Propagate to children as appropriate.  Unlike most other ALTER
  	 * routines, we have to do this one level of recursion at a time; we can't
*************** ATExecSetStatistics(Relation rel, const 
*** 4839,4846 ****
--- 4841,4854 ----
  {
  	int			newtarget;
  	Relation	attrelation;
+ 	Relation	statsrelation;
+ 	Oid		relid;
  	HeapTuple	tuple;
  	Form_pg_attribute attrtuple;
+ 	AttrNumber	attnum;
+ 	bool		inherited;
+ 	int2vector *attnumvector;
+ 	Form_pg_statistic stattuple;
  
  	Assert(IsA(newValue, Integer));
  	newtarget = intVal(newValue);
*************** ATExecSetStatistics(Relation rel, const 
*** 4866,4872 ****
  
  	attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
  
! 	tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
  
  	if (!HeapTupleIsValid(tuple))
  		ereport(ERROR,
--- 4874,4882 ----
  
  	attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
  
! 	relid = RelationGetRelid(rel);
! 
! 	tuple = SearchSysCacheAttName(relid, colName);
  
  	if (!HeapTupleIsValid(tuple))
  		ereport(ERROR,
*************** ATExecSetStatistics(Relation rel, const 
*** 4881,4896 ****
  				 errmsg("cannot alter system column \"%s\"",
  						colName)));
  
! 	attrtuple->attstattarget = newtarget;
  
! 	simple_heap_update(attrelation, &tuple->t_self, tuple);
  
! 	/* keep system catalog indexes current */
! 	CatalogUpdateIndexes(attrelation, tuple);
  
! 	heap_freetuple(tuple);
  
! 	heap_close(attrelation, RowExclusiveLock);
  }
  
  static void
--- 4891,4931 ----
  				 errmsg("cannot alter system column \"%s\"",
  						colName)));
  
! 	attnum = attrtuple->attnum;
! 	inherited = (attrtuple->attinhcount > 0);
  
! 	ReleaseSysCache(tuple);
  
! 	heap_close(attrelation, RowExclusiveLock);
  
! 	statsrelation = heap_open(StatisticRelationId, RowExclusiveLock);
  
! 	attnumvector = buildint2vector(&attnum, 1);
! 
! 	tuple = SearchSysCacheCopy3(STATRELATTINH,
! 								ObjectIdGetDatum(relid),
! 								PointerGetDatum(attnumvector),
! 								BoolGetDatum(inherited));
! 
! 	pfree(attnumvector);
! 
! 	if (!HeapTupleIsValid(tuple))
! 		AddStatistics(relid, &attnum, 1, inherited, newtarget);
! 	else
! 	{
! 		stattuple = (Form_pg_statistic) GETSTRUCT(tuple);
! 
! 		stattuple->statarget = newtarget;
! 
! 		simple_heap_update(statsrelation, &tuple->t_self, tuple);
! 
! 		/* keep system catalog indexes current */
! 		CatalogUpdateIndexes(statsrelation, tuple);
! 
! 		heap_freetuple(tuple);
! 	}
! 
! 	heap_close(statsrelation, RowExclusiveLock);
  }
  
  static void
*************** ATExecAlterColumnType(AlteredTableInfo *
*** 7390,7398 ****
  	add_column_collation_dependency(RelationGetRelid(rel), attnum, targetcollid);
  
  	/*
! 	 * Drop any pg_statistic entry for the column, since it's now wrong type
  	 */
! 	RemoveStatistics(RelationGetRelid(rel), attnum);
  
  	/*
  	 * Update the default, if present, by brute force --- remove and re-add
--- 7425,7433 ----
  	add_column_collation_dependency(RelationGetRelid(rel), attnum, targetcollid);
  
  	/*
! 	 * Invalidate any pg_statistic entry for the column, since it's now wrong type
  	 */
! 	InvalidateStatistics(RelationGetRelid(rel), attnum);
  
  	/*
  	 * Update the default, if present, by brute force --- remove and re-add
diff -dcrpN postgresql.orig/src/backend/executor/nodeHash.c postgresql/src/backend/executor/nodeHash.c
*** postgresql.orig/src/backend/executor/nodeHash.c	2011-09-02 12:52:41.347575337 +0200
--- postgresql/src/backend/executor/nodeHash.c	2011-09-12 10:01:16.578506729 +0200
***************
*** 32,37 ****
--- 32,38 ----
  #include "executor/nodeHash.h"
  #include "executor/nodeHashjoin.h"
  #include "miscadmin.h"
+ #include "utils/builtins.h"
  #include "utils/dynahash.h"
  #include "utils/memutils.h"
  #include "utils/lsyscache.h"
*************** ExecHashBuildSkewHash(HashJoinTable hash
*** 1125,1130 ****
--- 1126,1132 ----
  	int			nvalues;
  	float4	   *numbers;
  	int			nnumbers;
+ 	int2vector *attnumvector;
  
  	/* Do nothing if planner didn't identify the outer relation's join key */
  	if (!OidIsValid(node->skewTable))
*************** ExecHashBuildSkewHash(HashJoinTable hash
*** 1136,1145 ****
  	/*
  	 * Try to find the MCV statistics for the outer relation's join key.
  	 */
  	statsTuple = SearchSysCache3(STATRELATTINH,
  								 ObjectIdGetDatum(node->skewTable),
! 								 Int16GetDatum(node->skewColumn),
  								 BoolGetDatum(node->skewInherit));
  	if (!HeapTupleIsValid(statsTuple))
  		return;
  
--- 1138,1160 ----
  	/*
  	 * Try to find the MCV statistics for the outer relation's join key.
  	 */
+ 	attnumvector = buildint2vector(&(node->skewColumn), 1);
  	statsTuple = SearchSysCache3(STATRELATTINH,
  								 ObjectIdGetDatum(node->skewTable),
! 								 PointerGetDatum(attnumvector),
  								 BoolGetDatum(node->skewInherit));
+ 	pfree(attnumvector);
+ 
+ 	/* check whether the stats entry is valid */
+ 	if (HeapTupleIsValid(statsTuple))
+ 	{
+ 		if (!((Form_pg_statistic)GETSTRUCT(statsTuple))->stavalid)
+ 		{
+ 			ReleaseSysCache(statsTuple);
+ 			statsTuple = NULL;
+ 		}
+ 	}
+ 
  	if (!HeapTupleIsValid(statsTuple))
  		return;
  
diff -dcrpN postgresql.orig/src/backend/nodes/copyfuncs.c postgresql/src/backend/nodes/copyfuncs.c
*** postgresql.orig/src/backend/nodes/copyfuncs.c	2011-09-05 15:08:06.513567349 +0200
--- postgresql/src/backend/nodes/copyfuncs.c	2011-09-12 10:01:16.585506400 +0200
*************** _copyCreateForeignTableStmt(CreateForeig
*** 3455,3460 ****
--- 3455,3473 ----
  	return newnode;
  }
  
+ static ExtraStatStmt *
+ _copyExtraStatStmt(ExtraStatStmt *from)
+ {
+ 	ExtraStatStmt *newnode = makeNode(ExtraStatStmt);
+ 
+ 	COPY_SCALAR_FIELD(create);
+ 	newnode->relation = _copyRangeVar(from->relation);
+ 	COPY_NODE_FIELD(columns);
+ 	COPY_SCALAR_FIELD(statistics_target);
+ 
+ 	return newnode;
+ }
+ 
  static CreateTrigStmt *
  _copyCreateTrigStmt(CreateTrigStmt *from)
  {
*************** copyObject(void *from)
*** 4374,4379 ****
--- 4387,4395 ----
  		case T_CreateForeignTableStmt:
  			retval = _copyCreateForeignTableStmt(from);
  			break;
+ 		case T_ExtraStatStmt:
+ 			retval = _copyExtraStatStmt(from);
+ 			break;
  		case T_CreateTrigStmt:
  			retval = _copyCreateTrigStmt(from);
  			break;
diff -dcrpN postgresql.orig/src/backend/nodes/equalfuncs.c postgresql/src/backend/nodes/equalfuncs.c
*** postgresql.orig/src/backend/nodes/equalfuncs.c	2011-07-24 18:16:45.269678833 +0200
--- postgresql/src/backend/nodes/equalfuncs.c	2011-09-12 10:01:16.605505468 +0200
*************** _equalCreateForeignTableStmt(CreateForei
*** 1796,1801 ****
--- 1796,1813 ----
  }
  
  static bool
+ _equalExtraStatStmt(ExtraStatStmt *a, ExtraStatStmt *b)
+ {
+ 	COMPARE_SCALAR_FIELD(create);
+ 	if (!_equalRangeVar(a->relation, b->relation))
+ 		return FALSE;
+ 	COMPARE_NODE_FIELD(columns);
+ 	COMPARE_SCALAR_FIELD(statistics_target);
+ 
+ 	return true;
+ }
+ 
+ static bool
  _equalCreateTrigStmt(CreateTrigStmt *a, CreateTrigStmt *b)
  {
  	COMPARE_STRING_FIELD(trigname);
*************** equal(void *a, void *b)
*** 2931,2936 ****
--- 2943,2951 ----
  		case T_CreateForeignTableStmt:
  			retval = _equalCreateForeignTableStmt(a, b);
  			break;
+ 		case T_ExtraStatStmt:
+ 			retval = _equalExtraStatStmt(a, b);
+ 			break;
  		case T_CreateTrigStmt:
  			retval = _equalCreateTrigStmt(a, b);
  			break;
diff -dcrpN postgresql.orig/src/backend/parser/gram.y postgresql/src/backend/parser/gram.y
*** postgresql.orig/src/backend/parser/gram.y	2011-08-07 11:29:16.020256051 +0200
--- postgresql/src/backend/parser/gram.y	2011-09-12 10:01:16.618504860 +0200
*************** static void processCASbits(int cas_bits,
*** 214,220 ****
  		DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt
  		DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt
  		DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
! 		DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt
  		GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt
  		LockStmt NotifyStmt ExplainableStmt PreparableStmt
  		CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
--- 214,220 ----
  		DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt
  		DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt
  		DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
! 		DropForeignServerStmt DropUserMappingStmt ExplainStmt ExtraStatStmt FetchStmt
  		GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt
  		LockStmt NotifyStmt ExplainableStmt PreparableStmt
  		CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
*************** static void processCASbits(int cas_bits,
*** 246,252 ****
  				transaction_mode_item
  				create_extension_opt_item alter_extension_opt_item
  
! %type <ival>	opt_lock lock_type cast_context
  %type <ival>	vacuum_option_list vacuum_option_elem
  %type <boolean>	opt_force opt_or_replace
  				opt_grant_grant_option opt_grant_admin_option
--- 246,252 ----
  				transaction_mode_item
  				create_extension_opt_item alter_extension_opt_item
  
! %type <ival>	opt_lock lock_type cast_context opt_stattarget
  %type <ival>	vacuum_option_list vacuum_option_elem
  %type <boolean>	opt_force opt_or_replace
  				opt_grant_grant_option opt_grant_admin_option
*************** static void processCASbits(int cas_bits,
*** 325,330 ****
--- 325,332 ----
  %type <list>	opt_fdw_options fdw_options
  %type <defelt>	fdw_option
  
+ %type <list>	cc_column_list
+ 
  %type <range>	OptTempTableName
  %type <into>	into_clause create_as_target
  
*************** stmt :
*** 756,761 ****
--- 758,764 ----
  			| DropdbStmt
  			| ExecuteStmt
  			| ExplainStmt
+ 			| ExtraStatStmt
  			| FetchStmt
  			| GrantStmt
  			| GrantRoleStmt
*************** schema_stmt:
*** 1200,1205 ****
--- 1203,1276 ----
  
  /*****************************************************************************
   *
+  * Add / drop extra statistics
+  *
+  *****************************************************************************/
+ 
+ ExtraStatStmt:
+ 			CREATE CROSS COLUMN STATISTICS ON TABLE qualified_name '(' cc_column_list ')' opt_stattarget
+ 				{
+ 					ExtraStatStmt *n = makeNode(ExtraStatStmt);
+ 
+ 					n->relkind = 'r';
+ 					n->create = true;
+ 					n->relation = $7;
+ 					n->columns = $9;
+ 					n->statistics_target = $11;
+ 					$$ = (Node *)n;
+ 				}
+ 			| DROP CROSS COLUMN STATISTICS ON TABLE qualified_name '(' cc_column_list ')'
+ 				{
+ 					ExtraStatStmt *n = makeNode(ExtraStatStmt);
+ 
+ 					n->relkind = 'r';
+ 					n->create = false;
+ 					n->relation = $7;
+ 					n->columns = $9;
+ 					$$ = (Node *)n;
+ 				}
+ 			| CREATE CROSS COLUMN STATISTICS ON INDEX qualified_name opt_stattarget
+ 				{
+ 					ExtraStatStmt *n = makeNode(ExtraStatStmt);
+ 
+ 					n->relkind = 'i';
+ 					n->create = true;
+ 					n->relation = $7;
+ 					n->columns = NIL;
+ 					n->statistics_target = $8;
+ 					$$ = (Node *)n;
+ 				}
+ 			| DROP CROSS COLUMN STATISTICS ON INDEX qualified_name
+ 				{
+ 					ExtraStatStmt *n = makeNode(ExtraStatStmt);
+ 
+ 					n->relkind = 'i';
+ 					n->create = false;
+ 					n->relation = $7;
+ 					n->columns = NIL;
+ 					$$ = (Node *)n;
+ 				}
+ 		;
+ 
+ cc_column_list:
+ 			columnref
+ 				{
+ 					$$ = list_make1($1);
+ 				}
+ 			| cc_column_list ',' columnref
+ 				{
+ 					$$ = lappend($1, $3);
+ 				}
+ 		;
+ 
+ opt_stattarget:
+ 			WITH '(' Iconst ')'			{ $$ = $3; }
+ 			| /* EMPTY */				{ $$ = -1; }
+ 		;
+ 
+ 
+ /*****************************************************************************
+  *
   * Set PG internal variable
   *	  SET name TO 'var_value'
   * Include SQL92 syntax (thomas 1997-10-22):
diff -dcrpN postgresql.orig/src/backend/parser/parse_utilcmd.c postgresql/src/backend/parser/parse_utilcmd.c
*** postgresql.orig/src/backend/parser/parse_utilcmd.c	2011-09-02 12:52:41.361574202 +0200
--- postgresql/src/backend/parser/parse_utilcmd.c	2011-09-12 10:01:16.630504299 +0200
*************** setSchemaName(char *context_schema, char
*** 2732,2734 ****
--- 2732,2827 ----
  						"different from the one being created (%s)",
  						*stmt_schema_name, context_schema)));
  }
+ 
+ /*
+  * transformExtraStatistics
+  *             Transform the column list or the expression into a form
+  *             usable by the executor.
+  */
+ ExtraStatStmt *
+ transformExtraStatistics(ExtraStatStmt *stmt, const char *queryString)
+ {
+ 	ParseState	   *pstate;
+ 	RangeTblEntry	   *rte;
+ 	ExtraStatStmt	   *newstmt;
+ 	List		   *columns = NIL;
+ 	ListCell	   *cell;
+ 	Oid			relId;
+ 	HeapTuple		tuple;
+ 	HeapTuple		attuple;
+ 	Form_pg_class		classptr;
+ 	Form_pg_index		indexptr;
+ 	Form_pg_attribute	attptr;
+ 	AttrNumber		i;
+ 
+ 	switch (stmt->relkind)
+ 	{
+ 		case 'r':
+ 			pstate = make_parsestate(NULL);
+ 			pstate->p_sourcetext = queryString;
+ 
+ 			rte = addRangeTableEntry(pstate, stmt->relation, NULL, false, true);
+ 			addRTEtoQuery(pstate, rte, true, true, true);
+ 
+ 			foreach(cell, stmt->columns)
+ 			{
+ 				Node *col = lfirst(cell);
+ 
+ 				columns = lappend(columns, transformExpr(pstate, col));
+ 			}
+ 
+ 			break;
+ 
+ 		case 'i':
+ 			relId = RangeVarGetRelid(stmt->relation, ShareLock, false, false);
+ 
+ 			tuple = SearchSysCache1(RELOID, relId);
+ 			classptr = (Form_pg_class) GETSTRUCT(tuple);
+ 
+ 			if (classptr->relkind != 'i')
+ 				elog(ERROR, "not an index");
+ 
+ 			ReleaseSysCache(tuple);
+ 
+ 			tuple = SearchSysCache1(INDEXRELID, relId);
+ 			indexptr = (Form_pg_index) GETSTRUCT(tuple);
+ 
+ 			if (indexptr->indnatts < 2)
+ 			{
+ 				ReleaseSysCache(tuple);
+ 
+ 				elog(ERROR, "cross column statistics are only usable on multi-column indexes");
+ 			}
+ 
+ 			for (i = 1; i <= indexptr->indnatts; i++)
+ 			{
+ 				attuple = SearchSysCache2(ATTNUM, relId, i);
+ 				if (!HeapTupleIsValid(attuple))
+ 					elog(ERROR, "pg_attribute row not found for index");
+ 
+ 				attptr = (Form_pg_attribute) GETSTRUCT(attuple);
+ 
+ 				columns = lappend(columns, makeVar(0, i,
+ 										attptr->atttypid,
+ 										attptr->atttypmod,
+ 										InvalidOid, 0));
+ 
+ 				ReleaseSysCache(attuple);
+ 			}
+ 
+ 			ReleaseSysCache(tuple);
+ 			break;
+ 
+ 		default:
+ 			elog(ERROR, "invalid relkind");
+ 	}
+ 
+ 	newstmt = makeNode(ExtraStatStmt);
+ 	newstmt->relkind = stmt->relkind;
+ 	newstmt->create = stmt->create;
+ 	newstmt->relation = copyObject(stmt->relation);
+ 	newstmt->columns = columns;
+ 	newstmt->statistics_target = stmt->statistics_target;
+ 
+ 	return newstmt;
+ }
diff -dcrpN postgresql.orig/src/backend/tcop/utility.c postgresql/src/backend/tcop/utility.c
*** postgresql.orig/src/backend/tcop/utility.c	2011-07-24 18:16:45.276678481 +0200
--- postgresql/src/backend/tcop/utility.c	2011-09-12 10:01:16.639503878 +0200
*************** check_xact_readonly(Node *parsetree)
*** 237,242 ****
--- 237,243 ----
  		case T_AlterTableSpaceOptionsStmt:
  		case T_CreateForeignTableStmt:
  		case T_SecLabelStmt:
+ 		case T_ExtraStatStmt:
  			PreventCommandIfReadOnly(CreateCommandTag(parsetree));
  			break;
  		default:
*************** standard_ProcessUtility(Node *parsetree,
*** 581,586 ****
--- 582,595 ----
  			}
  			break;
  
+ 		case T_ExtraStatStmt:
+ 			{
+ 				ExtraStatStmt *newstmt = transformExtraStatistics((ExtraStatStmt *)parsetree, queryString);
+ 
+ 				ExtraStatistics(newstmt);
+ 			}
+ 			break;
+ 
  		case T_CreateTableSpaceStmt:
  			PreventTransactionChain(isTopLevel, "CREATE TABLESPACE");
  			CreateTableSpace((CreateTableSpaceStmt *) parsetree);
*************** CreateCommandTag(Node *parsetree)
*** 1744,1749 ****
--- 1753,1769 ----
  			tag = "CREATE FOREIGN TABLE";
  			break;
  
+ 		case T_ExtraStatStmt:
+ 			{
+ 				ExtraStatStmt *stmt = (ExtraStatStmt *)parsetree;
+ 
+ 				if (stmt->create)
+ 					tag = "CREATE CROSS COLUMN STATISTICS";
+ 				else
+ 					tag = "DROP CROSS COLUMN STATISTICS";
+ 			}
+ 			break;
+ 
  		case T_DropStmt:
  			switch (((DropStmt *) parsetree)->removeType)
  			{
diff -dcrpN postgresql.orig/src/backend/tsearch/ts_typanalyze.c postgresql/src/backend/tsearch/ts_typanalyze.c
*** postgresql.orig/src/backend/tsearch/ts_typanalyze.c	2011-09-02 12:52:41.368573634 +0200
--- postgresql/src/backend/tsearch/ts_typanalyze.c	2011-09-12 10:01:16.650503364 +0200
*************** typedef struct
*** 35,41 ****
  	int			delta;			/* And this is 'delta'. */
  } TrackItem;
  
! static void compute_tsvector_stats(VacAttrStats *stats,
  					   AnalyzeAttrFetchFunc fetchfunc,
  					   int samplerows,
  					   double totalrows);
--- 35,42 ----
  	int			delta;			/* And this is 'delta'. */
  } TrackItem;
  
! static void compute_tsvector_stats(VacStats *stats,
! 					   int index,
  					   AnalyzeAttrFetchFunc fetchfunc,
  					   int samplerows,
  					   double totalrows);
*************** static int	trackitem_compare_lexemes(con
*** 53,69 ****
  Datum
  ts_typanalyze(PG_FUNCTION_ARGS)
  {
! 	VacAttrStats *stats = (VacAttrStats *) PG_GETARG_POINTER(0);
! 	Form_pg_attribute attr = stats->attr;
  
! 	/* If the attstattarget column is negative, use the default value */
! 	/* NB: it is okay to scribble on stats->attr since it's a copy */
! 	if (attr->attstattarget < 0)
! 		attr->attstattarget = default_statistics_target;
  
! 	stats->compute_stats = compute_tsvector_stats;
  	/* see comment about the choice of minrows in commands/analyze.c */
! 	stats->minrows = 300 * attr->attstattarget;
  
  	PG_RETURN_BOOL(true);
  }
--- 54,74 ----
  Datum
  ts_typanalyze(PG_FUNCTION_ARGS)
  {
! 	VacStats *stats = (VacStats *) PG_GETARG_POINTER(0);
! 	int4		i = PG_GETARG_INT32(1);
  
! 	if (i < 0 || i >= STATISTIC_NUM_SLOTS)
! 		PG_RETURN_BOOL(false);
  
! 	/* If the statarget column is negative, use the default value */
! 	/* NB: it is okay to scribble on stats->statarget since it's a copy */
! 	if (stats->statarget < 0)
! 		stats->statarget = default_statistics_target;
! 
! 	stats->statfuncs[i].compute_func_ptr = compute_tsvector_stats;
! 	stats->statfuncs[i].findval_func_ptr = NULL; /* this prevents cross-column stats to be computed */
  	/* see comment about the choice of minrows in commands/analyze.c */
! 	stats->minrows = 300 * stats->statarget;
  
  	PG_RETURN_BOOL(true);
  }
*************** ts_typanalyze(PG_FUNCTION_ARGS)
*** 136,142 ****
   *	want.
   */
  static void
! compute_tsvector_stats(VacAttrStats *stats,
  					   AnalyzeAttrFetchFunc fetchfunc,
  					   int samplerows,
  					   double totalrows)
--- 141,148 ----
   *	want.
   */
  static void
! compute_tsvector_stats(VacStats *stats,
! 					   int index,
  					   AnalyzeAttrFetchFunc fetchfunc,
  					   int samplerows,
  					   double totalrows)
*************** compute_tsvector_stats(VacAttrStats *sta
*** 166,172 ****
  	 * the number of individual lexeme values tracked in pg_statistic ought to
  	 * be more than the number of values for a simple scalar column.
  	 */
! 	num_mcelem = stats->attr->attstattarget * 10;
  
  	/*
  	 * We set bucket width equal to (num_mcelem + 10) / 0.007 as per the
--- 172,178 ----
  	 * the number of individual lexeme values tracked in pg_statistic ought to
  	 * be more than the number of values for a simple scalar column.
  	 */
! 	num_mcelem = stats->statarget * 10;
  
  	/*
  	 * We set bucket width equal to (num_mcelem + 10) / 0.007 as per the
*************** compute_tsvector_stats(VacAttrStats *sta
*** 206,212 ****
  
  		vacuum_delay_point();
  
! 		value = fetchfunc(stats, vector_no, &isnull);
  
  		/*
  		 * Check for null/nonnull.
--- 212,218 ----
  
  		vacuum_delay_point();
  
! 		value = fetchfunc(stats, vector_no, stats->attnums->values[index], &isnull);
  
  		/*
  		 * Check for null/nonnull.
*************** compute_tsvector_stats(VacAttrStats *sta
*** 291,301 ****
  
  		stats->stats_valid = true;
  		/* Do the simple null-frac and average width stats */
! 		stats->stanullfrac = (double) null_cnt / (double) samplerows;
! 		stats->stawidth = total_width / (double) nonnull_cnt;
  
  		/* Assume it's a unique column (see notes above) */
! 		stats->stadistinct = -1.0;
  
  		/*
  		 * Construct an array of the interesting hashtable items, that is,
--- 297,307 ----
  
  		stats->stats_valid = true;
  		/* Do the simple null-frac and average width stats */
! 		stats->stanullfrac[index] = (double) null_cnt / (double) samplerows;
! 		stats->stawidth[index] = total_width / (double) nonnull_cnt;
  
  		/* Assume it's a unique column (see notes above) */
! 		stats->stadistinct[index] = -1.0;
  
  		/*
  		 * Construct an array of the interesting hashtable items, that is,
*************** compute_tsvector_stats(VacAttrStats *sta
*** 398,424 ****
  			mcelem_freqs[i] = (double) maxfreq / (double) nonnull_cnt;
  			MemoryContextSwitchTo(old_context);
  
! 			stats->stakind[0] = STATISTIC_KIND_MCELEM;
! 			stats->staop[0] = TextEqualOperator;
! 			stats->stanumbers[0] = mcelem_freqs;
  			/* See above comment about two extra frequency fields */
! 			stats->numnumbers[0] = num_mcelem + 2;
! 			stats->stavalues[0] = mcelem_values;
! 			stats->numvalues[0] = num_mcelem;
  			/* We are storing text values */
! 			stats->statypid[0] = TEXTOID;
! 			stats->statyplen[0] = -1;	/* typlen, -1 for varlena */
! 			stats->statypbyval[0] = false;
! 			stats->statypalign[0] = 'i';
  		}
  	}
  	else
  	{
  		/* We found only nulls; assume the column is entirely null */
  		stats->stats_valid = true;
! 		stats->stanullfrac = 1.0;
! 		stats->stawidth = 0;	/* "unknown" */
! 		stats->stadistinct = 0.0;		/* "unknown" */
  	}
  
  	/*
--- 404,430 ----
  			mcelem_freqs[i] = (double) maxfreq / (double) nonnull_cnt;
  			MemoryContextSwitchTo(old_context);
  
! 			stats->stakind[index][0] = STATISTIC_KIND_MCELEM;
! 			stats->staop[index][0] = TextEqualOperator;
! 			stats->stanumbers[index][0] = mcelem_freqs;
  			/* See above comment about two extra frequency fields */
! 			stats->numnumbers[index][0] = num_mcelem + 2;
! 			stats->stavalues[index][0] = mcelem_values;
! 			stats->numvalues[index][0] = num_mcelem;
  			/* We are storing text values */
! 			stats->statypid[index][0] = TEXTOID;
! 			stats->statyplen[index][0] = -1;	/* typlen, -1 for varlena */
! 			stats->statypbyval[index][0] = false;
! 			stats->statypalign[index][0] = 'i';
  		}
  	}
  	else
  	{
  		/* We found only nulls; assume the column is entirely null */
  		stats->stats_valid = true;
! 		stats->stanullfrac[index] = 1.0;
! 		stats->stawidth[index] = 0;	/* "unknown" */
! 		stats->stadistinct[index] = 0.0;		/* "unknown" */
  	}
  
  	/*
diff -dcrpN postgresql.orig/src/backend/utils/adt/int.c postgresql/src/backend/utils/adt/int.c
*** postgresql.orig/src/backend/utils/adt/int.c	2011-09-05 15:08:06.528566237 +0200
--- postgresql/src/backend/utils/adt/int.c	2011-09-12 10:01:16.665502664 +0200
*************** int2vectorsend(PG_FUNCTION_ARGS)
*** 254,274 ****
  	return array_send(fcinfo);
  }
  
- /*
-  * We don't have a complete set of int2vector support routines,
-  * but we need int2vectoreq for catcache indexing.
-  */
  Datum
  int2vectoreq(PG_FUNCTION_ARGS)
  {
! 	int2vector *a = (int2vector *) PG_GETARG_POINTER(0);
! 	int2vector *b = (int2vector *) PG_GETARG_POINTER(1);
  
! 	if (a->dim1 != b->dim1)
! 		PG_RETURN_BOOL(false);
! 	PG_RETURN_BOOL(memcmp(a->values, b->values, a->dim1 * sizeof(int2)) == 0);
  }
  
  
  /*****************************************************************************
   *	 PUBLIC ROUTINES														 *
--- 254,306 ----
  	return array_send(fcinfo);
  }
  
  Datum
  int2vectoreq(PG_FUNCTION_ARGS)
  {
! 	int32		cmp = DatumGetInt32(btint2vectorcmp(fcinfo));
  
! 	PG_RETURN_BOOL(cmp == 0);
! }
! 
! Datum
! int2vectorne(PG_FUNCTION_ARGS)
! {
! 	int32		cmp = DatumGetInt32(btint2vectorcmp(fcinfo));
! 
! 	PG_RETURN_BOOL(cmp != 0);
! }
! 
! Datum
! int2vectorlt(PG_FUNCTION_ARGS)
! {
! 	int32		cmp = DatumGetInt32(btint2vectorcmp(fcinfo));
! 
! 	PG_RETURN_BOOL(cmp < 0);
  }
  
+ Datum
+ int2vectorle(PG_FUNCTION_ARGS)
+ {
+ 	int32		cmp = DatumGetInt32(btint2vectorcmp(fcinfo));
+ 
+ 	PG_RETURN_BOOL(cmp <= 0);
+ }
+ 
+ Datum
+ int2vectorge(PG_FUNCTION_ARGS)
+ {
+ 	int32		cmp = DatumGetInt32(btint2vectorcmp(fcinfo));
+ 
+ 	PG_RETURN_BOOL(cmp >= 0);
+ }
+ 
+ Datum
+ int2vectorgt(PG_FUNCTION_ARGS)
+ {
+ 	int32		cmp = DatumGetInt32(btint2vectorcmp(fcinfo));
+ 
+ 	PG_RETURN_BOOL(cmp > 0);
+ }
  
  /*****************************************************************************
   *	 PUBLIC ROUTINES														 *
diff -dcrpN postgresql.orig/src/backend/utils/adt/selfuncs.c postgresql/src/backend/utils/adt/selfuncs.c
*** postgresql.orig/src/backend/utils/adt/selfuncs.c	2011-09-12 09:54:31.184313261 +0200
--- postgresql/src/backend/utils/adt/selfuncs.c	2011-09-12 10:01:16.679502009 +0200
*************** get_join_variables(PlannerInfo *root, Li
*** 4105,4110 ****
--- 4105,4138 ----
  }
  
  /*
+  * validate_statistics -- sets vardata->statsTuple only if the statistics is valid
+  */
+ void
+ validate_statistics(VariableStatData *vardata,
+ 			Oid relid, AttrNumber *attnums, int n_attnums, bool inherited)
+ {
+ 	int2vector *attnumvector = buildint2vector(attnums, n_attnums);
+ 	HeapTuple	tuple;
+ 
+ 	tuple = SearchSysCache3(STATRELATTINH,
+ 									ObjectIdGetDatum(relid),
+ 									PointerGetDatum(attnumvector),
+ 									  BoolGetDatum(inherited));
+ 	pfree(attnumvector);
+ 
+ 	if (HeapTupleIsValid(tuple))
+ 	{
+ 		if (((Form_pg_statistic) GETSTRUCT(tuple))->stavalid)
+ 		{
+ 			vardata->statsTuple = tuple;
+ 			vardata->freefunc = ReleaseSysCache;
+ 		}
+ 		else
+ 			ReleaseSysCache(tuple);
+ 	}
+ }
+ 
+ /*
   * examine_variable
   *		Try to look up statistical data about an expression.
   *		Fill in a VariableStatData struct to describe the expression.
*************** examine_variable(PlannerInfo *root, Node
*** 4292,4303 ****
  						}
  						else if (index->indpred == NIL)
  						{
! 							vardata->statsTuple =
! 								SearchSysCache3(STATRELATTINH,
! 										   ObjectIdGetDatum(index->indexoid),
! 												Int16GetDatum(pos + 1),
! 												BoolGetDatum(false));
! 							vardata->freefunc = ReleaseSysCache;
  						}
  						if (vardata->statsTuple)
  							break;
--- 4320,4328 ----
  						}
  						else if (index->indpred == NIL)
  						{
! 							int2		attnum = pos + 1;
! 
! 							validate_statistics(vardata, index->indexoid, &attnum, 1, false);
  						}
  						if (vardata->statsTuple)
  							break;
*************** examine_simple_variable(PlannerInfo *roo
*** 4345,4355 ****
  		 * Plain table or parent of an inheritance appendrel, so look up the
  		 * column in pg_statistic
  		 */
! 		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
! 											  ObjectIdGetDatum(rte->relid),
! 											  Int16GetDatum(var->varattno),
! 											  BoolGetDatum(rte->inh));
! 		vardata->freefunc = ReleaseSysCache;
  	}
  	else if (rte->rtekind == RTE_SUBQUERY && !rte->inh)
  	{
--- 4370,4376 ----
  		 * Plain table or parent of an inheritance appendrel, so look up the
  		 * column in pg_statistic
  		 */
! 		validate_statistics(vardata, rte->relid, &(var->varattno), 1, rte->inh);
  	}
  	else if (rte->rtekind == RTE_SUBQUERY && !rte->inh)
  	{
*************** btcostestimate(PG_FUNCTION_ARGS)
*** 6406,6418 ****
  				elog(ERROR, "no function provided to release variable stats with");
  		}
  		else
! 		{
! 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
! 												 ObjectIdGetDatum(relid),
! 												 Int16GetDatum(colnum),
! 												 BoolGetDatum(rte->inh));
! 			vardata.freefunc = ReleaseSysCache;
! 		}
  	}
  	else
  	{
--- 6427,6433 ----
  				elog(ERROR, "no function provided to release variable stats with");
  		}
  		else
! 			validate_statistics(&vardata, relid, &colnum, 1, rte->inh);
  	}
  	else
  	{
*************** btcostestimate(PG_FUNCTION_ARGS)
*** 6432,6444 ****
  				elog(ERROR, "no function provided to release variable stats with");
  		}
  		else
! 		{
! 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
! 												 ObjectIdGetDatum(relid),
! 												 Int16GetDatum(colnum),
! 												 BoolGetDatum(false));
! 			vardata.freefunc = ReleaseSysCache;
! 		}
  	}
  
  	if (HeapTupleIsValid(vardata.statsTuple))
--- 6447,6453 ----
  				elog(ERROR, "no function provided to release variable stats with");
  		}
  		else
! 			validate_statistics(&vardata, relid, &colnum, 1, false);
  	}
  
  	if (HeapTupleIsValid(vardata.statsTuple))
diff -dcrpN postgresql.orig/src/backend/utils/cache/lsyscache.c postgresql/src/backend/utils/cache/lsyscache.c
*** postgresql.orig/src/backend/utils/cache/lsyscache.c	2011-07-18 15:42:00.066375563 +0200
--- postgresql/src/backend/utils/cache/lsyscache.c	2011-09-12 10:01:16.696501214 +0200
*************** get_attavgwidth(Oid relid, AttrNumber at
*** 2632,2637 ****
--- 2632,2638 ----
  {
  	HeapTuple	tp;
  	int32		stawidth;
+ 	int2vector *attnumvector = NULL;
  
  	if (get_attavgwidth_hook)
  	{
*************** get_attavgwidth(Oid relid, AttrNumber at
*** 2639,2648 ****
  		if (stawidth > 0)
  			return stawidth;
  	}
  	tp = SearchSysCache3(STATRELATTINH,
  						 ObjectIdGetDatum(relid),
! 						 Int16GetDatum(attnum),
  						 BoolGetDatum(false));
  	if (HeapTupleIsValid(tp))
  	{
  		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
--- 2640,2651 ----
  		if (stawidth > 0)
  			return stawidth;
  	}
+ 	attnumvector = buildint2vector(&attnum, 1);
  	tp = SearchSysCache3(STATRELATTINH,
  						 ObjectIdGetDatum(relid),
! 						 PointerGetDatum(attnumvector),
  						 BoolGetDatum(false));
+ 	pfree(attnumvector);
  	if (HeapTupleIsValid(tp))
  	{
  		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
*************** get_attstatsslot(HeapTuple statstuple,
*** 2721,2728 ****
  		val = SysCacheGetAttr(STATRELATTINH, statstuple,
  							  Anum_pg_statistic_stavalues1 + i,
  							  &isnull);
  		if (isnull)
! 			elog(ERROR, "stavalues is null");
  		statarray = DatumGetArrayTypeP(val);
  
  		/*
--- 2724,2732 ----
  		val = SysCacheGetAttr(STATRELATTINH, statstuple,
  							  Anum_pg_statistic_stavalues1 + i,
  							  &isnull);
+ 		/* invalid stats record, i.e. analyze hasn't yet run for this column */
  		if (isnull)
! 			return false;
  		statarray = DatumGetArrayTypeP(val);
  
  		/*
*************** get_attstatsslot(HeapTuple statstuple,
*** 2775,2782 ****
  		val = SysCacheGetAttr(STATRELATTINH, statstuple,
  							  Anum_pg_statistic_stanumbers1 + i,
  							  &isnull);
  		if (isnull)
! 			elog(ERROR, "stanumbers is null");
  		statarray = DatumGetArrayTypeP(val);
  
  		/*
--- 2779,2787 ----
  		val = SysCacheGetAttr(STATRELATTINH, statstuple,
  							  Anum_pg_statistic_stanumbers1 + i,
  							  &isnull);
+ 		/* invalid stats record, i.e. analyze hasn't yet run for this column */
  		if (isnull)
! 			return false;
  		statarray = DatumGetArrayTypeP(val);
  
  		/*
diff -dcrpN postgresql.orig/src/backend/utils/cache/syscache.c postgresql/src/backend/utils/cache/syscache.c
*** postgresql.orig/src/backend/utils/cache/syscache.c	2011-06-20 10:11:35.741660316 +0200
--- postgresql/src/backend/utils/cache/syscache.c	2011-09-12 10:01:16.706500748 +0200
*************** static const struct cachedesc cacheinfo[
*** 588,598 ****
  		1024
  	},
  	{StatisticRelationId,		/* STATRELATTINH */
! 		StatisticRelidAttnumInhIndexId,
  		3,
  		{
  			Anum_pg_statistic_starelid,
! 			Anum_pg_statistic_staattnum,
  			Anum_pg_statistic_stainherit,
  			0
  		},
--- 588,598 ----
  		1024
  	},
  	{StatisticRelationId,		/* STATRELATTINH */
! 		StatisticRelidAttnumsInhIndexId,
  		3,
  		{
  			Anum_pg_statistic_starelid,
! 			Anum_pg_statistic_staattnums,
  			Anum_pg_statistic_stainherit,
  			0
  		},
diff -dcrpN postgresql.orig/src/include/catalog/heap.h postgresql/src/include/catalog/heap.h
*** postgresql.orig/src/include/catalog/heap.h	2011-07-24 18:16:45.286677978 +0200
--- postgresql/src/include/catalog/heap.h	2011-09-12 10:01:16.717500234 +0200
*************** extern void RemoveAttributeById(Oid reli
*** 107,113 ****
  extern void RemoveAttrDefault(Oid relid, AttrNumber attnum,
  				  DropBehavior behavior, bool complain);
  extern void RemoveAttrDefaultById(Oid attrdefId);
! extern void RemoveStatistics(Oid relid, AttrNumber attnum);
  
  extern Form_pg_attribute SystemAttributeDefinition(AttrNumber attno,
  						  bool relhasoids);
--- 107,118 ----
  extern void RemoveAttrDefault(Oid relid, AttrNumber attnum,
  				  DropBehavior behavior, bool complain);
  extern void RemoveAttrDefaultById(Oid attrdefId);
! extern void AddStatistics(Oid relid, AttrNumber *attnums,
! 						  int n_attnums,
! 						  bool inherited,
! 						  int statistics_target);
! extern void InvalidateStatistics(Oid relid, AttrNumber attnum);
! extern void RemoveStatistics(Oid relid, AttrNumber *attnums, int n_attnums);
  
  extern Form_pg_attribute SystemAttributeDefinition(AttrNumber attno,
  						  bool relhasoids);
diff -dcrpN postgresql.orig/src/include/catalog/indexing.h postgresql/src/include/catalog/indexing.h
*** postgresql.orig/src/include/catalog/indexing.h	2011-07-24 18:16:45.286677978 +0200
--- postgresql/src/include/catalog/indexing.h	2011-09-12 10:01:16.748498784 +0200
*************** DECLARE_INDEX(pg_shdepend_depender_index
*** 218,225 ****
  DECLARE_INDEX(pg_shdepend_reference_index, 1233, on pg_shdepend using btree(refclassid oid_ops, refobjid oid_ops));
  #define SharedDependReferenceIndexId	1233
  
! DECLARE_UNIQUE_INDEX(pg_statistic_relid_att_inh_index, 2696, on pg_statistic using btree(starelid oid_ops, staattnum int2_ops, stainherit bool_ops));
! #define StatisticRelidAttnumInhIndexId	2696
  
  DECLARE_UNIQUE_INDEX(pg_tablespace_oid_index, 2697, on pg_tablespace using btree(oid oid_ops));
  #define TablespaceOidIndexId  2697
--- 218,225 ----
  DECLARE_INDEX(pg_shdepend_reference_index, 1233, on pg_shdepend using btree(refclassid oid_ops, refobjid oid_ops));
  #define SharedDependReferenceIndexId	1233
  
! DECLARE_UNIQUE_INDEX(pg_statistic_relid_att_inh_index, 2696, on pg_statistic using btree(starelid oid_ops, staattnums int2vector_ops, stainherit bool_ops));
! #define StatisticRelidAttnumsInhIndexId	2696
  
  DECLARE_UNIQUE_INDEX(pg_tablespace_oid_index, 2697, on pg_tablespace using btree(oid oid_ops));
  #define TablespaceOidIndexId  2697
diff -dcrpN postgresql.orig/src/include/catalog/pg_amop.h postgresql/src/include/catalog/pg_amop.h
*** postgresql.orig/src/include/catalog/pg_amop.h	2011-04-11 15:36:27.235807013 +0200
--- postgresql/src/include/catalog/pg_amop.h	2011-09-12 10:01:16.757498366 +0200
*************** DATA(insert (	1991   30 30 4 s	648 403 0
*** 185,190 ****
--- 185,200 ----
  DATA(insert (	1991   30 30 5 s	646 403 0 ));
  
  /*
+  *	btree int2vector_ops
+  */
+ 
+ DATA(insert (	3097   22 22 1 s	199 403 0 ));
+ DATA(insert (	3097   22 22 2 s	322 403 0 ));
+ DATA(insert (	3097   22 22 3 s	386 403 0 ));
+ DATA(insert (	3097   22 22 4 s	323 403 0 ));
+ DATA(insert (	3097   22 22 5 s	276 403 0 ));
+ 
+ /*
   *	btree float_ops
   */
  
diff -dcrpN postgresql.orig/src/include/catalog/pg_amproc.h postgresql/src/include/catalog/pg_amproc.h
*** postgresql.orig/src/include/catalog/pg_amproc.h	2011-01-04 15:13:16.120551585 +0100
--- postgresql/src/include/catalog/pg_amproc.h	2011-09-12 10:01:16.768497849 +0200
*************** DATA(insert (	2233   703 703 1  380 ));
*** 123,128 ****
--- 123,129 ----
  DATA(insert (	2234   704 704 1  381 ));
  DATA(insert (	2789   27 27 1 2794 ));
  DATA(insert (	2968   2950 2950 1 2960 ));
+ DATA(insert (	3097   22 22 1 321 ));
  DATA(insert (	3522   3500 3500 1 3514 ));
  
  
diff -dcrpN postgresql.orig/src/include/catalog/pg_attribute.h postgresql/src/include/catalog/pg_attribute.h
*** postgresql.orig/src/include/catalog/pg_attribute.h	2011-08-07 11:29:16.032255410 +0200
--- postgresql/src/include/catalog/pg_attribute.h	2011-09-12 10:01:16.779497338 +0200
*************** CATALOG(pg_attribute,1249) BKI_BOOTSTRAP
*** 48,62 ****
  	Oid			atttypid;
  
  	/*
- 	 * attstattarget is the target number of statistics datapoints to collect
- 	 * during VACUUM ANALYZE of this column.  A zero here indicates that we do
- 	 * not wish to collect any stats about this column. A "-1" here indicates
- 	 * that no value has been explicitly set for this column, so ANALYZE
- 	 * should use the default setting.
- 	 */
- 	int4		attstattarget;
- 
- 	/*
  	 * attlen is a copy of the typlen field from pg_type for this attribute.
  	 * See atttypid comments above.
  	 */
--- 48,53 ----
*************** typedef FormData_pg_attribute *Form_pg_a
*** 182,209 ****
   * ----------------
   */
  
! #define Natts_pg_attribute				21
  #define Anum_pg_attribute_attrelid		1
  #define Anum_pg_attribute_attname		2
  #define Anum_pg_attribute_atttypid		3
! #define Anum_pg_attribute_attstattarget 4
! #define Anum_pg_attribute_attlen		5
! #define Anum_pg_attribute_attnum		6
! #define Anum_pg_attribute_attndims		7
! #define Anum_pg_attribute_attcacheoff	8
! #define Anum_pg_attribute_atttypmod		9
! #define Anum_pg_attribute_attbyval		10
! #define Anum_pg_attribute_attstorage	11
! #define Anum_pg_attribute_attalign		12
! #define Anum_pg_attribute_attnotnull	13
! #define Anum_pg_attribute_atthasdef		14
! #define Anum_pg_attribute_attisdropped	15
! #define Anum_pg_attribute_attislocal	16
! #define Anum_pg_attribute_attinhcount	17
! #define Anum_pg_attribute_attcollation	18
! #define Anum_pg_attribute_attacl		19
! #define Anum_pg_attribute_attoptions	20
! #define Anum_pg_attribute_attfdwoptions	21
  
  
  /* ----------------
--- 173,199 ----
   * ----------------
   */
  
! #define Natts_pg_attribute				20
  #define Anum_pg_attribute_attrelid		1
  #define Anum_pg_attribute_attname		2
  #define Anum_pg_attribute_atttypid		3
! #define Anum_pg_attribute_attlen		4
! #define Anum_pg_attribute_attnum		5
! #define Anum_pg_attribute_attndims		6
! #define Anum_pg_attribute_attcacheoff		7
! #define Anum_pg_attribute_atttypmod		8
! #define Anum_pg_attribute_attbyval		9
! #define Anum_pg_attribute_attstorage		10
! #define Anum_pg_attribute_attalign		11
! #define Anum_pg_attribute_attnotnull		12
! #define Anum_pg_attribute_atthasdef		13
! #define Anum_pg_attribute_attisdropped		14
! #define Anum_pg_attribute_attislocal		15
! #define Anum_pg_attribute_attinhcount		16
! #define Anum_pg_attribute_attcollation		17
! #define Anum_pg_attribute_attacl		18
! #define Anum_pg_attribute_attoptions		19
! #define Anum_pg_attribute_attfdwoptions		20
  
  
  /* ----------------
diff -dcrpN postgresql.orig/src/include/catalog/pg_class.h postgresql/src/include/catalog/pg_class.h
*** postgresql.orig/src/include/catalog/pg_class.h	2011-08-07 11:29:16.032255410 +0200
--- postgresql/src/include/catalog/pg_class.h	2011-09-12 10:01:16.790496821 +0200
*************** typedef FormData_pg_class *Form_pg_class
*** 132,138 ****
  /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
  DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f 3 _null_ _null_ ));
  DESCR("");
  DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 26 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
--- 132,138 ----
  /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
  DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 20 0 f f f f f 3 _null_ _null_ ));
  DESCR("");
  DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 26 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
diff -dcrpN postgresql.orig/src/include/catalog/pg_opclass.h postgresql/src/include/catalog/pg_opclass.h
*** postgresql.orig/src/include/catalog/pg_opclass.h	2011-01-04 15:13:16.125551330 +0100
--- postgresql/src/include/catalog/pg_opclass.h	2011-09-12 10:01:16.799496403 +0200
*************** DATA(insert (	403		bpchar_pattern_ops	PG
*** 158,163 ****
--- 158,164 ----
  DATA(insert (	403		money_ops			PGNSP PGUID 2099  790 t 0 ));
  DATA(insert (	405		bool_ops			PGNSP PGUID 2222   16 t 0 ));
  DATA(insert (	405		bytea_ops			PGNSP PGUID 2223   17 t 0 ));
+ DATA(insert (	403		int2vector_ops		PGNSP PGUID 3097   22 t 0 ));
  DATA(insert (	405		int2vector_ops		PGNSP PGUID 2224   22 t 0 ));
  DATA(insert (	403		tid_ops				PGNSP PGUID 2789   27 t 0 ));
  DATA(insert (	405		xid_ops				PGNSP PGUID 2225   28 t 0 ));
diff -dcrpN postgresql.orig/src/include/catalog/pg_operator.h postgresql/src/include/catalog/pg_operator.h
*** postgresql.orig/src/include/catalog/pg_operator.h	2011-06-06 09:12:54.421675553 +0200
--- postgresql/src/include/catalog/pg_operator.h	2011-09-12 10:01:16.814495702 +0200
*************** DATA(insert OID =  98 ( "="		   PGNSP PG
*** 134,139 ****
--- 134,150 ----
  DESCR("equal");
  #define TextEqualOperator	98
  
+ DATA(insert OID = 114 (  "<>"	   PGNSP PGUID b f f	22	22	16 114 386 int2vectorne neqsel neqjoinsel ));
+ DESCR("not equal");
+ DATA(insert OID = 199 (  "<"	   PGNSP PGUID b f f	22	22	16 276 323 int2vectorlt scalarltsel scalarltjoinsel ));
+ DESCR("less than");
+ DATA(insert OID = 276 (  ">"	   PGNSP PGUID b f f	22	22	16 199 322 int2vectorgt scalargtsel scalargtjoinsel ));
+ DESCR("greater than");
+ DATA(insert OID = 322 (  "<="	   PGNSP PGUID b f f	22	22	16 323 276 int2vectorle scalarltsel scalarltjoinsel ));
+ DESCR("less than or equal");
+ DATA(insert OID = 323 (  ">="	   PGNSP PGUID b f f	22	22	16 322 199 int2vectorge scalargtsel scalargtjoinsel ));
+ DESCR("greater than or equal");
+ 
  DATA(insert OID = 349 (  "||"	   PGNSP PGUID b f f 2277 2283 2277 0 0 array_append   -	   -	 ));
  DESCR("append element onto end of array");
  DATA(insert OID = 374 (  "||"	   PGNSP PGUID b f f 2283 2277 2277 0 0 array_prepend  -	   -	 ));
*************** DATA(insert OID = 389 (  "!!"	   PGNSP P
*** 151,157 ****
  DESCR("deprecated, use ! instead");
  DATA(insert OID = 385 (  "="	   PGNSP PGUID b f t	29	29	16 385	 0 cideq eqsel eqjoinsel ));
  DESCR("equal");
! DATA(insert OID = 386 (  "="	   PGNSP PGUID b f t	22	22	16 386	 0 int2vectoreq eqsel eqjoinsel ));
  DESCR("equal");
  
  DATA(insert OID = 387 (  "="	   PGNSP PGUID b t f	27	27	16 387 402 tideq eqsel eqjoinsel ));
--- 162,168 ----
  DESCR("deprecated, use ! instead");
  DATA(insert OID = 385 (  "="	   PGNSP PGUID b f t	29	29	16 385	 0 cideq eqsel eqjoinsel ));
  DESCR("equal");
! DATA(insert OID = 386 (  "="	   PGNSP PGUID b t t	22	22	16 386 114 int2vectoreq eqsel eqjoinsel ));
  DESCR("equal");
  
  DATA(insert OID = 387 (  "="	   PGNSP PGUID b t f	27	27	16 387 402 tideq eqsel eqjoinsel ));
diff -dcrpN postgresql.orig/src/include/catalog/pg_opfamily.h postgresql/src/include/catalog/pg_opfamily.h
*** postgresql.orig/src/include/catalog/pg_opfamily.h	2011-01-04 15:13:16.126551278 +0100
--- postgresql/src/include/catalog/pg_opfamily.h	2011-09-12 10:01:16.833494812 +0200
*************** DATA(insert OID = 2099 (	403		money_ops	
*** 114,119 ****
--- 114,120 ----
  DATA(insert OID = 2222 (	405		bool_ops		PGNSP PGUID ));
  #define BOOL_HASH_FAM_OID 2222
  DATA(insert OID = 2223 (	405		bytea_ops		PGNSP PGUID ));
+ DATA(insert OID = 3097 (	403		int2vector_ops	PGNSP PGUID ));
  DATA(insert OID = 2224 (	405		int2vector_ops	PGNSP PGUID ));
  DATA(insert OID = 2789 (	403		tid_ops			PGNSP PGUID ));
  DATA(insert OID = 2225 (	405		xid_ops			PGNSP PGUID ));
diff -dcrpN postgresql.orig/src/include/catalog/pg_proc.h postgresql/src/include/catalog/pg_proc.h
*** postgresql.orig/src/include/catalog/pg_proc.h	2011-07-18 15:42:00.078374691 +0200
--- postgresql/src/include/catalog/pg_proc.h	2011-09-12 10:01:16.849494065 +0200
*************** DESCR("length");
*** 216,221 ****
--- 216,228 ----
  DATA(insert OID = 1258 (  textcat		   PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 25 "25 25" _null_ _null_ _null_ _null_ textcat _null_ _null_ _null_ ));
  
  DATA(insert OID =  84 (  boolne			   PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "16 16" _null_ _null_ _null_ _null_ boolne _null_ _null_ _null_ ));
+ 
+ DATA(insert OID =  86 (  int2vectorne		   PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "22 22" _null_ _null_ _null_ _null_ int2vectorne _null_ _null_ _null_ ));
+ DATA(insert OID =  87 (  int2vectorlt		   PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "22 22" _null_ _null_ _null_ _null_ int2vectorlt _null_ _null_ _null_ ));
+ DATA(insert OID =  88 (  int2vectorle		   PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "22 22" _null_ _null_ _null_ _null_ int2vectorle _null_ _null_ _null_ ));
+ DATA(insert OID =  90 (  int2vectorge		   PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "22 22" _null_ _null_ _null_ _null_ int2vectorge _null_ _null_ _null_ ));
+ DATA(insert OID = 3122 (  int2vectorgt		   PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 16 "22 22" _null_ _null_ _null_ _null_ int2vectorgt _null_ _null_ _null_ ));
+ 
  DATA(insert OID =  89 (  version		   PGNSP PGUID 12 1 0 0 0 f f f t f s 0 0 25 "" _null_ _null_ _null_ _null_ pgsql_version _null_ _null_ _null_ ));
  DESCR("PostgreSQL version string");
  
*************** DESCR("I/O");
*** 566,571 ****
--- 573,580 ----
  
  DATA(insert OID = 350 (  btint2cmp		   PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 23 "21 21" _null_ _null_ _null_ _null_ btint2cmp _null_ _null_ _null_ ));
  DESCR("less-equal-greater");
+ DATA(insert OID = 321 (  btint2vectorcmp	   PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 23 "22 22" _null_ _null_ _null_ _null_ btint2vectorcmp _null_ _null_ _null_ ));
+ DESCR("less-equal-greater");
  DATA(insert OID = 351 (  btint4cmp		   PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 23 "23 23" _null_ _null_ _null_ _null_ btint4cmp _null_ _null_ _null_ ));
  DESCR("less-equal-greater");
  DATA(insert OID = 842 (  btint8cmp		   PGNSP PGUID 12 1 0 0 0 f f f t f i 2 0 23 "20 20" _null_ _null_ _null_ _null_ btint8cmp _null_ _null_ _null_ ));
*************** DATA(insert OID = 3686 (  tsmatchsel		PG
*** 4141,4147 ****
  DESCR("restriction selectivity of tsvector @@ tsquery");
  DATA(insert OID = 3687 (  tsmatchjoinsel	PGNSP PGUID 12 1 0 0 0 f f f t f s 5 0 701 "2281 26 2281 21 2281" _null_ _null_ _null_ _null_ tsmatchjoinsel _null_ _null_ _null_ ));
  DESCR("join selectivity of tsvector @@ tsquery");
! DATA(insert OID = 3688 (  ts_typanalyze		PGNSP PGUID 12 1 0 0 0 f f f t f s 1 0 16 "2281" _null_ _null_ _null_ _null_ ts_typanalyze _null_ _null_ _null_ ));
  DESCR("tsvector typanalyze");
  
  DATA(insert OID = 3689 (  ts_stat		PGNSP PGUID 12 10 10000 0 0 f f f t t v 1 0 2249 "25" "{25,25,23,23}" "{i,o,o,o}" "{query,word,ndoc,nentry}" _null_ ts_stat1 _null_ _null_ _null_ ));
--- 4150,4156 ----
  DESCR("restriction selectivity of tsvector @@ tsquery");
  DATA(insert OID = 3687 (  tsmatchjoinsel	PGNSP PGUID 12 1 0 0 0 f f f t f s 5 0 701 "2281 26 2281 21 2281" _null_ _null_ _null_ _null_ tsmatchjoinsel _null_ _null_ _null_ ));
  DESCR("join selectivity of tsvector @@ tsquery");
! DATA(insert OID = 3688 (  ts_typanalyze		PGNSP PGUID 12 1 0 0 0 f f f t f s 2 0 16 "2281 23" _null_ _null_ _null_ _null_ ts_typanalyze _null_ _null_ _null_ ));
  DESCR("tsvector typanalyze");
  
  DATA(insert OID = 3689 (  ts_stat		PGNSP PGUID 12 10 10000 0 0 f f f t t v 1 0 2249 "25" "{25,25,23,23}" "{i,o,o,o}" "{query,word,ndoc,nentry}" _null_ ts_stat1 _null_ _null_ _null_ ));
diff -dcrpN postgresql.orig/src/include/catalog/pg_statistic.h postgresql/src/include/catalog/pg_statistic.h
*** postgresql.orig/src/include/catalog/pg_statistic.h	2011-02-22 18:51:42.762512469 +0100
--- postgresql/src/include/catalog/pg_statistic.h	2011-09-12 10:01:16.877492756 +0200
***************
*** 40,50 ****
  
  CATALOG(pg_statistic,2619) BKI_WITHOUT_OIDS
  {
! 	/* These fields form the unique key for the entry: */
  	Oid			starelid;		/* relation containing attribute */
- 	int2		staattnum;		/* attribute (column) stats are for */
  	bool		stainherit;		/* true if inheritance children are included */
  
  	/* the fraction of the column's entries that are NULL: */
  	float4		stanullfrac;
  
--- 40,63 ----
  
  CATALOG(pg_statistic,2619) BKI_WITHOUT_OIDS
  {
! 	/*
! 	 * These fields (together with the staattnums field below in the variable fields)
! 	 * form the unique key for the entry:
! 	 */
  	Oid			starelid;		/* relation containing attribute */
  	bool		stainherit;		/* true if inheritance children are included */
  
+ 	/* this entry is valid */
+ 	bool		stavalid;
+ 
+ 	/* statarget is the target number of statistics datapoints to collect
+ 	 * during VACUUM ANALYZE of this column.  A zero here indicates that we do
+ 	 * not wish to collect any stats about this column. A "-1" here indicates
+ 	 * that no value has been explicitly set for this column, so ANALYZE
+ 	 * should use the default setting.
+ 	 */
+ 	int4		statarget;
+ 
  	/* the fraction of the column's entries that are NULL: */
  	float4		stanullfrac;
  
*************** CATALOG(pg_statistic,2619) BKI_WITHOUT_O
*** 110,115 ****
--- 123,129 ----
  	 * the full field access machinery (heap_getattr) for them.  We declare
  	 * them here for the catalog machinery.
  	 */
+ 	int2vector		staattnums;		/* attributes (columns) stats are for */
  
  	float4		stanumbers1[1];
  	float4		stanumbers2[1];
*************** typedef FormData_pg_statistic *Form_pg_s
*** 143,171 ****
   *		compiler constants for pg_statistic
   * ----------------
   */
! #define Natts_pg_statistic				22
  #define Anum_pg_statistic_starelid		1
! #define Anum_pg_statistic_staattnum		2
! #define Anum_pg_statistic_stainherit	3
! #define Anum_pg_statistic_stanullfrac	4
! #define Anum_pg_statistic_stawidth		5
! #define Anum_pg_statistic_stadistinct	6
! #define Anum_pg_statistic_stakind1		7
! #define Anum_pg_statistic_stakind2		8
! #define Anum_pg_statistic_stakind3		9
! #define Anum_pg_statistic_stakind4		10
! #define Anum_pg_statistic_staop1		11
! #define Anum_pg_statistic_staop2		12
! #define Anum_pg_statistic_staop3		13
! #define Anum_pg_statistic_staop4		14
! #define Anum_pg_statistic_stanumbers1	15
! #define Anum_pg_statistic_stanumbers2	16
! #define Anum_pg_statistic_stanumbers3	17
! #define Anum_pg_statistic_stanumbers4	18
! #define Anum_pg_statistic_stavalues1	19
! #define Anum_pg_statistic_stavalues2	20
! #define Anum_pg_statistic_stavalues3	21
! #define Anum_pg_statistic_stavalues4	22
  
  /*
   * Currently, three statistical slot "kinds" are defined: most common values,
--- 157,187 ----
   *		compiler constants for pg_statistic
   * ----------------
   */
! #define Natts_pg_statistic				24
  #define Anum_pg_statistic_starelid		1
! #define Anum_pg_statistic_stainherit		2
! #define Anum_pg_statistic_stavalid		3
! #define Anum_pg_statistic_statarget		4
! #define Anum_pg_statistic_stanullfrac		5
! #define Anum_pg_statistic_stawidth		6
! #define Anum_pg_statistic_stadistinct		7
! #define Anum_pg_statistic_stakind1		8
! #define Anum_pg_statistic_stakind2		9
! #define Anum_pg_statistic_stakind3		10
! #define Anum_pg_statistic_stakind4		11
! #define Anum_pg_statistic_staop1		12
! #define Anum_pg_statistic_staop2		13
! #define Anum_pg_statistic_staop3		14
! #define Anum_pg_statistic_staop4		15
! #define Anum_pg_statistic_staattnums		16
! #define Anum_pg_statistic_stanumbers1	17
! #define Anum_pg_statistic_stanumbers2	18
! #define Anum_pg_statistic_stanumbers3	19
! #define Anum_pg_statistic_stanumbers4	20
! #define Anum_pg_statistic_stavalues1	21
! #define Anum_pg_statistic_stavalues2	22
! #define Anum_pg_statistic_stavalues3	23
! #define Anum_pg_statistic_stavalues4	24
  
  /*
   * Currently, three statistical slot "kinds" are defined: most common values,
diff -dcrpN postgresql.orig/src/include/commands/defrem.h postgresql/src/include/commands/defrem.h
*** postgresql.orig/src/include/commands/defrem.h	2011-07-24 18:16:45.287677928 +0200
--- postgresql/src/include/commands/defrem.h	2011-09-12 10:01:16.899491728 +0200
*************** extern void RemoveAggregate(RemoveFuncSt
*** 93,98 ****
--- 93,101 ----
  extern void RenameAggregate(List *name, List *args, const char *newname);
  extern void AlterAggregateOwner(List *name, List *args, Oid newOwnerId);
  
+ /* commands/analyze.c */
+ extern void ExtraStatistics(ExtraStatStmt *stmt);
+ 
  /* commands/opclasscmds.c */
  extern void DefineOpClass(CreateOpClassStmt *stmt);
  extern void DefineOpFamily(CreateOpFamilyStmt *stmt);
diff -dcrpN postgresql.orig/src/include/commands/vacuum.h postgresql/src/include/commands/vacuum.h
*** postgresql.orig/src/include/commands/vacuum.h	2011-06-02 10:21:24.006634564 +0200
--- postgresql/src/include/commands/vacuum.h	2011-09-12 10:01:16.912491121 +0200
***************
*** 56,67 ****
   * This might change in some future release.
   *----------
   */
! typedef struct VacAttrStats *VacAttrStatsP;
  
! typedef Datum (*AnalyzeAttrFetchFunc) (VacAttrStatsP stats, int rownum,
  												   bool *isNull);
  
! typedef struct VacAttrStats
  {
  	/*
  	 * These fields are set up by the main ANALYZE code before invoking the
--- 56,84 ----
   * This might change in some future release.
   *----------
   */
! typedef struct VacStats *VacStatsP;
  
! typedef Datum (*AnalyzeAttrFetchFunc) (VacStatsP stats, int rownum, AttrNumber tupattnum,
  												   bool *isNull);
  
! typedef void (*ComputeStatsFunc) (VacStatsP stats,
! 												   int index,
! 												   AnalyzeAttrFetchFunc fetchfunc,
! 												   int samplerows,
! 												   double totalrows);
! 
! typedef int (*FindValueIndex) (VacStatsP stats,
! 												   int rownum,
! 												   int index,
! 												   AnalyzeAttrFetchFunc fetchfunc,
! 												   void *arg);
! 
! typedef struct StatsFuncStruct {
! 	ComputeStatsFunc	compute_func_ptr;
! 	FindValueIndex		findval_func_ptr;
! } StatsFuncStruct;
! 
! typedef struct VacStats
  {
  	/*
  	 * These fields are set up by the main ANALYZE code before invoking the
*************** typedef struct VacAttrStats
*** 73,109 ****
  	 * column/expression.  Instead use attrtypid, attrtypmod, and attrtype for
  	 * information about the datatype being fed to the typanalyze function.
  	 */
! 	Form_pg_attribute attr;		/* copy of pg_attribute row for column */
! 	Oid			attrtypid;		/* type of data being analyzed */
! 	int32		attrtypmod;		/* typmod of data being analyzed */
! 	Form_pg_type attrtype;		/* copy of pg_type row for attrtypid */
  	MemoryContext anl_context;	/* where to save long-lived data */
  
  	/*
  	 * These fields must be filled in by the typanalyze routine, unless it
  	 * returns FALSE.
  	 */
! 	void		(*compute_stats) (VacAttrStatsP stats,
! 											  AnalyzeAttrFetchFunc fetchfunc,
! 											  int samplerows,
! 											  double totalrows);
  	int			minrows;		/* Minimum # of rows wanted for stats */
! 	void	   *extra_data;		/* for extra type-specific data */
  
  	/*
  	 * These fields are to be filled in by the compute_stats routine. (They
  	 * are initialized to zero when the struct is created.)
  	 */
  	bool		stats_valid;
! 	float4		stanullfrac;	/* fraction of entries that are NULL */
! 	int4		stawidth;		/* average width of column values */
! 	float4		stadistinct;	/* # distinct values */
! 	int2		stakind[STATISTIC_NUM_SLOTS];
! 	Oid			staop[STATISTIC_NUM_SLOTS];
! 	int			numnumbers[STATISTIC_NUM_SLOTS];
! 	float4	   *stanumbers[STATISTIC_NUM_SLOTS];
! 	int			numvalues[STATISTIC_NUM_SLOTS];
! 	Datum	   *stavalues[STATISTIC_NUM_SLOTS];
  
  	/*
  	 * These fields describe the stavalues[n] element types. They will be
--- 90,126 ----
  	 * column/expression.  Instead use attrtypid, attrtypmod, and attrtype for
  	 * information about the datatype being fed to the typanalyze function.
  	 */
! 	Form_pg_attribute attrs[STATISTIC_NUM_SLOTS];	/* copy of pg_attribute row for column */
! 	int2vector *attnums;		/* array of attributes this statistics is for */
! 	int4		statarget;	/* effective statistics target */
! 	Oid			attrtypids[STATISTIC_NUM_SLOTS];	/* type of data being analyzed */
! 	int32		attrtypmods[STATISTIC_NUM_SLOTS];		/* typmod of data being analyzed */
! 	Form_pg_type attrtypes[STATISTIC_NUM_SLOTS];			/* copy of pg_type row for attrtypid */
  	MemoryContext anl_context;	/* where to save long-lived data */
  
  	/*
  	 * These fields must be filled in by the typanalyze routine, unless it
  	 * returns FALSE.
  	 */
! 
! 	StatsFuncStruct		statfuncs[STATISTIC_NUM_SLOTS];
  	int			minrows;		/* Minimum # of rows wanted for stats */
! 	void	   *extra_data[STATISTIC_NUM_SLOTS];	/* for extra type-specific data */
  
  	/*
  	 * These fields are to be filled in by the compute_stats routine. (They
  	 * are initialized to zero when the struct is created.)
  	 */
  	bool		stats_valid;
! 	float4		stanullfrac[STATISTIC_NUM_SLOTS];	/* fraction of entries that are NULL */
! 	int4		stawidth[STATISTIC_NUM_SLOTS];		/* average width of column values */
! 	float4		stadistinct[STATISTIC_NUM_SLOTS];	/* # distinct values */
! 	int2		stakind[STATISTIC_NUM_SLOTS][STATISTIC_NUM_SLOTS];
! 	Oid			staop[STATISTIC_NUM_SLOTS][STATISTIC_NUM_SLOTS];
! 	int			numnumbers[STATISTIC_NUM_SLOTS][STATISTIC_NUM_SLOTS];
! 	float4	   *stanumbers[STATISTIC_NUM_SLOTS][STATISTIC_NUM_SLOTS];
! 	int			numvalues[STATISTIC_NUM_SLOTS][STATISTIC_NUM_SLOTS];
! 	Datum	   *stavalues[STATISTIC_NUM_SLOTS][STATISTIC_NUM_SLOTS];
  
  	/*
  	 * These fields describe the stavalues[n] element types. They will be
*************** typedef struct VacAttrStats
*** 111,132 ****
  	 * want to store an array of something other than the analyzed column's
  	 * elements. It should then overwrite these fields.
  	 */
! 	Oid			statypid[STATISTIC_NUM_SLOTS];
! 	int2		statyplen[STATISTIC_NUM_SLOTS];
! 	bool		statypbyval[STATISTIC_NUM_SLOTS];
! 	char		statypalign[STATISTIC_NUM_SLOTS];
  
  	/*
  	 * These fields are private to the main ANALYZE code and should not be
  	 * looked at by type-specific functions.
  	 */
- 	int			tupattnum;		/* attribute number within tuples */
  	HeapTuple  *rows;			/* access info for std fetch function */
  	TupleDesc	tupDesc;
  	Datum	   *exprvals;		/* access info for index fetch function */
  	bool	   *exprnulls;
  	int			rowstride;
! } VacAttrStats;
  
  
  /* GUC parameters */
--- 128,148 ----
  	 * want to store an array of something other than the analyzed column's
  	 * elements. It should then overwrite these fields.
  	 */
! 	Oid			statypid[STATISTIC_NUM_SLOTS][STATISTIC_NUM_SLOTS];
! 	int2		statyplen[STATISTIC_NUM_SLOTS][STATISTIC_NUM_SLOTS];
! 	bool		statypbyval[STATISTIC_NUM_SLOTS][STATISTIC_NUM_SLOTS];
! 	char		statypalign[STATISTIC_NUM_SLOTS][STATISTIC_NUM_SLOTS];
  
  	/*
  	 * These fields are private to the main ANALYZE code and should not be
  	 * looked at by type-specific functions.
  	 */
  	HeapTuple  *rows;			/* access info for std fetch function */
  	TupleDesc	tupDesc;
  	Datum	   *exprvals;		/* access info for index fetch function */
  	bool	   *exprnulls;
  	int			rowstride;
! } VacStats;
  
  
  /* GUC parameters */
diff -dcrpN postgresql.orig/src/include/nodes/nodes.h postgresql/src/include/nodes/nodes.h
*** postgresql.orig/src/include/nodes/nodes.h	2011-08-07 11:29:16.032255410 +0200
--- postgresql/src/include/nodes/nodes.h	2011-09-12 10:01:16.929490327 +0200
*************** typedef enum NodeTag
*** 362,367 ****
--- 362,368 ----
  	T_CreateExtensionStmt,
  	T_AlterExtensionStmt,
  	T_AlterExtensionContentsStmt,
+ 	T_ExtraStatStmt,
  
  	/*
  	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
diff -dcrpN postgresql.orig/src/include/nodes/parsenodes.h postgresql/src/include/nodes/parsenodes.h
*** postgresql.orig/src/include/nodes/parsenodes.h	2011-08-07 11:29:16.033255357 +0200
--- postgresql/src/include/nodes/parsenodes.h	2011-09-12 10:01:16.939489863 +0200
*************** typedef enum DropBehavior
*** 1161,1166 ****
--- 1161,1180 ----
  } DropBehavior;
  
  /* ----------------------
+  *     Create Cross Column Statistics
+  * ----------------------
+  */
+ typedef struct ExtraStatStmt
+ {
+ 	NodeTag		type;
+ 	char		relkind;
+ 	bool		create;
+ 	RangeVar   *relation;
+ 	List	   *columns;
+ 	int		statistics_target;
+ } ExtraStatStmt;
+ 
+ /* ----------------------
   *	Alter Table
   * ----------------------
   */
diff -dcrpN postgresql.orig/src/include/parser/parse_utilcmd.h postgresql/src/include/parser/parse_utilcmd.h
*** postgresql.orig/src/include/parser/parse_utilcmd.h	2011-01-04 15:13:16.163549374 +0100
--- postgresql/src/include/parser/parse_utilcmd.h	2011-09-12 10:01:16.953489208 +0200
*************** extern void transformRuleStmt(RuleStmt *
*** 25,28 ****
--- 25,31 ----
  				  List **actions, Node **whereClause);
  extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt);
  
+ extern ExtraStatStmt *transformExtraStatistics(ExtraStatStmt *stmt,
+ 						const char *queryString);
+ 
  #endif   /* PARSE_UTILCMD_H */
diff -dcrpN postgresql.orig/src/include/utils/builtins.h postgresql/src/include/utils/builtins.h
*** postgresql.orig/src/include/utils/builtins.h	2011-06-24 11:38:23.342905815 +0200
--- postgresql/src/include/utils/builtins.h	2011-09-12 10:01:16.963488738 +0200
*************** extern Datum int2vectorout(PG_FUNCTION_A
*** 174,179 ****
--- 174,184 ----
  extern Datum int2vectorrecv(PG_FUNCTION_ARGS);
  extern Datum int2vectorsend(PG_FUNCTION_ARGS);
  extern Datum int2vectoreq(PG_FUNCTION_ARGS);
+ extern Datum int2vectorne(PG_FUNCTION_ARGS);
+ extern Datum int2vectorlt(PG_FUNCTION_ARGS);
+ extern Datum int2vectorle(PG_FUNCTION_ARGS);
+ extern Datum int2vectorgt(PG_FUNCTION_ARGS);
+ extern Datum int2vectorge(PG_FUNCTION_ARGS);
  extern Datum int4in(PG_FUNCTION_ARGS);
  extern Datum int4out(PG_FUNCTION_ARGS);
  extern Datum int4recv(PG_FUNCTION_ARGS);
*************** extern void pg_lltoa(int64 ll, char *a);
*** 283,288 ****
--- 288,294 ----
   */
  extern Datum btboolcmp(PG_FUNCTION_ARGS);
  extern Datum btint2cmp(PG_FUNCTION_ARGS);
+ extern Datum btint2vectorcmp(PG_FUNCTION_ARGS);
  extern Datum btint4cmp(PG_FUNCTION_ARGS);
  extern Datum btint8cmp(PG_FUNCTION_ARGS);
  extern Datum btfloat4cmp(PG_FUNCTION_ARGS);
diff -dcrpN postgresql.orig/src/include/utils/selfuncs.h postgresql/src/include/utils/selfuncs.h
*** postgresql.orig/src/include/utils/selfuncs.h	2011-09-05 15:08:06.539565424 +0200
--- postgresql/src/include/utils/selfuncs.h	2011-09-12 10:01:16.988487572 +0200
*************** typedef bool (*get_index_stats_hook_type
*** 110,115 ****
--- 110,119 ----
  												  VariableStatData *vardata);
  extern PGDLLIMPORT get_index_stats_hook_type get_index_stats_hook;
  
+ extern void validate_statistics(VariableStatData *vardata,
+ 						 Oid relid,
+ 						 AttrNumber *attnums, int n_attnums,
+ 						 bool inherited);
  extern void examine_variable(PlannerInfo *root, Node *node, int varRelid,
  				 VariableStatData *vardata);
  extern bool get_restriction_variable(PlannerInfo *root, List *args,
diff -dcrpN postgresql.orig/src/test/regress/expected/rules.out postgresql/src/test/regress/expected/rules.out
*** postgresql.orig/src/test/regress/expected/rules.out	2011-07-24 18:16:45.313676620 +0200
--- postgresql/src/test/regress/expected/rules.out	2011-09-12 10:01:16.999487056 +0200
*************** SELECT viewname, definition FROM pg_view
*** 1317,1323 ****
   pg_statio_user_indexes          | SELECT pg_statio_all_indexes.relid, pg_statio_all_indexes.indexrelid, pg_statio_all_indexes.schemaname, pg_statio_all_indexes.relname, pg_statio_all_indexes.indexrelname, pg_statio_all_indexes.idx_blks_read, pg_statio_all_indexes.idx_blks_hit FROM pg_statio_all_indexes WHERE ((pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_indexes.schemaname !~ '^pg_toast'::text));
   pg_statio_user_sequences        | SELECT pg_statio_all_sequences.relid, pg_statio_all_sequences.schemaname, pg_statio_all_sequences.relname, pg_statio_all_sequences.blks_read, pg_statio_all_sequences.blks_hit FROM pg_statio_all_sequences WHERE ((pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_sequences.schemaname !~ '^pg_toast'::text));
   pg_statio_user_tables           | SELECT pg_statio_all_tables.relid, pg_statio_all_tables.schemaname, pg_statio_all_tables.relname, pg_statio_all_tables.heap_blks_read, pg_statio_all_tables.heap_blks_hit, pg_statio_all_tables.idx_blks_read, pg_statio_all_tables.idx_blks_hit, pg_statio_all_tables.toast_blks_read, pg_statio_all_tables.toast_blks_hit, pg_statio_all_tables.tidx_blks_read, pg_statio_all_tables.tidx_blks_hit FROM pg_statio_all_tables WHERE ((pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_tables.schemaname !~ '^pg_toast'::text));
!  pg_stats                        | SELECT n.nspname AS schemaname, c.relname AS tablename, a.attname, s.stainherit AS inherited, s.stanullfrac AS null_frac, s.stawidth AS avg_width, s.stadistinct AS n_distinct, CASE WHEN (s.stakind1 = ANY (ARRAY[1, 4])) THEN s.stavalues1 WHEN (s.stakind2 = ANY (ARRAY[1, 4])) THEN s.stavalues2 WHEN (s.stakind3 = ANY (ARRAY[1, 4])) THEN s.stavalues3 WHEN (s.stakind4 = ANY (ARRAY[1, 4])) THEN s.stavalues4 ELSE NULL::anyarray END AS most_common_vals, CASE WHEN (s.stakind1 = ANY (ARRAY[1, 4])) THEN s.stanumbers1 WHEN (s.stakind2 = ANY (ARRAY[1, 4])) THEN s.stanumbers2 WHEN (s.stakind3 = ANY (ARRAY[1, 4])) THEN s.stanumbers3 WHEN (s.stakind4 = ANY (ARRAY[1, 4])) THEN s.stanumbers4 ELSE NULL::real[] END AS most_common_freqs, CASE WHEN (s.stakind1 = 2) THEN s.stavalues1 WHEN (s.stakind2 = 2) THEN s.stavalues2 WHEN (s.stakind3 = 2) THEN s.stavalues3 WHEN (s.stakind4 = 2) THEN s.stavalues4 ELSE NULL::anyarray END AS histogram_bounds, CASE WHEN (s.stakind1 = 3) THEN s.stanumbers1[1] WHEN (s.stakind2 = 3) THEN s.stanumbers2[1] WHEN (s.stakind3 = 3) THEN s.stanumbers3[1] WHEN (s.stakind4 = 3) THEN s.stanumbers4[1] ELSE NULL::real END AS correlation FROM (((pg_statistic s JOIN pg_class c ON ((c.oid = s.starelid))) JOIN pg_attribute a ON (((c.oid = a.attrelid) AND (a.attnum = s.staattnum)))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE ((NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text));
   pg_tables                       | SELECT n.nspname AS schemaname, c.relname AS tablename, pg_get_userbyid(c.relowner) AS tableowner, t.spcname AS tablespace, c.relhasindex AS hasindexes, c.relhasrules AS hasrules, c.relhastriggers AS hastriggers FROM ((pg_class c LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace))) WHERE (c.relkind = 'r'::"char");
   pg_timezone_abbrevs             | SELECT pg_timezone_abbrevs.abbrev, pg_timezone_abbrevs.utc_offset, pg_timezone_abbrevs.is_dst FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst);
   pg_timezone_names               | SELECT pg_timezone_names.name, pg_timezone_names.abbrev, pg_timezone_names.utc_offset, pg_timezone_names.is_dst FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
--- 1317,1323 ----
   pg_statio_user_indexes          | SELECT pg_statio_all_indexes.relid, pg_statio_all_indexes.indexrelid, pg_statio_all_indexes.schemaname, pg_statio_all_indexes.relname, pg_statio_all_indexes.indexrelname, pg_statio_all_indexes.idx_blks_read, pg_statio_all_indexes.idx_blks_hit FROM pg_statio_all_indexes WHERE ((pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_indexes.schemaname !~ '^pg_toast'::text));
   pg_statio_user_sequences        | SELECT pg_statio_all_sequences.relid, pg_statio_all_sequences.schemaname, pg_statio_all_sequences.relname, pg_statio_all_sequences.blks_read, pg_statio_all_sequences.blks_hit FROM pg_statio_all_sequences WHERE ((pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_sequences.schemaname !~ '^pg_toast'::text));
   pg_statio_user_tables           | SELECT pg_statio_all_tables.relid, pg_statio_all_tables.schemaname, pg_statio_all_tables.relname, pg_statio_all_tables.heap_blks_read, pg_statio_all_tables.heap_blks_hit, pg_statio_all_tables.idx_blks_read, pg_statio_all_tables.idx_blks_hit, pg_statio_all_tables.toast_blks_read, pg_statio_all_tables.toast_blks_hit, pg_statio_all_tables.tidx_blks_read, pg_statio_all_tables.tidx_blks_hit FROM pg_statio_all_tables WHERE ((pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_statio_all_tables.schemaname !~ '^pg_toast'::text));
!  pg_stats                        | SELECT n.nspname AS schemaname, c.relname AS tablename, a.attname, s.stainherit AS inherited, s.stanullfrac AS null_frac, s.stawidth AS avg_width, s.stadistinct AS n_distinct, CASE WHEN (s.stakind1 = ANY (ARRAY[1, 4])) THEN s.stavalues1 WHEN (s.stakind2 = ANY (ARRAY[1, 4])) THEN s.stavalues2 WHEN (s.stakind3 = ANY (ARRAY[1, 4])) THEN s.stavalues3 WHEN (s.stakind4 = ANY (ARRAY[1, 4])) THEN s.stavalues4 ELSE NULL::anyarray END AS most_common_vals, CASE WHEN (s.stakind1 = ANY (ARRAY[1, 4])) THEN s.stanumbers1 WHEN (s.stakind2 = ANY (ARRAY[1, 4])) THEN s.stanumbers2 WHEN (s.stakind3 = ANY (ARRAY[1, 4])) THEN s.stanumbers3 WHEN (s.stakind4 = ANY (ARRAY[1, 4])) THEN s.stanumbers4 ELSE NULL::real[] END AS most_common_freqs, CASE WHEN (s.stakind1 = 2) THEN s.stavalues1 WHEN (s.stakind2 = 2) THEN s.stavalues2 WHEN (s.stakind3 = 2) THEN s.stavalues3 WHEN (s.stakind4 = 2) THEN s.stavalues4 ELSE NULL::anyarray END AS histogram_bounds, CASE WHEN (s.stakind1 = 3) THEN s.stanumbers1[1] WHEN (s.stakind2 = 3) THEN s.stanumbers2[1] WHEN (s.stakind3 = 3) THEN s.stanumbers3[1] WHEN (s.stakind4 = 3) THEN s.stanumbers4[1] ELSE NULL::real END AS correlation FROM (((pg_statistic s JOIN pg_class c ON ((c.oid = s.starelid))) JOIN pg_attribute a ON ((((c.oid = a.attrelid) AND (array_length(s.staattnums, 1) = 1)) AND (a.attnum = s.staattnums[0])))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE ((NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text));
   pg_tables                       | SELECT n.nspname AS schemaname, c.relname AS tablename, pg_get_userbyid(c.relowner) AS tableowner, t.spcname AS tablespace, c.relhasindex AS hasindexes, c.relhasrules AS hasrules, c.relhastriggers AS hastriggers FROM ((pg_class c LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace))) WHERE (c.relkind = 'r'::"char");
   pg_timezone_abbrevs             | SELECT pg_timezone_abbrevs.abbrev, pg_timezone_abbrevs.utc_offset, pg_timezone_abbrevs.is_dst FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst);
   pg_timezone_names               | SELECT pg_timezone_names.name, pg_timezone_names.abbrev, pg_timezone_names.utc_offset, pg_timezone_names.is_dst FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
diff -dcrpN postgresql.orig/src/test/regress/expected/type_sanity.out postgresql/src/test/regress/expected/type_sanity.out
*** postgresql.orig/src/test/regress/expected/type_sanity.out	2011-01-04 15:13:16.217546597 +0100
--- postgresql/src/test/regress/expected/type_sanity.out	2011-09-12 10:01:17.017486217 +0200
*************** WHERE p1.typarray = p2.oid AND NOT (p1.t
*** 267,274 ****
  SELECT p1.oid, p1.typname, p2.oid, p2.proname
  FROM pg_type AS p1, pg_proc AS p2
  WHERE p1.typanalyze = p2.oid AND p1.typtype in ('b', 'p') AND NOT
!     (p2.pronargs = 1 AND
       p2.proargtypes[0] = 'internal'::regtype AND
       p2.prorettype = 'bool'::regtype AND NOT p2.proretset);
   oid | typname | oid | proname 
  -----+---------+-----+---------
--- 267,275 ----
  SELECT p1.oid, p1.typname, p2.oid, p2.proname
  FROM pg_type AS p1, pg_proc AS p2
  WHERE p1.typanalyze = p2.oid AND p1.typtype in ('b', 'p') AND NOT
!     (p2.pronargs = 2 AND
       p2.proargtypes[0] = 'internal'::regtype AND
+      p2.proargtypes[1] = 'int4'::regtype AND
       p2.prorettype = 'bool'::regtype AND NOT p2.proretset);
   oid | typname | oid | proname 
  -----+---------+-----+---------
diff -dcrpN postgresql.orig/src/test/regress/sql/type_sanity.sql postgresql/src/test/regress/sql/type_sanity.sql
*** postgresql.orig/src/test/regress/sql/type_sanity.sql	2011-01-04 15:13:16.220546441 +0100
--- postgresql/src/test/regress/sql/type_sanity.sql	2011-09-12 10:01:17.029485654 +0200
*************** WHERE p1.typarray = p2.oid AND NOT (p1.t
*** 207,214 ****
  SELECT p1.oid, p1.typname, p2.oid, p2.proname
  FROM pg_type AS p1, pg_proc AS p2
  WHERE p1.typanalyze = p2.oid AND p1.typtype in ('b', 'p') AND NOT
!     (p2.pronargs = 1 AND
       p2.proargtypes[0] = 'internal'::regtype AND
       p2.prorettype = 'bool'::regtype AND NOT p2.proretset);
  
  -- **************** pg_class ****************
--- 207,215 ----
  SELECT p1.oid, p1.typname, p2.oid, p2.proname
  FROM pg_type AS p1, pg_proc AS p2
  WHERE p1.typanalyze = p2.oid AND p1.typtype in ('b', 'p') AND NOT
!     (p2.pronargs = 2 AND
       p2.proargtypes[0] = 'internal'::regtype AND
+      p2.proargtypes[1] = 'int4'::regtype AND
       p2.prorettype = 'bool'::regtype AND NOT p2.proretset);
  
  -- **************** pg_class ****************