cross-col-stats.v1.patch

application/octet-stream

Filename: cross-col-stats.v1.patch
Type: application/octet-stream
Part: 0
Message: WIP: cross column correlation, 2nd shot

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: unified
Series: patch v1
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 825 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/optimizer/path/clausesel.c 736 2
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/array_typanalyze.c 35 34
src/backend/utils/adt/int.c 41 9
src/backend/utils/adt/selfuncs.c 108 29
src/backend/utils/cache/lsyscache.c 12 4
src/backend/utils/cache/relcache.c 1 0
src/backend/utils/cache/syscache.c 2 2
src/bin/initdb/initdb.c 24 18
src/bin/psql/describe.c 2 1
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 13 2
src/include/catalog/pg_opfamily.h 1 0
src/include/catalog/pg_proc.h 12 3
src/include/catalog/pg_statistic.h 52 29
src/include/commands/defrem.h 3 0
src/include/commands/vacuum.h 42 29
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 --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 1f40b7c..5fe1710 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -339,8 +339,6 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			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)
@@ -473,7 +471,6 @@ TupleDescInitEntry(TupleDesc desc,
 	else
 		MemSet(NameStr(att->attname), 0, NAMEDATALEN);
 
-	att->attstattarget = -1;
 	att->attcacheoff = -1;
 	att->atttypmod = typmod;
 
diff --git a/src/backend/access/nbtree/nbtcompare.c b/src/backend/access/nbtree/nbtcompare.c
index fedde93..c5a9474 100644
--- a/src/backend/access/nbtree/nbtcompare.c
+++ b/src/backend/access/nbtree/nbtcompare.c
@@ -308,6 +308,30 @@ btoidvectorcmp(PG_FUNCTION_ARGS)
 }
 
 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 --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index e3ae92d..bde0285 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -737,7 +737,6 @@ DefineAttr(char *name, char *type, int attnum)
 			attrtypes[attnum]->attndims = 0;
 	}
 
-	attrtypes[attnum]->attstattarget = -1;
 	attrtypes[attnum]->attcacheoff = -1;
 	attrtypes[attnum]->atttypmod = -1;
 	attrtypes[attnum]->attislocal = true;
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index a8653cd..e3b17e8 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -126,37 +126,37 @@ static List *insert_ordered_unique_oid(List *list, Oid datum);
  */
 
 static FormData_pg_attribute a1 = {
-	0, {"ctid"}, TIDOID, 0, sizeof(ItemPointerData),
+	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, 0, sizeof(Oid),
+	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, 0, sizeof(TransactionId),
+	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, 0, sizeof(CommandId),
+	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, 0, sizeof(TransactionId),
+	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, 0, sizeof(CommandId),
+	0, {"cmax"}, CIDOID, sizeof(CommandId),
 	MaxCommandIdAttributeNumber, 0, -1, -1,
 	true, 'p', 'i', true, false, false, true, 0
 };
@@ -168,7 +168,7 @@ static FormData_pg_attribute a6 = {
  * used in SQL.
  */
 static FormData_pg_attribute a7 = {
-	0, {"tableoid"}, OIDOID, 0, sizeof(Oid),
+	0, {"tableoid"}, OIDOID, sizeof(Oid),
 	TableOidAttributeNumber, 0, -1, -1,
 	true, 'p', 'i', true, false, false, true, 0
 };
@@ -595,7 +595,6 @@ InsertPgAttributeTuple(Relation pg_attribute_rel,
 	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);
@@ -667,12 +666,13 @@ AddNewAttributeTuples(Oid new_rel_oid,
 		attr = tupdesc->attrs[i];
 		/* Fill in the correct relation OID */
 		attr->attrelid = new_rel_oid;
-		/* Make sure these are OK, too */
-		attr->attstattarget = -1;
+		/* 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;
@@ -1490,9 +1490,6 @@ RemoveAttributeById(Oid relid, AttrNumber attnum)
 		/* 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
 		 */
@@ -1514,8 +1511,9 @@ RemoveAttributeById(Oid relid, AttrNumber attnum)
 
 	heap_close(attr_rel, RowExclusiveLock);
 
+	/* Only drop pg_statistic entries for non system columns. */
 	if (attnum > 0)
-		RemoveStatistics(relid, attnum);
+		RemoveStatistics(relid, &attnum, 1);
 
 	relation_close(rel, NoLock);
 }
@@ -1740,7 +1738,7 @@ heap_drop_with_catalog(Oid relid)
 	/*
 	 * delete statistics
 	 */
-	RemoveStatistics(relid, 0);
+	RemoveStatistics(relid, NULL, 0);
 
 	/*
 	 * delete attribute tuples
@@ -2542,19 +2540,214 @@ cookConstraint(ParseState *pstate,
 
 
 /*
- * RemoveStatistics --- remove entries in pg_statistic for a rel or column
+ * 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 attnum is zero, remove all entries for rel; else remove only the one(s)
- * for that column.
+ * 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 attnum)
+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);
 
@@ -2563,18 +2756,19 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
 				BTEqualStrategyNumber, F_OIDEQ,
 				ObjectIdGetDatum(relid));
 
-	if (attnum == 0)
+	if (attnums == NULL)
 		nkeys = 1;
 	else
 	{
+		attnumvector = buildint2vector(attnums, n_attnums);
 		ScanKeyInit(&key[1],
-					Anum_pg_statistic_staattnum,
-					BTEqualStrategyNumber, F_INT2EQ,
-					Int16GetDatum(attnum));
+					Anum_pg_statistic_staattnums,
+					BTEqualStrategyNumber, F_INT2VECTOREQ,
+					PointerGetDatum(attnumvector));
 		nkeys = 2;
 	}
 
-	scan = systable_beginscan(pgstatistic, StatisticRelidAttnumInhIndexId, true,
+	scan = systable_beginscan(pgstatistic, StatisticRelidAttnumsInhIndexId, true,
 							  SnapshotNow, nkeys, key);
 
 	/* we must loop even when attnum != 0, in case of inherited stats */
@@ -2583,6 +2777,9 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
 
 	systable_endscan(scan);
 
+	if (attnumvector)
+		pfree(attnumvector);
+
 	heap_close(pgstatistic, RowExclusiveLock);
 }
 
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index bfbe642..74cfa58 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -342,7 +342,6 @@ ConstructTupleDescriptor(Relation heapRelation,
 			 */
 			to->attnum = i + 1;
 
-			to->attstattarget = -1;
 			to->attcacheoff = -1;
 			to->attnotnull = false;
 			to->atthasdef = false;
@@ -380,7 +379,6 @@ ConstructTupleDescriptor(Relation heapRelation,
 			to->attbyval = typeTup->typbyval;
 			to->attstorage = typeTup->typstorage;
 			to->attalign = typeTup->typalign;
-			to->attstattarget = -1;
 			to->attcacheoff = -1;
 			to->atttypmod = -1;
 			to->attislocal = true;
@@ -1353,7 +1351,7 @@ index_drop(Oid indexId)
 	 * them.
 	 */
 	if (hasexprs)
-		RemoveStatistics(indexId, 0);
+		RemoveStatistics(indexId, NULL, 0);
 
 	/*
 	 * fix ATTRIBUTE relation
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ab594eb..62c4ce3 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -166,7 +166,7 @@ CREATE VIEW pg_stats AS
             WHEN stakind5 = 5 THEN stanumbers5
         END AS elem_count_histogram
     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)
+         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 --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 9cd6e67..1ac9b5a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -21,12 +21,15 @@
 #include "access/tuptoaster.h"
 #include "access/visibilitymap.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"
@@ -42,7 +45,9 @@
 #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"
@@ -69,8 +74,10 @@ typedef struct AnlIndexData
 {
 	IndexInfo  *indexInfo;		/* BuildIndexInfo result */
 	double		tupleFract;		/* fraction of rows for partial index */
-	VacAttrStats **vacattrstats;	/* index attrs to analyze */
-	int			attr_cnt;
+	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;
 
 
@@ -94,8 +101,8 @@ static void compute_index_stats(Relation onerel, double totalrows,
 					AnlIndexData *indexdata, int nindexes,
 					HeapTuple *rows, int numrows,
 					MemoryContext col_context);
-static VacAttrStats *examine_attribute(Relation onerel, int attnum,
-				  Node *index_expr);
+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);
@@ -106,10 +113,16 @@ static int acquire_inherited_sample_rows(Relation onerel,
 							  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);
+				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 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
@@ -252,20 +265,78 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
 	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			attr_cnt,
+	int			stats_cnt,
+				extrastats_cnt,
 				tcnt,
 				i,
 				ind;
 	Relation   *Irel;
 	int			nindexes;
 	bool		hasindex;
-	VacAttrStats **vacattrstats;
+	VacStats **vacstats;
+	int2vector   **extrastats_attnums;
 	AnlIndexData *indexdata;
 	int			targrows,
 				numrows;
@@ -328,38 +399,75 @@ do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh)
 	{
 		ListCell   *le;
 
-		vacattrstats = (VacAttrStats **) palloc(list_length(vacstmt->va_cols) *
-												sizeof(VacAttrStats *));
+		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;
 
-			i = attnameAttNum(onerel, col, false);
-			if (i == InvalidAttrNumber)
+			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))));
-			vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
-			if (vacattrstats[tcnt] != NULL)
+			attnums = buildint2vector(&attnum, 1);
+			vacstats[tcnt] = examine_attribute(onerel, attnums, inh, NULL);
+			if (vacstats[tcnt] != NULL)
 				tcnt++;
+			else
+				pfree(attnums);
 		}
-		attr_cnt = tcnt;
+		stats_cnt = tcnt;
+		extrastats_cnt = 0;
 	}
 	else
 	{
-		attr_cnt = onerel->rd_att->natts;
-		vacattrstats = (VacAttrStats **)
-			palloc(attr_cnt * sizeof(VacAttrStats *));
+		/*
+		 * 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 <= attr_cnt; i++)
+		for (i = 1; i <= stats_cnt; i++)
 		{
-			vacattrstats[tcnt] = examine_attribute(onerel, i, NULL);
-			if (vacattrstats[tcnt] != NULL)
+			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);
 		}
-		attr_cnt = tcnt;
+
+		/* 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;
 	}
 
 	/*
@@ -388,13 +496,20 @@ do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh)
 
 			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->vacattrstats = (VacAttrStats **)
-					palloc(indexInfo->ii_NumIndexAttrs * sizeof(VacAttrStats *));
-				tcnt = 0;
+				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];
@@ -403,19 +518,74 @@ do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh)
 					{
 						/* 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);
-						thisdata->vacattrstats[tcnt] =
-							examine_attribute(Irel[ind], i + 1, indexkey);
-						if (thisdata->vacattrstats[tcnt] != NULL)
+
+						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);
 					}
 				}
-				thisdata->attr_cnt = tcnt;
+
 			}
+
+			/*
+			 * 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;
 		}
 	}
 
@@ -426,19 +596,19 @@ do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh)
 	 * the target in the corner case where there are no analyzable columns.)
 	 */
 	targrows = 100;
-	for (i = 0; i < attr_cnt; i++)
+	for (i = 0; i < stats_cnt; i++)
 	{
-		if (targrows < vacattrstats[i]->minrows)
-			targrows = vacattrstats[i]->minrows;
+		if (targrows < vacstats[i]->minrows)
+			targrows = vacstats[i]->minrows;
 	}
 	for (ind = 0; ind < nindexes; ind++)
 	{
 		AnlIndexData *thisdata = &indexdata[ind];
 
-		for (i = 0; i < thisdata->attr_cnt; i++)
+		for (i = 0; i < thisdata->stats_cnt; i++)
 		{
-			if (targrows < thisdata->vacattrstats[i]->minrows)
-				targrows = thisdata->vacattrstats[i]->minrows;
+			if (targrows < thisdata->vacstats[i]->minrows)
+				targrows = thisdata->vacstats[i]->minrows;
 		}
 	}
 
@@ -456,7 +626,7 @@ do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh)
 	/*
 	 * 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
+	 * responsible to make sure that whatever they store into the VacStats
 	 * structure is allocated in anl_context.
 	 */
 	if (numrows > 0)
@@ -471,33 +641,40 @@ do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh)
 											ALLOCSET_DEFAULT_MAXSIZE);
 		old_context = MemoryContextSwitchTo(col_context);
 
-		for (i = 0; i < attr_cnt; i++)
+		for (i = 0; i < stats_cnt; i++)
 		{
-			VacAttrStats *stats = vacattrstats[i];
+			VacStats *stats = vacstats[i];
 			AttributeOpts *aopt;
+			int		j;
 
 			stats->rows = rows;
 			stats->tupDesc = onerel->rd_att;
-			(*stats->compute_stats) (stats,
+
+			for (j = 0; j < stats->attnums->dim1; 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.
+				 */
+				aopt = get_attribute_options(onerel->rd_id, stats->attnums->values[j]);
+				if (aopt != NULL)
+				{
+					float8		n_distinct =
+					inh ? aopt->n_distinct_inherited : aopt->n_distinct;
 
-			/*
-			 * If the appropriate flavor of the n_distinct option is
-			 * specified, override with the corresponding value.
-			 */
-			aopt = get_attribute_options(onerel->rd_id, stats->attr->attnum);
-			if (aopt != NULL)
-			{
-				float8		n_distinct;
-
-				n_distinct = inh ? aopt->n_distinct_inherited : aopt->n_distinct;
-				if (n_distinct != 0.0)
-					stats->stadistinct = 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)
@@ -515,14 +692,17 @@ do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh)
 		 * pg_statistic for columns we didn't process, we leave them alone.)
 		 */
 		update_attstats(RelationGetRelid(onerel), inh,
-						attr_cnt, vacattrstats);
+						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->attr_cnt, thisdata->vacattrstats);
+							thisdata->stats_cnt, thisdata->vacstats);
 		}
 	}
 
@@ -632,6 +812,8 @@ compute_index_stats(Relation onerel, double totalrows,
 				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;
 
@@ -646,7 +828,7 @@ compute_index_stats(Relation onerel, double totalrows,
 	{
 		AnlIndexData *thisdata = &indexdata[ind];
 		IndexInfo  *indexInfo = thisdata->indexInfo;
-		int			attr_cnt = thisdata->attr_cnt;
+		int			stats_cnt = thisdata->stats_cnt;
 		TupleTableSlot *slot;
 		EState	   *estate;
 		ExprContext *econtext;
@@ -657,11 +839,30 @@ compute_index_stats(Relation onerel, double totalrows,
 					tcnt,
 					rowno;
 		double		totalindexrows;
+		ListCell   *indexpr_item = list_head(indexInfo->ii_Expressions);
 
 		/* Ignore index if no columns to analyze and not partial */
-		if (attr_cnt == 0 && indexInfo->ii_Predicate == NIL)
+		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
@@ -681,8 +882,8 @@ compute_index_stats(Relation onerel, double totalrows,
 							estate);
 
 		/* Compute and save index expression values */
-		exprvals = (Datum *) palloc(numrows * attr_cnt * sizeof(Datum));
-		exprnulls = (bool *) palloc(numrows * attr_cnt * sizeof(bool));
+		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++)
@@ -706,7 +907,7 @@ compute_index_stats(Relation onerel, double totalrows,
 			}
 			numindexrows++;
 
-			if (attr_cnt > 0)
+			if (stats_cnt > 0)
 			{
 				/*
 				 * Evaluate the index row to compute expression values. We
@@ -719,24 +920,18 @@ compute_index_stats(Relation onerel, double totalrows,
 							   isnull);
 
 				/*
-				 * Save just the columns we care about.  We copy the values
-				 * into ind_context from the estate's per-tuple context.
+				 * We copy the values * into ind_context from the estate's per-tuple context.
 				 */
-				for (i = 0; i < attr_cnt; i++)
+				for (i = 0; i < thisdata->attr_cnt; i++)
 				{
-					VacAttrStats *stats = thisdata->vacattrstats[i];
-					int			attnum = stats->attr->attnum;
-
-					if (isnull[attnum - 1])
+					if (isnull[i])
 					{
 						exprvals[tcnt] = (Datum) 0;
 						exprnulls[tcnt] = true;
 					}
 					else
 					{
-						exprvals[tcnt] = datumCopy(values[attnum - 1],
-												   stats->attrtype->typbyval,
-												   stats->attrtype->typlen);
+						exprvals[tcnt] = datumCopy(values[i], typbyval[i], typlen[i]);
 						exprnulls[tcnt] = false;
 					}
 					tcnt++;
@@ -757,28 +952,37 @@ compute_index_stats(Relation onerel, double totalrows,
 		if (numindexrows > 0)
 		{
 			MemoryContextSwitchTo(col_context);
-			for (i = 0; i < attr_cnt; i++)
+			for (i = 0; i < stats_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);
+				VacStats *stats = thisdata->vacstats[i];
+				AttributeOpts *aopt;
+				int		j;
 
-				/*
-				 * 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;
+				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);
 			}
@@ -797,38 +1001,77 @@ compute_index_stats(Relation onerel, double totalrows,
 }
 
 /*
- * examine_attribute -- pre-analysis of a single column
+ * 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 VacAttrStats struct for it.  If not, return NULL.
+ * 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 VacAttrStats *
-examine_attribute(Relation onerel, int attnum, Node *index_expr)
+static VacStats *
+examine_attribute(Relation onerel, int2vector *attnums, bool inh, Node **index_exprs)
 {
-	Form_pg_attribute attr = onerel->rd_att->attrs[attnum - 1];
 	HeapTuple	typtuple;
-	VacAttrStats *stats;
-	int			i;
+	int4		statarget;
+	VacStats *stats;
+	int			i, j;
 	bool		ok;
 
-	/* Never analyze dropped columns */
-	if (attr->attisdropped)
-		return NULL;
+	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 */
-	if (attr->attstattarget == 0)
+	statarget = statistics_target(onerel->rd_id, attnums, inh);
+	if (statarget == 0)
 		return NULL;
 
 	/*
-	 * Create the VacAttrStats struct.	Note that we only have a copy of the
+	 * Create the VacStats 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);
+	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
@@ -839,54 +1082,86 @@ examine_attribute(Relation onerel, int attnum, Node *index_expr)
 	 * 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
+
+	for (i = 0; i < attnums->dim1; i++)
 	{
-		stats->attrtypid = attr->atttypid;
-		stats->attrtypmod = attr->atttypmod;
+		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);
 	}
 
-	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++)
+	for (i = 0; i < attnums->dim1; i++)
 	{
-		stats->statypid[i] = stats->attrtypid;
-		stats->statyplen[i] = stats->attrtype->typlen;
-		stats->statypbyval[i] = stats->attrtype->typbyval;
-		stats->statypalign[i] = stats->attrtype->typalign;
+		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;
+		}
 	}
 
 	/*
-	 * Call the type-specific typanalyze function.	If none is specified, use
-	 * std_typanalyze().
+	 * 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 (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)
+	if (stats->attnums->dim1 > 1)
 	{
-		heap_freetuple(typtuple);
-		pfree(stats->attr);
-		pfree(stats);
-		return NULL;
+		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;
@@ -1560,7 +1835,7 @@ acquire_inherited_sample_rows(Relation onerel, HeapTuple *rows, int targrows,
  *		by taking a self-exclusive lock on the relation in analyze_rel().
  */
 static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacStats **vacstats)
 {
 	Relation	sd;
 	int			attno;
@@ -1572,7 +1847,7 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 
 	for (attno = 0; attno < natts; attno++)
 	{
-		VacAttrStats *stats = vacattrstats[attno];
+		VacStats *stats = vacstats[attno];
 		HeapTuple	stup,
 					oldtup;
 		int			i,
@@ -1595,26 +1870,35 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			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_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);
+		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[k]);		/* stakindN */
+			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[k]);	/* staopN */
+			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[k];
+			int			nnum = stats->numnumbers[0][k];
 
 			if (nnum > 0)
 			{
@@ -1622,7 +1906,7 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 				ArrayType  *arry;
 
 				for (n = 0; n < nnum; n++)
-					numdatums[n] = Float4GetDatum(stats->stanumbers[k][n]);
+					numdatums[n] = Float4GetDatum(stats->stanumbers[0][k][n]);
 				/* XXX knows more than it should about type float4: */
 				arry = construct_array(numdatums, nnum,
 									   FLOAT4OID,
@@ -1638,16 +1922,29 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 		i = Anum_pg_statistic_stavalues1 - 1;
 		for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
 		{
-			if (stats->numvalues[k] > 0)
+			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[k],
-									   stats->numvalues[k],
-									   stats->statypid[k],
-									   stats->statyplen[k],
-									   stats->statypbyval[k],
-									   stats->statypalign[k]);
+				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
@@ -1660,11 +1957,14 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 		/* Is there already a pg_statistic tuple for this attribute? */
 		oldtup = SearchSysCache3(STATRELATTINH,
 								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
+								 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),
@@ -1697,13 +1997,12 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
  * and the actual storage of the sample data.
  */
 static Datum
-std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull)
+std_fetch_func(VacStatsP stats, int rownum, AttrNumber tupattnum, bool *isNull)
 {
-	int			attnum = stats->tupattnum;
 	HeapTuple	tuple = stats->rows[rownum];
 	TupleDesc	tupDesc = stats->tupDesc;
 
-	return heap_getattr(tuple, attnum, tupDesc, isNull);
+	return heap_getattr(tuple, tupattnum, tupDesc, isNull);
 }
 
 /*
@@ -1713,12 +2012,11 @@ std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull)
  * just in Datum arrays.
  */
 static Datum
-ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull)
+ind_fetch_func(VacStatsP stats, int rownum, AttrNumber tupattnum, bool *isNull)
 {
 	int			i;
 
-	/* exprvals and exprnulls are already offset for proper column */
-	i = rownum * stats->rowstride;
+	i = rownum * stats->rowstride + tupattnum - 1;
 	*isNull = stats->exprnulls[i];
 	return stats->exprvals[i];
 }
@@ -1776,12 +2074,19 @@ typedef struct
 	int		   *tupnoLink;
 } CompareScalarsContext;
 
+typedef struct
+{
+	SortSupport		ssup;
+	FmgrInfo		cmpFn; /* FIXME: this should be somewhere else? */
+} CompareDatumsContext;
 
-static void compute_minimal_stats(VacAttrStatsP stats,
+static void compute_minimal_stats(VacStatsP stats,
+					  int index,
 					  AnalyzeAttrFetchFunc fetchfunc,
 					  int samplerows,
 					  double totalrows);
-static void compute_scalar_stats(VacAttrStatsP stats,
+static void compute_scalar_stats(VacStatsP stats,
+					 int index,
 					 AnalyzeAttrFetchFunc fetchfunc,
 					 int samplerows,
 					 double totalrows);
@@ -1793,20 +2098,19 @@ static int	compare_mcvs(const void *a, const void *b);
  * std_typanalyze -- the default type-specific typanalyze function
  */
 bool
-std_typanalyze(VacAttrStats *stats)
+std_typanalyze(VacStats *stats, int index)
 {
-	Form_pg_attribute attr = stats->attr;
 	Oid			ltopr;
 	Oid			eqopr;
 	StdAnalyzeData *mystats;
 
-	/* If the attstattarget column is negative, use the default value */
+	/* 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 (attr->attstattarget < 0)
-		attr->attstattarget = default_statistics_target;
+	if (stats->statarget < 0)
+		stats->statarget = default_statistics_target;
 
 	/* Look for default "<" and "=" operators for column's type */
-	get_sort_group_operators(stats->attrtypid,
+	get_sort_group_operators(stats->attrtypids[index],
 							 false, false, false,
 							 &ltopr, &eqopr, NULL,
 							 NULL);
@@ -1820,7 +2124,7 @@ std_typanalyze(VacAttrStats *stats)
 	mystats->eqopr = eqopr;
 	mystats->eqfunc = get_opcode(eqopr);
 	mystats->ltopr = ltopr;
-	stats->extra_data = mystats;
+	stats->extra_data[index] = mystats;
 
 	/*
 	 * Determine which standard statistics algorithm to use
@@ -1828,7 +2132,8 @@ std_typanalyze(VacAttrStats *stats)
 	if (OidIsValid(ltopr))
 	{
 		/* Seems to be a scalar datatype */
-		stats->compute_stats = compute_scalar_stats;
+		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?"
@@ -1848,14 +2153,15 @@ std_typanalyze(VacAttrStats *stats)
 		 * know it at this point.
 		 *--------------------
 		 */
-		stats->minrows = 300 * attr->attstattarget;
+		stats->minrows = 300 * stats->statarget;
 	}
 	else
 	{
 		/* Can't do much but the minimal stuff */
-		stats->compute_stats = compute_minimal_stats;
+		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 * attr->attstattarget;
+		stats->minrows = 300 * stats->statarget;
 	}
 
 	return true;
@@ -1877,7 +2183,8 @@ std_typanalyze(VacAttrStats *stats)
  *	depend mainly on the length of the list we are willing to keep.
  */
 static void
-compute_minimal_stats(VacAttrStatsP stats,
+compute_minimal_stats(VacStatsP stats,
+					  int index,
 					  AnalyzeAttrFetchFunc fetchfunc,
 					  int samplerows,
 					  double totalrows)
@@ -1887,10 +2194,10 @@ compute_minimal_stats(VacAttrStatsP stats,
 	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);
+	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
 	{
@@ -1900,8 +2207,8 @@ compute_minimal_stats(VacAttrStatsP stats,
 	TrackItem  *track;
 	int			track_cnt,
 				track_max;
-	int			num_mcv = stats->attr->attstattarget;
-	StdAnalyzeData *mystats = (StdAnalyzeData *) stats->extra_data;
+	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
@@ -1924,7 +2231,7 @@ compute_minimal_stats(VacAttrStatsP stats,
 
 		vacuum_delay_point();
 
-		value = fetchfunc(stats, i, &isnull);
+		value = fetchfunc(stats, i, stats->attnums->values[index], &isnull);
 
 		/* Check for null/nonnull */
 		if (isnull)
@@ -2021,11 +2328,11 @@ compute_minimal_stats(VacAttrStatsP stats,
 
 		stats->stats_valid = true;
 		/* Do the simple null-frac and width stats */
-		stats->stanullfrac = (double) null_cnt / (double) samplerows;
+		stats->stanullfrac[index] = (double) null_cnt / (double) samplerows;
 		if (is_varwidth)
-			stats->stawidth = total_width / (double) nonnull_cnt;
+			stats->stawidth[index] = total_width / (double) nonnull_cnt;
 		else
-			stats->stawidth = stats->attrtype->typlen;
+			stats->stawidth[index] = stats->attrtypes[index]->typlen;
 
 		/* Count the number of values we found multiple times */
 		summultiple = 0;
@@ -2039,7 +2346,7 @@ compute_minimal_stats(VacAttrStatsP stats,
 		if (nmultiple == 0)
 		{
 			/* If we found no repeated values, assume it's a unique column */
-			stats->stadistinct = -1.0;
+			stats->stadistinct[index] = -1.0;
 		}
 		else if (track_cnt < track_max && toowide_cnt == 0 &&
 				 nmultiple == track_cnt)
@@ -2049,7 +2356,7 @@ compute_minimal_stats(VacAttrStatsP stats,
 			 * value appeared more than once.  Assume the column has just
 			 * these values.
 			 */
-			stats->stadistinct = track_cnt;
+			stats->stadistinct[index] = track_cnt;
 		}
 		else
 		{
@@ -2088,7 +2395,7 @@ compute_minimal_stats(VacAttrStatsP stats,
 				stadistinct = (double) d;
 			if (stadistinct > totalrows)
 				stadistinct = totalrows;
-			stats->stadistinct = floor(stadistinct + 0.5);
+			stats->stadistinct[index] = floor(stadistinct + 0.5);
 		}
 
 		/*
@@ -2097,8 +2404,8 @@ compute_minimal_stats(VacAttrStatsP stats,
 		 * stadistinct should scale with the row count rather than be a fixed
 		 * value.
 		 */
-		if (stats->stadistinct > 0.1 * totalrows)
-			stats->stadistinct = -(stats->stadistinct / totalrows);
+		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
@@ -2118,7 +2425,7 @@ compute_minimal_stats(VacAttrStatsP stats,
 		}
 		else
 		{
-			double		ndistinct = stats->stadistinct;
+			double		ndistinct = stats->stadistinct[index];
 			double		avgcount,
 						mincount;
 
@@ -2156,18 +2463,18 @@ compute_minimal_stats(VacAttrStatsP stats,
 			for (i = 0; i < num_mcv; i++)
 			{
 				mcv_values[i] = datumCopy(track[i].value,
-										  stats->attrtype->typbyval,
-										  stats->attrtype->typlen);
+										  stats->attrtypes[index]->typbyval,
+										  stats->attrtypes[index]->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;
+			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
@@ -2179,12 +2486,12 @@ compute_minimal_stats(VacAttrStatsP stats,
 	{
 		/* We found only nulls; assume the column is entirely null */
 		stats->stats_valid = true;
-		stats->stanullfrac = 1.0;
+		stats->stanullfrac[index] = 1.0;
 		if (is_varwidth)
-			stats->stawidth = 0;	/* "unknown" */
+			stats->stawidth[index] = 0;	/* "unknown" */
 		else
-			stats->stawidth = stats->attrtype->typlen;
-		stats->stadistinct = 0.0;		/* "unknown" */
+			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 */
@@ -2204,7 +2511,8 @@ compute_minimal_stats(VacAttrStatsP stats,
  *	data values into order.
  */
 static void
-compute_scalar_stats(VacAttrStatsP stats,
+compute_scalar_stats(VacStatsP stats,
+					 int index,
 					 AnalyzeAttrFetchFunc fetchfunc,
 					 int samplerows,
 					 double totalrows)
@@ -2214,10 +2522,10 @@ compute_scalar_stats(VacAttrStatsP stats,
 	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);
+	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;
 	SortSupportData ssup;
 	ScalarItem *values;
@@ -2225,9 +2533,9 @@ compute_scalar_stats(VacAttrStatsP stats,
 	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;
+	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));
@@ -2249,7 +2557,7 @@ compute_scalar_stats(VacAttrStatsP stats,
 
 		vacuum_delay_point();
 
-		value = fetchfunc(stats, i, &isnull);
+		value = fetchfunc(stats, i, stats->attnums->values[index], &isnull);
 
 		/* Check for null/nonnull */
 		if (isnull)
@@ -2378,16 +2686,16 @@ compute_scalar_stats(VacAttrStatsP stats,
 
 		stats->stats_valid = true;
 		/* Do the simple null-frac and width stats */
-		stats->stanullfrac = (double) null_cnt / (double) samplerows;
+		stats->stanullfrac[index] = (double) null_cnt / (double) samplerows;
 		if (is_varwidth)
-			stats->stawidth = total_width / (double) nonnull_cnt;
+			stats->stawidth[index] = total_width / (double) nonnull_cnt;
 		else
-			stats->stawidth = stats->attrtype->typlen;
+			stats->stawidth[index] = stats->attrtypes[index]->typlen;
 
 		if (nmultiple == 0)
 		{
 			/* If we found no repeated values, assume it's a unique column */
-			stats->stadistinct = -1.0;
+			stats->stadistinct[index] = -1.0;
 		}
 		else if (toowide_cnt == 0 && nmultiple == ndistinct)
 		{
@@ -2395,7 +2703,7 @@ compute_scalar_stats(VacAttrStatsP stats,
 			 * Every value in the sample appeared more than once.  Assume the
 			 * column has just these values.
 			 */
-			stats->stadistinct = ndistinct;
+			stats->stadistinct[index] = ndistinct;
 		}
 		else
 		{
@@ -2430,7 +2738,7 @@ compute_scalar_stats(VacAttrStatsP stats,
 				stadistinct = (double) d;
 			if (stadistinct > totalrows)
 				stadistinct = totalrows;
-			stats->stadistinct = floor(stadistinct + 0.5);
+			stats->stadistinct[index] = floor(stadistinct + 0.5);
 		}
 
 		/*
@@ -2439,8 +2747,8 @@ compute_scalar_stats(VacAttrStatsP stats,
 		 * stadistinct should scale with the row count rather than be a fixed
 		 * value.
 		 */
-		if (stats->stadistinct > 0.1 * totalrows)
-			stats->stadistinct = -(stats->stadistinct / totalrows);
+		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
@@ -2457,7 +2765,7 @@ compute_scalar_stats(VacAttrStatsP stats,
 		 * but we prefer to treat such values as MCVs if at all possible.)
 		 */
 		if (track_cnt == ndistinct && toowide_cnt == 0 &&
-			stats->stadistinct > 0 &&
+			stats->stadistinct[index] > 0 &&
 			track_cnt <= num_mcv)
 		{
 			/* Track list includes all values seen, and all will fit */
@@ -2465,7 +2773,7 @@ compute_scalar_stats(VacAttrStatsP stats,
 		}
 		else
 		{
-			double		ndistinct = stats->stadistinct;
+			double		ndistinct = stats->stadistinct[index];
 			double		avgcount,
 						mincount,
 						maxmincount;
@@ -2508,18 +2816,18 @@ compute_scalar_stats(VacAttrStatsP stats,
 			for (i = 0; i < num_mcv; i++)
 			{
 				mcv_values[i] = datumCopy(values[track[i].first].value,
-										  stats->attrtype->typbyval,
-										  stats->attrtype->typlen);
+										  stats->attrtypes[index]->typbyval,
+										  stats->attrtypes[index]->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;
+			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
@@ -2615,8 +2923,8 @@ compute_scalar_stats(VacAttrStatsP stats,
 			for (i = 0; i < num_hist; i++)
 			{
 				hist_values[i] = datumCopy(values[pos].value,
-										   stats->attrtype->typbyval,
-										   stats->attrtype->typlen);
+										   stats->attrtypes[index]->typbyval,
+										   stats->attrtypes[index]->typlen);
 				pos += delta;
 				posfrac += deltafrac;
 				if (posfrac >= (num_hist - 1))
@@ -2629,10 +2937,10 @@ compute_scalar_stats(VacAttrStatsP stats,
 
 			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;
+			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
@@ -2672,10 +2980,10 @@ compute_scalar_stats(VacAttrStatsP stats,
 			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;
+			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++;
 		}
 	}
@@ -2683,12 +2991,12 @@ compute_scalar_stats(VacAttrStatsP stats,
 	{
 		/* We found only nulls; assume the column is entirely null */
 		stats->stats_valid = true;
-		stats->stanullfrac = 1.0;
+		stats->stanullfrac[index] = 1.0;
 		if (is_varwidth)
-			stats->stawidth = 0;	/* "unknown" */
+			stats->stawidth[index] = 0;	/* "unknown" */
 		else
-			stats->stawidth = stats->attrtype->typlen;
-		stats->stadistinct = 0.0;		/* "unknown" */
+			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 */
@@ -2732,6 +3040,209 @@ compare_scalars(const void *a, const void *b, void *arg)
 }
 
 /*
+ * 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 *ctx = (CompareDatumsContext *) arg;
+
+	return ApplySortComparator(da, false, db, false, ctx->ssup);
+}
+
+/*
+ * 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 non MCV value, >= 2 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;
+
+	idx_lo = 0;
+	idx_hi = stats->numvalues[index][0] - 1;
+
+	idx_mid = (idx_lo + idx_hi) / 2;
+	while (idx_lo <= idx_hi)
+	{
+		compare = ApplySortComparator(value, false,
+								stats->stavalues[index][0][idx_mid], false, ctx->ssup);
+		if (compare == 0)
+			break;
+		else if (compare < 0)
+			idx_hi = idx_mid - 1;
+		else /* compare > 0 */
+			idx_lo = idx_mid + 1;
+
+		idx_mid = (idx_lo + idx_hi) / 2;
+	}
+
+	if (compare == 0)
+		return idx_mid + 2;
+
+	/* Not found, return "other" special slot */
+	return 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, >= 2 for a valid index and 1 for a Datum
+ * that isn't in stavalues ("other" 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;
+
+	/* FIXME: Assert ctx->cmpFn is correct */
+
+	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 + 2;
+	}
+
+	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;
+			SortSupportData *ssup;
+			/* FIXME: shouldn't be necessary in this case */
+			bool reverse, issupport;
+
+			if (!get_sort_function_for_ordering_op(mystats->ltopr, &cmpFn,
+					&issupport, &reverse)) {
+				elog(ERROR, "operator %u is not a valid ordering operator",
+						mystats->ltopr);
+			}
+			fmgr_info(cmpFn, &(ctx[i].cmpFn));
+
+			ssup = (SortSupportData*) palloc0(sizeof(SortSupportData));
+			ssup->ssup_cxt = CurrentMemoryContext;
+			/* We always use the default collation for statistics */
+			ssup->ssup_collation = DEFAULT_COLLATION_OID;
+			ssup->ssup_nulls_first = false;
+
+			PrepareSortSupportFromOrderingOp(mystats->ltopr, ssup);
+
+			ctx[i].ssup = ssup;
+
+			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[stats->attnums->dim1 - i - 1][0] + 2;
+	}
+
+	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
@@ -2742,3 +3253,74 @@ compare_mcvs(const void *a, const void *b)
 
 	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);
+
+	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 --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cd4490a..4ab4b4c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -35,6 +35,7 @@
 #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"
@@ -4342,7 +4343,6 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	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;
@@ -4504,6 +4504,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	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
@@ -4885,8 +4887,14 @@ ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 {
 	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);
@@ -4912,7 +4920,9 @@ ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 
 	attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
 
-	tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
+	relid = RelationGetRelid(rel);
+
+	tuple = SearchSysCacheAttName(relid, colName);
 
 	if (!HeapTupleIsValid(tuple))
 		ereport(ERROR,
@@ -4927,16 +4937,41 @@ ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
-	attrtuple->attstattarget = newtarget;
+	attnum = attrtuple->attnum;
+	inherited = (attrtuple->attinhcount > 0);
 
-	simple_heap_update(attrelation, &tuple->t_self, tuple);
+	ReleaseSysCache(tuple);
 
-	/* keep system catalog indexes current */
-	CatalogUpdateIndexes(attrelation, tuple);
+	heap_close(attrelation, RowExclusiveLock);
 
-	heap_freetuple(tuple);
+	statsrelation = heap_open(StatisticRelationId, RowExclusiveLock);
 
-	heap_close(attrelation, 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
@@ -7587,9 +7622,9 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 	add_column_collation_dependency(RelationGetRelid(rel), attnum, targetcollid);
 
 	/*
-	 * Drop any pg_statistic entry for the column, since it's now wrong type
+	 * Invalidate any pg_statistic entry for the column, since it's now wrong type
 	 */
-	RemoveStatistics(RelationGetRelid(rel), attnum);
+	InvalidateStatistics(RelationGetRelid(rel), attnum);
 
 	/*
 	 * Update the default, if present, by brute force --- remove and re-add
diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c
index cabe663..38b5f73 100644
--- a/src/backend/executor/nodeHash.c
+++ b/src/backend/executor/nodeHash.c
@@ -31,6 +31,7 @@
 #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"
@@ -1124,6 +1125,7 @@ ExecHashBuildSkewHash(HashJoinTable hashtable, Hash *node, int mcvsToUse)
 	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))
@@ -1135,10 +1137,23 @@ ExecHashBuildSkewHash(HashJoinTable hashtable, Hash *node, int mcvsToUse)
 	/*
 	 * Try to find the MCV statistics for the outer relation's join key.
 	 */
+	attnumvector = buildint2vector(&(node->skewColumn), 1);
 	statsTuple = SearchSysCache3(STATRELATTINH,
 								 ObjectIdGetDatum(node->skewTable),
-								 Int16GetDatum(node->skewColumn),
+								 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 --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 868fb71..66dc24a 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3422,6 +3422,19 @@ _copyCreateForeignTableStmt(const CreateForeignTableStmt *from)
 	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(const CreateTrigStmt *from)
 {
@@ -4290,6 +4303,9 @@ copyObject(const void *from)
 		case T_CreateForeignTableStmt:
 			retval = _copyCreateForeignTableStmt(from);
 			break;
+		case T_ExtraStatStmt:
+			retval = _copyExtraStatStmt(from);
+			break;
 		case T_CreateTrigStmt:
 			retval = _copyCreateTrigStmt(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index d2a79eb..a7bf2a6 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1758,6 +1758,18 @@ _equalCreateForeignTableStmt(const CreateForeignTableStmt *a, const CreateForeig
 }
 
 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(const CreateTrigStmt *a, const CreateTrigStmt *b)
 {
 	COMPARE_STRING_FIELD(trigname);
@@ -2850,6 +2862,9 @@ equal(const void *a, const void *b)
 		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 --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index bb148d7..cae5562 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -13,17 +13,29 @@
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
+#include "postgres_ext.h"
 
+#include "access/skey.h"
+#include "access/relscan.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_collation.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_statistic.h"
+#include "catalog/pg_type.h"
 #include "nodes/makefuncs.h"
+#include "nodes/pg_list.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/plancat.h"
+#include "optimizer/var.h"
+#include "utils/array.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
+#include "utils/rel.h"
 #include "utils/selfuncs.h"
-
+#include "utils/syscache.h"
+#include "utils/tqual.h"
 
 /*
  * Data structure for accumulating info about possible range-query
@@ -33,6 +45,7 @@ typedef struct RangeQueryClause
 {
 	struct RangeQueryClause *next;		/* next in linked list */
 	Node	   *var;			/* The common variable of the clauses */
+	AttrNumber	varattno;	/* for finding cross-column statistics */
 	bool		have_lobound;	/* found a low-bound clause yet? */
 	bool		have_hibound;	/* found a high-bound clause yet? */
 	Selectivity lobound;		/* Selectivity of a var > something clause */
@@ -42,6 +55,24 @@ typedef struct RangeQueryClause
 static void addRangeClause(RangeQueryClause **rqlist, Node *clause,
 			   bool varonleft, bool isLTsel, Selectivity s2);
 
+typedef struct CrossColumnClause
+{
+	struct CrossColumnClause *next;
+	Node	   *var;
+	AttrNumber	varattno;
+	OpExpr	   *expr;
+	Selectivity	sel;
+} CrossColumnClause;
+
+static void addXCClause(CrossColumnClause **xclist, OpExpr *clause,
+			   bool varonleft, Selectivity s2);
+
+static bool crosscolumn_selectivity(Oid relId,
+			   CrossColumnClause **xcnext, RangeQueryClause **rqlist,
+			   Selectivity *result_sel);
+
+static Selectivity calculate_xcol_sel(PlannerInfo *root, CrossColumnClause **clauses, int2vector *attnums, HeapTuple statstuple, TupleDesc tupDesc);
+static Selectivity match_xcol_stats(PlannerInfo *root, Oid relId, CrossColumnClause **xclist);
 
 /****************************************************************************
  *		ROUTINES TO COMPUTE SELECTIVITIES
@@ -98,8 +129,13 @@ clauselist_selectivity(PlannerInfo *root,
 {
 	Selectivity s1 = 1.0;
 	RangeQueryClause *rqlist = NULL;
+	CrossColumnClause *xclist = NULL;
+	Oid		relId = InvalidOid;
+	bool		onerel = false;
 	ListCell   *l;
 
+//	elog(NOTICE, "clauselist_selectivity varRelid %d, list length %d", varRelid, list_length(clauses));
+
 	/*
 	 * If there's exactly one clause, then no use in trying to match up pairs,
 	 * so just go directly to clause_selectivity().
@@ -161,6 +197,22 @@ clauselist_selectivity(PlannerInfo *root,
 					 (varonleft = false,
 					  is_pseudo_constant_clause_relids(linitial(expr->args),
 													   rinfo->left_relids)));
+				if (ok)
+				{
+					int	relid;
+					Oid	tmprelId;
+
+					relid = bms_singleton_member(rinfo->clause_relids);
+					tmprelId = root->simple_rte_array[relid]->relid;
+
+					if (!OidIsValid(relId))
+					{
+						onerel = true;
+						relId = tmprelId;
+					}
+					else if (relId != tmprelId)
+						onerel = false;
+				}
 			}
 			else
 			{
@@ -168,6 +220,25 @@ clauselist_selectivity(PlannerInfo *root,
 					(is_pseudo_constant_clause(lsecond(expr->args)) ||
 					 (varonleft = false,
 					  is_pseudo_constant_clause(linitial(expr->args))));
+				if (ok)
+				{
+					Relids	relids;
+					int	relid;
+					Oid	tmprelId;
+
+					relids = pull_varnos(clause);
+					relid = bms_singleton_member(relids);
+					tmprelId = root->simple_rte_array[relid]->relid;
+					bms_free(relids);
+
+					if (!OidIsValid(relId))
+					{
+						onerel = true;
+						relId = tmprelId;
+					}
+					else if (relId != tmprelId)
+						onerel = false;
+				}
 			}
 
 			if (ok)
@@ -187,6 +258,10 @@ clauselist_selectivity(PlannerInfo *root,
 						addRangeClause(&rqlist, clause,
 									   varonleft, false, s2);
 						break;
+					case F_EQSEL:
+						addXCClause(&xclist, expr,
+									   varonleft, s2);
+						break;
 					default:
 						/* Just merge the selectivity in generically */
 						s1 = s1 * s2;
@@ -201,6 +276,34 @@ clauselist_selectivity(PlannerInfo *root,
 	}
 
 	/*
+	 * Scan xclist and rqlist recursively and filter out
+	 * all possible cross-column selectivities.
+	 */
+	if (onerel)
+	{
+		// elog(NOTICE, "Old selectivity: %0.5f", s1);
+		s1 = s1 * match_xcol_stats(root, relId, &xclist);
+		/*crosscolumn_selectivity(relId, &xclist, &rqlist, &s1);*/
+		// elog(NOTICE, "New selectivity: %0.5f", s1);
+	}
+
+	/*
+	 * Free the cross-column clauses
+	 */
+	while (xclist != NULL)
+	{
+		CrossColumnClause *xcnext;
+
+		// elog(NOTICE, "Freeing att %d", xclist->varattno);
+
+		s1 = s1 * xclist->sel;
+
+		xcnext = xclist->next;
+		pfree(xclist);
+		xclist = xcnext;
+	}
+
+	/*
 	 * Now scan the rangequery pair list.
 	 */
 	while (rqlist != NULL)
@@ -278,6 +381,22 @@ clauselist_selectivity(PlannerInfo *root,
 	return s1;
 }
 
+static AttrNumber
+var_get_attno(Node *clause)
+{
+	Var	   *var;
+
+	if (IsA(clause, Var))
+	{
+		var = (Var *)clause;
+//		elog(NOTICE, "var_get_attno varattno %d", var->varattno);
+		return var->varattno;
+	}
+
+//	elog(NOTICE, "var_get_attno default 0");
+	return 0;
+}
+
 /*
  * addRangeClause --- add a new range clause for clauselist_selectivity
  *
@@ -357,6 +476,8 @@ addRangeClause(RangeQueryClause **rqlist, Node *clause,
 	/* No matching var found, so make a new clause-pair data structure */
 	rqelem = (RangeQueryClause *) palloc(sizeof(RangeQueryClause));
 	rqelem->var = var;
+	rqelem->varattno = var_get_attno(var);
+
 	if (is_lobound)
 	{
 		rqelem->have_lobound = true;
@@ -374,6 +495,49 @@ addRangeClause(RangeQueryClause **rqlist, Node *clause,
 }
 
 /*
+ * addXCClause - add a new clause to the list of clauses for cross-column stats inspection
+ *
+ * Clauses are added in varattno order.
+ */
+static void
+addXCClause(CrossColumnClause **xclist, OpExpr *clause,
+					bool varonleft, Selectivity s)
+{
+	CrossColumnClause *xcelem;
+	CrossColumnClause **xcinsertpos;
+	CrossColumnClause *xcnext;
+	Node	   *var;
+	/*Node	   *expr;*/
+
+	if (varonleft)
+	{
+		var = get_leftop((Expr *) clause);
+		/*expr = get_rightop((Expr *) clause);*/
+	}
+	else
+	{
+		var = get_rightop((Expr *) clause);
+		/*expr = get_leftop((Expr *) clause);*/
+	}
+
+	xcelem = (CrossColumnClause *) palloc(sizeof(CrossColumnClause));
+	xcelem->var = var;
+	xcelem->varattno = var_get_attno(var);
+	xcelem->expr = clause;
+	xcelem->sel = s;
+
+	xcinsertpos = xclist;
+	xcnext = *xclist;
+	while (xcnext && xcnext->varattno < xcelem->varattno)
+	{
+		xcinsertpos = &(xcnext->next);
+		xcnext = xcnext->next;
+	}
+	xcelem->next = xcnext;
+	*xcinsertpos = xcelem;
+}
+
+/*
  * bms_is_subset_singleton
  *
  * Same result as bms_is_subset(s, bms_make_singleton(x)),
@@ -498,6 +662,8 @@ clause_selectivity(PlannerInfo *root,
 	{
 		rinfo = (RestrictInfo *) clause;
 
+//		elog(NOTICE, "RestrictInfo, %s", nodeToString(rinfo->clause));
+
 		/*
 		 * If the clause is marked pseudoconstant, then it will be used as a
 		 * gating qual and should not affect selectivity estimates; hence
@@ -563,7 +729,7 @@ clause_selectivity(PlannerInfo *root,
 
 		/*
 		 * We probably shouldn't ever see an uplevel Var here, but if we do,
-		 * return the default selectivity...
+		 * return the default selectivity... (FIXME: wrong comment given code below)
 		 */
 		if (var->varlevelsup == 0 &&
 			(varRelid == 0 || varRelid == (int) var->varno))
@@ -778,3 +944,571 @@ clause_selectivity(PlannerInfo *root,
 
 	return s1;
 }
+
+static bool
+has_xcol_selectivity(Oid relId, int natts, AttrNumber *attnums, Selectivity *result_sel)
+{
+	Relation	rel;
+	Datum		*datums = (Datum *)palloc(natts * sizeof(Datum));
+	ArrayType	*arr_attnums;
+	int		i;
+	int16		typlen;
+	bool		typbyval;
+	char		typalign;
+	ScanKeyData	scanKey[2];
+	SysScanDesc	scan;
+	HeapTuple	tuple;
+	bool		result;
+	Selectivity	sel = 1e-5; /* fixed selectivity for now */
+
+	for (i = 0; i < natts; i++)
+		datums[i] = Int16GetDatum(attnums[i]);
+
+	get_typlenbyvalalign(INT2OID, &typlen, &typbyval, &typalign);
+	arr_attnums = construct_array(datums, natts,
+							INT2OID, typlen, typbyval, typalign);
+
+	rel = heap_open(StatisticRelationId, AccessShareLock);
+
+	ScanKeyInit(&scanKey[0],
+					Anum_pg_statistic_starelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(relId));
+	ScanKeyInit(&scanKey[1],
+					Anum_pg_statistic_staattnums,
+					BTEqualStrategyNumber, F_ARRAY_EQ,
+					PointerGetDatum(arr_attnums));
+
+	scan = systable_beginscan(rel, StatisticRelidAttnumsInhIndexId, true,
+								SnapshotNow, 2, scanKey);
+
+	tuple = systable_getnext(scan);
+
+	result = HeapTupleIsValid(tuple);
+
+	systable_endscan(scan);
+
+	heap_close(rel, NoLock);
+
+	pfree(datums);
+
+	if (result)
+		*result_sel = sel;
+
+	return result;
+}
+
+static Selectivity
+match_xcol_stats(PlannerInfo *root, Oid relId, CrossColumnClause **xclist)
+{
+	Relation	pgstatistic;
+	Datum		datums[2];
+	ArrayType	*arr_attnums;
+	int16		typlen;
+	bool		typbyval;
+	char		typalign;
+	ScanKeyData	scanKey[2];
+	SysScanDesc	scan;
+	HeapTuple	tuple;
+	TupleDesc	tupDesc;
+	bool		isnull;
+	Datum		sta_attnums_dat;
+	int2vector	*sta_attnum_vec;
+	CrossColumnClause *clauses[STATISTIC_NUM_SLOTS];
+	Selectivity s1, s2;
+	Form_pg_statistic stats;
+
+	s1 = 1.0;
+
+	datums[0] = Int16GetDatum(0);
+	datums[1] = Int16GetDatum(0);
+	get_typlenbyvalalign(INT2OID, &typlen, &typbyval, &typalign);
+	arr_attnums = construct_array(datums, 2,
+							INT2OID, typlen, typbyval, typalign);
+
+
+	pgstatistic = heap_open(StatisticRelationId, AccessShareLock);
+	tupDesc = RelationGetDescr(pgstatistic);
+
+	ScanKeyInit(&scanKey[0],
+					Anum_pg_statistic_starelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(relId));
+	ScanKeyInit(&scanKey[1],
+					Anum_pg_statistic_staattnums,
+					BTGreaterEqualStrategyNumber, F_INT2VECTORGE,
+					PointerGetDatum(arr_attnums));
+
+	scan = systable_beginscan(pgstatistic, StatisticRelidAttnumsInhIndexId, true,
+								SnapshotNow, 2, scanKey);
+
+	while (HeapTupleIsValid(tuple = systable_getnext(scan))) {
+		CrossColumnClause *xc_tmp;
+		CrossColumnClause **prev_link;
+		int i, cur_attno;
+		Selectivity old_sel = 1.0;
+
+		stats = (Form_pg_statistic) GETSTRUCT(tuple);
+		if (!stats->stavalid)
+			continue;
+
+		sta_attnums_dat = heap_getattr(tuple, Anum_pg_statistic_staattnums, tupDesc, &isnull);
+		Assert(!isnull && sta_attnums_dat);
+		sta_attnum_vec = (int2vector *) DatumGetPointer(sta_attnums_dat);
+
+		/* match up against clauses */
+		xc_tmp = *xclist;
+		for (i = 0; i < sta_attnum_vec->dim1; i++)
+		{
+			cur_attno = sta_attnum_vec->values[i];
+			while (xc_tmp && xc_tmp->varattno < cur_attno)
+				xc_tmp = xc_tmp->next;
+
+			if (!xc_tmp || xc_tmp->varattno > cur_attno)
+				break;
+
+			/* store picked clause */
+			clauses[i] = xc_tmp;
+		}
+		if (i != sta_attnum_vec->dim1) /* No match */
+			continue;
+
+
+
+		s2 = calculate_xcol_sel(root, clauses, sta_attnum_vec, tuple, tupDesc);
+		if (s2 < 0.0)
+			continue;
+
+		/* remove matched clauses from consideration */
+		prev_link = xclist;
+		xc_tmp = *xclist;
+		i = 0;
+		while (xc_tmp && i < sta_attnum_vec->dim1)
+		{
+			if (xc_tmp == clauses[i])
+			{
+				CrossColumnClause *xc_removed;
+				// elog(NOTICE, "Removing clause with att %d from consideration", xc_tmp->varattno);
+				old_sel *= xc_tmp->sel;
+				*prev_link = xc_tmp->next;
+				xc_removed = xc_tmp;
+				xc_tmp = xc_tmp->next;
+				pfree(xc_removed);
+				i++;
+
+			} else {
+				prev_link = &(xc_tmp->next);
+				xc_tmp = xc_tmp->next;
+			}
+		}
+		// elog(NOTICE, "New selectivity %0.5f old sel %0.5f", s2, old_sel);
+		/* Merge in selectivity */
+		s1 = s1*s2;
+	}
+	systable_endscan(scan);
+
+	heap_close(pgstatistic, AccessShareLock);
+
+	return s1;
+}
+
+/* Calculates selectivity from cross column statistics.
+ * Returns a negative number if not possible to calculate. */
+static Selectivity
+calculate_xcol_sel(PlannerInfo *root, CrossColumnClause **clauses, int2vector *attnums, HeapTuple statstuple, TupleDesc tupDesc)
+{
+	int i;
+	/* elog(NOTICE, "Calculating stats for %d clauses", attnums->dim1);
+	for (i = 0; i < attnums->dim1; i++)
+	{
+		elog(NOTICE, "Attribute number: %d", clauses[i]->varattno);
+	}*/
+	bool		isnull;
+	Datum		values_dat;
+	ArrayType	*statarray;
+	Oid			arrayelemtype;
+	HeapTuple	typeTuple;
+	Form_pg_type typeForm;
+	Datum		*values;
+	int			nvalues;
+	int			j;
+	CrossColumnClause *clause;
+	Form_pg_statistic stats;
+	int			dimindices[STATISTIC_NUM_SLOTS];
+	int			dimstrides[STATISTIC_NUM_SLOTS];
+	int			mcv_index;
+	Selectivity		s1 = 1.0;
+
+	stats = (Form_pg_statistic) GETSTRUCT(statstuple);
+
+	for (i = 0; i < attnums->dim1; i++)
+	{
+		VariableStatData vardata;
+		Node		*other;
+		bool		varonleft;
+		Datum 		constval;
+		bool		result;
+		FmgrInfo	eqproc;
+
+		clause = clauses[i];
+
+		/* FIXME: refactor multi-col stats storage to store operators per col
+		 * so we can check that the correct op is used
+		opproc = DatumGetObjectId(heap_getattr(statstuple, Anum_pg_statistic_staop1 + i,
+				  tupDesc, &isnull));
+		Assert(opproc == clause->expr->opno); */
+		values_dat = heap_getattr(statstuple, Anum_pg_statistic_stavalues1 + i,
+								  tupDesc, &isnull);
+		Assert(!isnull);
+
+		statarray = DatumGetArrayTypeP(values_dat);
+
+		arrayelemtype = ARR_ELEMTYPE(statarray);
+		typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(arrayelemtype));
+		if (!HeapTupleIsValid(typeTuple))
+			elog(ERROR, "cache lookup failed for type %u", arrayelemtype);
+
+		typeForm = (Form_pg_type) GETSTRUCT(typeTuple);
+		deconstruct_array(statarray,
+						  arrayelemtype,
+						  typeForm->typlen,
+						  typeForm->typbyval,
+						  typeForm->typalign,
+						  &values, NULL, &nvalues);
+		ReleaseSysCache(typeTuple);
+
+		dimstrides[i] = 1;
+		for (j = i-1; j >= 0; j--)
+		{
+			dimstrides[j] *= (nvalues + 2);
+		}
+
+		get_restriction_variable(root, clause->expr->args, 0 /* FIXME: pass this in */,
+							&vardata, &other, &varonleft);
+		constval = ((Const *) other)->constvalue;
+
+		/* Null is stored in the first item of a dimension */
+		if (((Const *)other)->constisnull)
+		{
+			dimindices[i] = 0;
+			ReleaseVariableStats(vardata);
+			continue;
+		}
+
+		/* TODO: use binary search when possible */
+		for (j = 0; j < nvalues; j++)
+		{
+			fmgr_info(get_opcode(clause->expr->opno), &eqproc);
+			// elog(NOTICE, "Comparing %d to %d using %d", values[j], constval, clause->expr->opno);
+			result = DatumGetBool(FunctionCall2Coll(&eqproc,
+									   DEFAULT_COLLATION_OID,
+									   values[j],
+									   constval));
+			if (result) { /* Found matching value */
+				dimindices[i] = j+2;
+				break;
+			}
+		}
+
+		if (j == nvalues) /* Nothing matching was found in MCV's */
+		{
+			double ndistinct;
+			bool isdefault;
+
+			// elog(NOTICE, "Value for dimension %d not found, constval %d", i, constval);
+			dimindices[i] = 1;
+
+			/* We presume uniform distribution of values not present in MCV.
+			 * Predivide selectivity with ndistinct for this column. */
+			ndistinct = get_variable_numdistinct(&vardata, &isdefault);
+			ndistinct -= nvalues;
+			// elog(NOTICE, "Predividing selectivity with %f", ndistinct);
+			if (ndistinct > 1.0)
+				s1 = s1 / ndistinct;
+		}
+
+		/*
+		 * Free statarray if it's a detoasted copy.
+		 */
+		if ((Pointer) statarray != DatumGetPointer(values_dat))
+			pfree(statarray);
+
+		ReleaseVariableStats(vardata);
+	}
+
+	mcv_index = 0;
+	for (i = 0; i < attnums->dim1; i++)
+	{
+		mcv_index += dimindices[i]*dimstrides[i];
+		// elog(NOTICE, "MCV dimension %d index %d", i, dimindices[i]);
+	}
+	// elog(NOTICE, "MCV position %d", mcv_index);
+	do {
+		Datum		numbers_dat;
+		int			narrayelem;
+
+		numbers_dat = heap_getattr(statstuple, Anum_pg_statistic_stanumbers1,
+								  tupDesc, &isnull);
+		Assert(!isnull);
+
+		statarray = DatumGetArrayTypeP(numbers_dat);
+
+		/*
+		 * We expect the array to be a 1-D float4 array; verify that. We don't
+		 * need to use deconstruct_array() since the array data is just going
+		 * to look like a C array of float4 values.
+		 */
+		narrayelem = ARR_DIMS(statarray)[0];
+		if (ARR_NDIM(statarray) != 1 || narrayelem <= 0 ||
+			ARR_HASNULL(statarray) ||
+			ARR_ELEMTYPE(statarray) != FLOAT4OID)
+			elog(ERROR, "stanumbers is not a 1-D float4 array");
+		if (narrayelem <= mcv_index)
+			elog(ERROR, "stanumbers is too short");
+		s1 = s1 * ((float4 *) ARR_DATA_PTR(statarray))[mcv_index];
+
+		/*
+		 * Free statarray if it's a detoasted copy.
+		 */
+		if ((Pointer) statarray != DatumGetPointer(numbers_dat))
+			pfree(statarray);
+
+	} while(0);
+
+	//elog(NOTICE, "Returning selectivity %0.7f", s1);
+	return s1;
+}
+
+typedef struct {
+	CrossColumnClause	*xc;
+	RangeQueryClause	*rq;
+} reclist;
+
+typedef struct {
+	int	len;
+	reclist	*rclist;
+	AttrNumber *attnums;
+} reclist2;
+
+
+/* add rclist to our list, so the ordered attnums arrays are unique */
+static void
+add_reclist(int len, reclist *rclist, List **results)
+{
+	ListCell   *lc;
+	int		i, j;
+	reclist2	*rclist2;
+	AttrNumber	*attnums = (AttrNumber *) palloc(len * sizeof(AttrNumber));
+
+	/* collect the ordered varattnos from the Vars */
+	for (i = 0; i < len; i++)
+	{
+		if (rclist[i].xc)
+			attnums[i] = rclist[i].xc->varattno;
+		else
+			attnums[i] = rclist[i].rq->varattno;
+	}
+	/* FIXME: is this supposed to be a bubblesort? Maybe do an insertion sort? */
+	for (i = 0; i < len - 1; i++)
+		for (j = i + 1; j < len; j++)
+		{
+			AttrNumber tmp = attnums[i];
+			attnums[i] = attnums[j];
+			attnums[j] = tmp;
+		}
+
+	/* match this ordered attnum list against the current list of attnum arrays */
+	foreach(lc, *results)
+	{
+		reclist2   *rc2 = (reclist2 *) lfirst(lc);
+
+		if (len != rc2->len)
+			continue;
+
+		for (i = 0; i < len; i++)
+			if (attnums[i] != rc2->attnums[i])
+				break;
+		if (i < len)
+			continue;
+
+		/* found */
+		return;
+	}
+
+	/* not found, add it to the list */
+	rclist2 = (reclist2 *) palloc(sizeof(reclist2));
+	rclist2->len = len;
+	rclist2->rclist = (reclist *) palloc(len * sizeof(reclist));
+	for (i = 0; i < len; i++)
+	{
+		rclist2->rclist[i].xc = rclist[i].xc;
+		rclist2->rclist[i].rq = rclist[i].rq;
+	}
+	rclist2->attnums = attnums;
+
+	*results = lappend(*results, rclist2);
+}
+
+static int
+compare_reclist2(reclist2 *a, reclist2 *b)
+{
+	int	i;
+
+	if (a->len < b->len)
+		return -1;
+	else if (a->len > b->len)
+		return 1;
+
+	for (i = 0; i < a->len; i++)
+	{
+		if (a->attnums[i] < b->attnums[i])
+			return -1;
+		else if (a->attnums[i] > b->attnums[i])
+			return 1;
+	}
+
+	return 0;
+}
+
+static bool
+add_reclist2(int *len, reclist2 **p_reclist2, reclist2 *rclist2)
+{
+	int	curr_len = *len;
+	int	i, j;
+
+	if (curr_len == 0)
+	{
+		p_reclist2[i] = rclist2;
+		curr_len++;
+		*len = curr_len;
+		return true;
+	}
+
+	for (i = 0; i < curr_len; i++)
+	{
+		if (compare_reclist2(rclist2, p_reclist2[i]) > 0)
+		{
+			for (j = curr_len; j > i; j--)
+				p_reclist2[j] = p_reclist2[j - 1];
+			p_reclist2[i] = rclist2;
+			curr_len++;
+			*len = curr_len;
+			return true;
+		}
+	}
+
+	return false;
+}
+
+static void
+collect_xcol_lists(int curr_depth, CrossColumnClause *xclist, RangeQueryClause *rqlist, reclist *rclist, List **results)
+{
+	CrossColumnClause	*xc_tmp;
+	RangeQueryClause	*rq_tmp;
+
+	for (xc_tmp = xclist; xc_tmp; xc_tmp = xc_tmp->next)
+	{
+		if (xc_tmp->varattno == 0)
+			continue;
+
+		rclist[curr_depth].xc = xc_tmp;
+		collect_xcol_lists(curr_depth + 1, xc_tmp->next, rqlist, rclist, results);
+		add_reclist(curr_depth + 1, rclist, results);
+		rclist[curr_depth].xc = NULL;
+	}
+
+	for (rq_tmp = rqlist; rq_tmp; rq_tmp = rq_tmp->next)
+	{
+		if (rq_tmp->varattno == 0)
+			continue;
+
+		rclist[curr_depth].rq = rq_tmp;
+		collect_xcol_lists(curr_depth + 1, (xclist ? xclist->next : xclist), rq_tmp->next, rclist, results);
+		add_reclist(curr_depth + 1, rclist, results);
+		rclist[curr_depth].rq = NULL;
+	}
+}
+
+static bool
+crosscolumn_selectivity(Oid relId, CrossColumnClause **xclist, RangeQueryClause **rqlist, Selectivity *result_sel)
+{
+	CrossColumnClause *xc;
+	RangeQueryClause *rq;
+	List	   *resultlist = NIL;
+	ListCell   *lc;
+	reclist	   *rclist;
+	reclist2   **p_rclist2;
+	int		max_len, i;
+	Selectivity	sel = 1.0;
+	bool		found_xc_sel = false;
+
+	max_len = 0;
+	for (rq = *rqlist; rq; max_len++, rq = rq->next)
+		;
+	for (xc = *xclist; xc; max_len++, xc = xc->next)
+		;
+
+//	elog(NOTICE, "crosscolumn_selectivity max length of array %d", max_len);
+
+	rclist = (reclist *) palloc(max_len * sizeof(reclist));
+	for (i = 0; i < max_len; i++)
+	{
+		rclist[i].xc = NULL;
+		rclist[i].rq = NULL;
+	}
+
+	collect_xcol_lists(0, *xclist, *rqlist, rclist, &resultlist);
+
+	pfree(rclist);
+
+	max_len = list_length(resultlist);
+//	elog(NOTICE, "crosscolumn_selectivity list length of arrays %d", max_len);
+	p_rclist2 = (reclist2 **) palloc(max_len * sizeof(reclist2 *));
+
+	max_len = 0;
+	foreach (lc, resultlist)
+	{
+		reclist2 *rclist2 = (reclist2 *) lfirst(lc);
+
+		if (!add_reclist2(&max_len, p_rclist2, rclist2))
+		{
+			pfree(rclist2->rclist);
+			pfree(rclist2->attnums);
+			pfree(rclist2);
+		}
+	}
+//	elog(NOTICE, "crosscolumn_selectivity length of ordered/unique array of previous list %d", max_len);
+
+	list_free(resultlist);
+
+	for (i = 0; i < max_len; i++)
+	{
+		if (p_rclist2[i] == NULL)
+			continue;
+
+		if (has_xcol_selectivity(relId, p_rclist2[i]->len, p_rclist2[i]->attnums, &sel))
+		{
+			int	j;
+
+			/* remove the xclist and rqlist members found in p_rclist2[i] */
+			for (j = 0; j < p_rclist2[i]->len; j++)
+			{
+				/* TODO ... */
+			}
+
+			/* also, remove later elements in p_rclist2 that has any of the removed elements */
+			/* TODO ... */
+
+//			elog(NOTICE, "crosscolumn_selectivity found xc selectivity %lf", sel);
+			found_xc_sel = true;
+			*result_sel *= sel;
+		}
+
+		pfree(p_rclist2[i]->rclist);
+		pfree(p_rclist2[i]->attnums);
+		pfree(p_rclist2[i]);
+	}
+	pfree(p_rclist2);
+
+	return found_xc_sel;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9aea2cd..7e4f352 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -214,7 +214,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
 		DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt
 		DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt
 		DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
-		DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt
+		DropForeignServerStmt DropUserMappingStmt ExplainStmt ExtraStatStmt FetchStmt
 		GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt
 		LockStmt NotifyStmt ExplainableStmt PreparableStmt
 		CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
@@ -246,7 +246,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
 				transaction_mode_item
 				create_extension_opt_item alter_extension_opt_item
 
-%type <ival>	opt_lock lock_type cast_context
+%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
@@ -325,6 +325,8 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
 %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
 
@@ -755,6 +757,7 @@ stmt :
 			| DropdbStmt
 			| ExecuteStmt
 			| ExplainStmt
+			| ExtraStatStmt
 			| FetchStmt
 			| GrantStmt
 			| GrantRoleStmt
@@ -1199,6 +1202,74 @@ schema_stmt:
 
 /*****************************************************************************
  *
+ * 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 --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 43f5634..2378435 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -2754,3 +2754,96 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
 						"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);
+
+			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 --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 5b81c0b..6506142 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -230,6 +230,7 @@ check_xact_readonly(Node *parsetree)
 		case T_AlterTableSpaceOptionsStmt:
 		case T_CreateForeignTableStmt:
 		case T_SecLabelStmt:
+		case T_ExtraStatStmt:
 			PreventCommandIfReadOnly(CreateCommandTag(parsetree));
 			break;
 		default:
@@ -574,6 +575,14 @@ standard_ProcessUtility(Node *parsetree,
 			}
 			break;
 
+		case T_ExtraStatStmt:
+			{
+				ExtraStatStmt *newstmt = transformExtraStatistics((ExtraStatStmt *)parsetree, queryString);
+
+				ExtraStatistics(newstmt);
+			}
+			break;
+
 		case T_CreateTableSpaceStmt:
 			PreventTransactionChain(isTopLevel, "CREATE TABLESPACE");
 			CreateTableSpace((CreateTableSpaceStmt *) parsetree);
@@ -1638,6 +1647,17 @@ CreateCommandTag(Node *parsetree)
 			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 --git a/src/backend/tsearch/ts_typanalyze.c b/src/backend/tsearch/ts_typanalyze.c
index 9771415..170d5ca 100644
--- a/src/backend/tsearch/ts_typanalyze.c
+++ b/src/backend/tsearch/ts_typanalyze.c
@@ -35,7 +35,8 @@ typedef struct
 	int			delta;			/* And this is 'delta'. */
 } TrackItem;
 
-static void compute_tsvector_stats(VacAttrStats *stats,
+static void compute_tsvector_stats(VacStats *stats,
+					   int index,
 					   AnalyzeAttrFetchFunc fetchfunc,
 					   int samplerows,
 					   double totalrows);
@@ -53,17 +54,21 @@ static int	trackitem_compare_lexemes(const void *e1, const void *e2);
 Datum
 ts_typanalyze(PG_FUNCTION_ARGS)
 {
-	VacAttrStats *stats = (VacAttrStats *) PG_GETARG_POINTER(0);
-	Form_pg_attribute attr = stats->attr;
+	VacStats *stats = (VacStats *) PG_GETARG_POINTER(0);
+	int4		i = PG_GETARG_INT32(1);
 
-	/* 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;
+	if (i < 0 || i >= STATISTIC_NUM_SLOTS)
+		PG_RETURN_BOOL(false);
 
-	stats->compute_stats = compute_tsvector_stats;
+	/* 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 * attr->attstattarget;
+	stats->minrows = 300 * stats->statarget;
 
 	PG_RETURN_BOOL(true);
 }
@@ -136,7 +141,8 @@ ts_typanalyze(PG_FUNCTION_ARGS)
  *	want.
  */
 static void
-compute_tsvector_stats(VacAttrStats *stats,
+compute_tsvector_stats(VacStats *stats,
+					   int index,
 					   AnalyzeAttrFetchFunc fetchfunc,
 					   int samplerows,
 					   double totalrows)
@@ -166,7 +172,7 @@ compute_tsvector_stats(VacAttrStats *stats,
 	 * 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;
+	num_mcelem = stats->statarget * 10;
 
 	/*
 	 * We set bucket width equal to (num_mcelem + 10) / 0.007 as per the
@@ -206,7 +212,7 @@ compute_tsvector_stats(VacAttrStats *stats,
 
 		vacuum_delay_point();
 
-		value = fetchfunc(stats, vector_no, &isnull);
+		value = fetchfunc(stats, vector_no, stats->attnums->values[index], &isnull);
 
 		/*
 		 * Check for null/nonnull.
@@ -291,11 +297,11 @@ compute_tsvector_stats(VacAttrStats *stats,
 
 		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;
+		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 = -1.0;
+		stats->stadistinct[index] = -1.0;
 
 		/*
 		 * Construct an array of the interesting hashtable items, that is,
@@ -403,27 +409,27 @@ compute_tsvector_stats(VacAttrStats *stats,
 			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;
+			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[0] = num_mcelem + 2;
-			stats->stavalues[0] = mcelem_values;
-			stats->numvalues[0] = num_mcelem;
+			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[0] = TEXTOID;
-			stats->statyplen[0] = -1;	/* typlen, -1 for varlena */
-			stats->statypbyval[0] = false;
-			stats->statypalign[0] = 'i';
+			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 = 1.0;
-		stats->stawidth = 0;	/* "unknown" */
-		stats->stadistinct = 0.0;		/* "unknown" */
+		stats->stanullfrac[index] = 1.0;
+		stats->stawidth[index] = 0;	/* "unknown" */
+		stats->stadistinct[index] = 0.0;		/* "unknown" */
 	}
 
 	/*
diff --git a/src/backend/utils/adt/array_typanalyze.c b/src/backend/utils/adt/array_typanalyze.c
index ba98739..4abc29e 100644
--- a/src/backend/utils/adt/array_typanalyze.c
+++ b/src/backend/utils/adt/array_typanalyze.c
@@ -50,7 +50,7 @@ typedef struct
 	FmgrInfo   *hash;
 
 	/* Saved state from std_typanalyze() */
-	AnalyzeAttrComputeStatsFunc std_compute_stats;
+	ComputeStatsFunc std_compute_stats;
 	void	   *std_extra_data;
 } ArrayAnalyzeExtraData;
 
@@ -78,7 +78,7 @@ typedef struct
 	int			frequency;		/* Number of arrays seen with this count */
 } DECountItem;
 
-static void compute_array_stats(VacAttrStats *stats,
+static void compute_array_stats(VacStats *stats, int index,
 		   AnalyzeAttrFetchFunc fetchfunc, int samplerows, double totalrows);
 static void prune_element_hashtable(HTAB *elements_tab, int b_current);
 static uint32 element_hash(const void *key, Size keysize);
@@ -95,7 +95,8 @@ static int	countitem_compare_count(const void *e1, const void *e2);
 Datum
 array_typanalyze(PG_FUNCTION_ARGS)
 {
-	VacAttrStats *stats = (VacAttrStats *) PG_GETARG_POINTER(0);
+	VacStats *stats = (VacStats *) PG_GETARG_POINTER(0);
+	int4 index = PG_GETARG_INT32(1);
 	Oid			element_typeid;
 	TypeCacheEntry *typentry;
 	ArrayAnalyzeExtraData *extra_data;
@@ -104,17 +105,17 @@ array_typanalyze(PG_FUNCTION_ARGS)
 	 * Call the standard typanalyze function.  It may fail to find needed
 	 * operators, in which case we also can't do anything, so just fail.
 	 */
-	if (!std_typanalyze(stats))
+	if (!std_typanalyze(stats, index))
 		PG_RETURN_BOOL(false);
 
 	/*
 	 * Check attribute data type is a varlena array.
 	 */
-	element_typeid = stats->attrtype->typelem;
+	element_typeid = stats->attrtypes[index]->typelem;
 
-	if (!OidIsValid(element_typeid) || stats->attrtype->typlen != -1)
+	if (!OidIsValid(element_typeid) || stats->attrtypes[index]->typlen != -1)
 		elog(ERROR, "array_typanalyze was invoked for non-array type %u",
-			 stats->attrtypid);
+			 stats->attrtypids[index]);
 
 	/*
 	 * Gather information about the element type.  If we fail to find
@@ -141,12 +142,12 @@ array_typanalyze(PG_FUNCTION_ARGS)
 	extra_data->hash = &typentry->hash_proc_finfo;
 
 	/* Save old compute_stats and extra_data for scalar statistics ... */
-	extra_data->std_compute_stats = stats->compute_stats;
-	extra_data->std_extra_data = stats->extra_data;
+	extra_data->std_compute_stats = stats->statfuncs[index].compute_func_ptr;
+	extra_data->std_extra_data = stats->extra_data[index];
 
 	/* ... and replace with our info */
-	stats->compute_stats = compute_array_stats;
-	stats->extra_data = extra_data;
+	stats->statfuncs[index].compute_func_ptr = compute_array_stats;
+	stats->extra_data[index] = extra_data;
 
 	/*
 	 * Note we leave stats->minrows set as std_typanalyze set it.  Should
@@ -211,7 +212,7 @@ array_typanalyze(PG_FUNCTION_ARGS)
  * We divide the raw counts by nonnull_cnt to get those figures.
  */
 static void
-compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
+compute_array_stats(VacStats *stats, int index, AnalyzeAttrFetchFunc fetchfunc,
 					int samplerows, double totalrows)
 {
 	ArrayAnalyzeExtraData *extra_data;
@@ -238,16 +239,16 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 	HASHCTL		count_hash_ctl;
 	DECountItem *count_item;
 
-	extra_data = (ArrayAnalyzeExtraData *) stats->extra_data;
+	extra_data = (ArrayAnalyzeExtraData *) stats->extra_data[index];
 
 	/*
 	 * Invoke analyze.c's standard analysis function to create scalar-style
 	 * stats for the column.  It will expect its own extra_data pointer,
 	 * so temporarily install that.
 	 */
-	stats->extra_data = extra_data->std_extra_data;
-	(*extra_data->std_compute_stats) (stats, fetchfunc, samplerows, totalrows);
-	stats->extra_data = extra_data;
+	stats->extra_data[index] = extra_data->std_extra_data;
+	(*extra_data->std_compute_stats) (stats, index, fetchfunc, samplerows, totalrows);
+	stats->extra_data[index] = extra_data;
 
 	/*
 	 * Set up static pointer for use by subroutines.  We wait till here in
@@ -262,7 +263,7 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 	 * the number of individual elements tracked in pg_statistic ought to be
 	 * more than the number of values for a simple scalar column.
 	 */
-	num_mcelem = stats->attr->attstattarget * 10;
+	num_mcelem = stats->statarget * 10;
 
 	/*
 	 * We set bucket width equal to num_mcelem / 0.007 as per the comment
@@ -318,7 +319,7 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 
 		vacuum_delay_point();
 
-		value = fetchfunc(stats, array_no, &isnull);
+		value = fetchfunc(stats, array_no, stats->attnums->values[index], &isnull);
 		if (isnull)
 		{
 			/* array is null, just count that */
@@ -437,7 +438,7 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 
 	/* Skip pg_statistic slots occupied by standard statistics */
 	slot_idx = 0;
-	while (slot_idx < STATISTIC_NUM_SLOTS && stats->stakind[slot_idx] != 0)
+	while (slot_idx < STATISTIC_NUM_SLOTS && stats->stakind[index][slot_idx] != 0)
 		slot_idx++;
 	if (slot_idx > STATISTIC_NUM_SLOTS - 2)
 		elog(ERROR, "insufficient pg_statistic slots for array stats");
@@ -558,18 +559,18 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 
 			MemoryContextSwitchTo(old_context);
 
-			stats->stakind[slot_idx] = STATISTIC_KIND_MCELEM;
-			stats->staop[slot_idx] = extra_data->eq_opr;
-			stats->stanumbers[slot_idx] = mcelem_freqs;
+			stats->stakind[index][slot_idx] = STATISTIC_KIND_MCELEM;
+			stats->staop[index][slot_idx] = extra_data->eq_opr;
+			stats->stanumbers[index][slot_idx] = mcelem_freqs;
 			/* See above comment about extra stanumber entries */
-			stats->numnumbers[slot_idx] = num_mcelem + 3;
-			stats->stavalues[slot_idx] = mcelem_values;
-			stats->numvalues[slot_idx] = num_mcelem;
+			stats->numnumbers[index][slot_idx] = num_mcelem + 3;
+			stats->stavalues[index][slot_idx] = mcelem_values;
+			stats->numvalues[index][slot_idx] = num_mcelem;
 			/* We are storing values of element type */
-			stats->statypid[slot_idx] = extra_data->type_id;
-			stats->statyplen[slot_idx] = extra_data->typlen;
-			stats->statypbyval[slot_idx] = extra_data->typbyval;
-			stats->statypalign[slot_idx] = extra_data->typalign;
+			stats->statypid[index][slot_idx] = extra_data->type_id;
+			stats->statyplen[index][slot_idx] = extra_data->typlen;
+			stats->statypbyval[index][slot_idx] = extra_data->typbyval;
+			stats->statypalign[index][slot_idx] = extra_data->typalign;
 			slot_idx++;
 		}
 
@@ -577,7 +578,7 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 		count_items_count = hash_get_num_entries(count_tab);
 		if (count_items_count > 0)
 		{
-			int			num_hist = stats->attr->attstattarget;
+			int			num_hist = stats->statarget;
 			DECountItem **sorted_count_items;
 			int			j;
 			int			delta;
@@ -659,10 +660,10 @@ compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 			}
 			Assert(j == count_items_count - 1);
 
-			stats->stakind[slot_idx] = STATISTIC_KIND_DECHIST;
-			stats->staop[slot_idx] = extra_data->eq_opr;
-			stats->stanumbers[slot_idx] = hist;
-			stats->numnumbers[slot_idx] = num_hist + 1;
+			stats->stakind[index][slot_idx] = STATISTIC_KIND_DECHIST;
+			stats->staop[index][slot_idx] = extra_data->eq_opr;
+			stats->stanumbers[index][slot_idx] = hist;
+			stats->numnumbers[index][slot_idx] = num_hist + 1;
 			slot_idx++;
 		}
 	}
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index a339abf..cb10dbe 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -254,21 +254,53 @@ int2vectorsend(PG_FUNCTION_ARGS)
 	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);
+	int32		cmp = DatumGetInt32(btint2vectorcmp(fcinfo));
 
-	if (a->dim1 != b->dim1)
-		PG_RETURN_BOOL(false);
-	PG_RETURN_BOOL(memcmp(a->values, b->values, a->dim1 * sizeof(int2)) == 0);
+	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 --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 382cd73..34db4a6 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -94,6 +94,7 @@
 #include "access/gin.h"
 #include "access/sysattr.h"
 #include "catalog/index.h"
+#include "catalog/indexing.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_statistic.h"
@@ -112,6 +113,7 @@
 #include "optimizer/var.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
+#include "parser/parse_utilcmd.h"
 #include "parser/parsetree.h"
 #include "utils/builtins.h"
 #include "utils/bytea.h"
@@ -4140,6 +4142,34 @@ get_join_variables(PlannerInfo *root, List *args, SpecialJoinInfo *sjinfo,
 }
 
 /*
+ * 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.
@@ -4177,6 +4207,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 {
 	Node	   *basenode;
 	Relids		varnos;
+	int		onerelid = 0;
 	RelOptInfo *onerel;
 
 	/* Make sure we don't return dangling pointers in vardata */
@@ -4229,8 +4260,8 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 		case BMS_SINGLETON:
 			if (varRelid == 0 || bms_is_member(varRelid, varnos))
 			{
-				onerel = find_base_rel(root,
-					   (varRelid ? varRelid : bms_singleton_member(varnos)));
+				onerelid = (varRelid ? varRelid : bms_singleton_member(varnos));
+				onerel = find_base_rel(root, onerelid);
 				vardata->rel = onerel;
 				node = basenode;	/* strip any relabeling */
 			}
@@ -4264,15 +4295,82 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 	{
 		/*
 		 * We have an expression in vars of a single relation.	Try to match
-		 * it to expressional index columns, in hopes of finding some
-		 * statistics.
+		 * it to expression statistics first then to expressional index columns,
+		 * in hopes of finding some statistics.
 		 *
 		 * XXX it's conceivable that there are multiple matches with different
 		 * index opfamilies; if so, we need to pick one that matches the
 		 * operator we are estimating for.	FIXME later.
 		 */
 		ListCell   *ilist;
+		Node	   *expr = copyObject(node);
+		char	   *exprbin;
+		Datum		exprbindatum;
+
+#if 0
+#define USE_SYSCACHE_FOR_SEARCH	0
+#if !USE_SYSCACHE_FOR_SEARCH
+		Relation	rel;
+		ScanKeyData	scanKey[2];  
+		SysScanDesc	scan;
+		HeapTuple	tuple;
+#endif
+#endif
+
+		/* FIXME:
+		 * query_or_expression_tree_walker(expr, set_location_unknown_walker, NULL, 0);*/
+		exprbin = nodeToString(expr);
+		exprbindatum = CStringGetTextDatum(exprbin);
+
+#if 0
+#if USE_SYSCACHE_FOR_SEARCH
+		vardata->statsTuple = SearchSysCache3(STAT3RELEXPRINH,
+								ObjectIdGetDatum(root->simple_rte_array[onerelid]->relid),
+								exprbindatum,
+								BoolGetDatum(false));
+		if (HeapTupleIsValid(vardata->statsTuple))
+		{
+			vardata->stats_type = STAT_EXPRESSION;
+			vardata->freefunc = ReleaseSysCache;
+			return;
+		}
 
+#else
+
+		rel = heap_open(StatisticRelationId, RowShareLock);
+
+		ScanKeyInit(&scanKey[0],
+							Anum_pg_statistic3_sta3relid,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(root->simple_rte_array[onerelid]->relid));
+		ScanKeyInit(&scanKey[1],
+							Anum_pg_statistic3_sta3expr,
+							BTEqualStrategyNumber, F_TEXTEQ,
+							exprbindatum);
+
+		scan = systable_beginscan(rel, Statistic3RelidExprInhIndexId, true,
+							SnapshotNow, 2, scanKey);
+
+		tuple = systable_getnext(scan);
+		if (HeapTupleIsValid(tuple))
+		{
+//			elog(NOTICE, "examine_variable expression found");
+			vardata->stats_type = STAT_EXPRESSION;
+			vardata->statsTuple = heap_copytuple(tuple);
+			vardata->freefunc = heap_freetuple;
+		}
+
+		systable_endscan(scan);
+
+		pfree(exprbin);
+		pfree(DatumGetPointer(exprbindatum));
+
+		relation_close(rel, RowShareLock);
+
+		if (vardata->statsTuple)
+			return;
+#endif
+#endif
 		foreach(ilist, onerel->indexlist)
 		{
 			IndexOptInfo *index = (IndexOptInfo *) lfirst(ilist);
@@ -4330,12 +4428,9 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-										   ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							int2		attnum = pos + 1;
+
+							validate_statistics(vardata, index->indexoid, &attnum, 1, false);
 						}
 						if (vardata->statsTuple)
 							break;
@@ -4383,11 +4478,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 		 * 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;
+		validate_statistics(vardata, rte->relid, &(var->varattno), 1, rte->inh);
 	}
 	else if (rte->rtekind == RTE_SUBQUERY && !rte->inh)
 	{
@@ -6514,13 +6605,7 @@ btcostestimate(PG_FUNCTION_ARGS)
 				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;
-		}
+			validate_statistics(&vardata, relid, &colnum, 1, rte->inh);
 	}
 	else
 	{
@@ -6540,13 +6625,7 @@ btcostestimate(PG_FUNCTION_ARGS)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
 		else
-		{
-			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
-												 ObjectIdGetDatum(relid),
-												 Int16GetDatum(colnum),
-												 BoolGetDatum(false));
-			vardata.freefunc = ReleaseSysCache;
-		}
+			validate_statistics(&vardata, relid, &colnum, 1, false);
 	}
 
 	if (HeapTupleIsValid(vardata.statsTuple))
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 44dab82..6391d92 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2674,6 +2674,7 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 {
 	HeapTuple	tp;
 	int32		stawidth;
+	int2vector *attnumvector = NULL;
 
 	if (get_attavgwidth_hook)
 	{
@@ -2681,10 +2682,12 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	attnumvector = buildint2vector(&attnum, 1);
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
-						 Int16GetDatum(attnum),
+						 PointerGetDatum(attnumvector),
 						 BoolGetDatum(false));
+	pfree(attnumvector);
 	if (HeapTupleIsValid(tp))
 	{
 		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
@@ -2735,7 +2738,7 @@ get_attstatsslot(HeapTuple statstuple,
 				 Datum **values, int *nvalues,
 				 float4 **numbers, int *nnumbers)
 {
-	Form_pg_statistic stats = (Form_pg_statistic) GETSTRUCT(statstuple);
+	Form_pg_statistic stats;
 	int			i,
 				j;
 	Datum		val;
@@ -2746,12 +2749,15 @@ get_attstatsslot(HeapTuple statstuple,
 	HeapTuple	typeTuple;
 	Form_pg_type typeForm;
 
+	stats = (Form_pg_statistic) GETSTRUCT(statstuple);
+
 	for (i = 0; i < STATISTIC_NUM_SLOTS; i++)
 	{
 		if ((&stats->stakind1)[i] == reqkind &&
 			(reqop == InvalidOid || (&stats->staop1)[i] == reqop))
 			break;
 	}
+
 	if (i >= STATISTIC_NUM_SLOTS)
 		return false;			/* not there */
 
@@ -2763,8 +2769,9 @@ get_attstatsslot(HeapTuple statstuple,
 		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)
-			elog(ERROR, "stavalues is null");
+			return false;
 		statarray = DatumGetArrayTypeP(val);
 
 		/*
@@ -2817,8 +2824,9 @@ get_attstatsslot(HeapTuple statstuple,
 		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)
-			elog(ERROR, "stanumbers is null");
+			return false;
 		statarray = DatumGetArrayTypeP(val);
 
 		/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index a59950e..9da6ed8 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4333,6 +4333,7 @@ write_relcache_init_file(bool shared)
 		write_item(relform, CLASS_TUPLE_SIZE, fp);
 
 		/* next, do all the attribute tuple form data entries */
+		elog(LOG, "write_relcache_init_file relname '%s' relnatts %d", NameStr(relform->relname), relform->relnatts);
 		for (i = 0; i < relform->relnatts; i++)
 		{
 			write_item(rel->rd_att->attrs[i], ATTRIBUTE_FIXED_PART_SIZE, fp);
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 78ce0b8..f7c1ba0 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -600,11 +600,11 @@ static const struct cachedesc cacheinfo[] = {
 		1024
 	},
 	{StatisticRelationId,		/* STATRELATTINH */
-		StatisticRelidAttnumInhIndexId,
+		StatisticRelidAttnumsInhIndexId,
 		3,
 		{
 			Anum_pg_statistic_starelid,
-			Anum_pg_statistic_staattnum,
+			Anum_pg_statistic_staattnums,
 			Anum_pg_statistic_stainherit,
 			0
 		},
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 99cf5b4..bb95a6d 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -119,6 +119,12 @@ static bool noclean = false;
 static bool show_setting = false;
 static char *xlog_dir = "";
 
+#ifdef DEVNULL
+#undef DEVNULL
+#endif
+#define DEVNULL	"/tmp/initdb.log"
+
+#define DEVNULLINPUT	"/dev/zero"
 
 /* internal vars */
 static const char *progname;
@@ -165,7 +171,7 @@ static char *authwarning = NULL;
  * (no quoting to worry about).
  */
 static const char *boot_options = "-F";
-static const char *backend_options = "--single -F -O -c search_path=pg_catalog -c exit_on_error=true";
+static const char *backend_options = "--single -F -O -c search_path=pg_catalog -c exit_on_error=true -c log_min_messages=warning -c log_min_error_statement=info ";
 
 
 /* path to 'initdb' binary directory */
@@ -924,7 +930,7 @@ test_config_settings(void)
 				 "< \"%s\" > \"%s\" 2>&1" SYSTEMQUOTE,
 				 backend_exec, boot_options,
 				 test_conns, test_buffs,
-				 DEVNULL, DEVNULL);
+				 DEVNULLINPUT, DEVNULL);
 		status = system(cmd);
 		if (status == 0)
 		{
@@ -958,7 +964,7 @@ test_config_settings(void)
 				 "< \"%s\" > \"%s\" 2>&1" SYSTEMQUOTE,
 				 backend_exec, boot_options,
 				 n_connections, test_buffs,
-				 DEVNULL, DEVNULL);
+				 DEVNULLINPUT, DEVNULL);
 		status = system(cmd);
 		if (status == 0)
 			break;
@@ -1273,7 +1279,7 @@ setup_auth(void)
 	fflush(stdout);
 
 	snprintf(cmd, sizeof(cmd),
-			 "\"%s\" %s template1 >%s",
+			 "\"%s\" %s template1 >>%s 2>&1",
 			 backend_exec, backend_options,
 			 DEVNULL);
 
@@ -1351,7 +1357,7 @@ get_set_pwd(void)
 	fflush(stdout);
 
 	snprintf(cmd, sizeof(cmd),
-			 "\"%s\" %s template1 >%s",
+			 "\"%s\" %s template1 >>%s 2>&1",
 			 backend_exec, backend_options,
 			 DEVNULL);
 
@@ -1451,7 +1457,7 @@ setup_depend(void)
 	fflush(stdout);
 
 	snprintf(cmd, sizeof(cmd),
-			 "\"%s\" %s template1 >%s",
+			 "\"%s\" %s template1 >>%s 2>&1",
 			 backend_exec, backend_options,
 			 DEVNULL);
 
@@ -1484,7 +1490,7 @@ setup_sysviews(void)
 	 * We use -j here to avoid backslashing stuff in system_views.sql
 	 */
 	snprintf(cmd, sizeof(cmd),
-			 "\"%s\" %s -j template1 >%s",
+			 "\"%s\" %s -j template1 >>%s 2>&1",
 			 backend_exec, backend_options,
 			 DEVNULL);
 
@@ -1515,7 +1521,7 @@ setup_description(void)
 	fflush(stdout);
 
 	snprintf(cmd, sizeof(cmd),
-			 "\"%s\" %s template1 >%s",
+			 "\"%s\" %s template1 >>%s 2>&1",
 			 backend_exec, backend_options,
 			 DEVNULL);
 
@@ -1622,7 +1628,7 @@ setup_collation(void)
 
 #if defined(HAVE_LOCALE_T) && !defined(WIN32)
 	snprintf(cmd, sizeof(cmd),
-			 "\"%s\" %s template1 >%s",
+			 "\"%s\" %s template1 >>%s 2>&1",
 			 backend_exec, backend_options,
 			 DEVNULL);
 
@@ -1761,7 +1767,7 @@ setup_conversion(void)
 	fflush(stdout);
 
 	snprintf(cmd, sizeof(cmd),
-			 "\"%s\" %s template1 >%s",
+			 "\"%s\" %s template1 >>%s 2>&1",
 			 backend_exec, backend_options,
 			 DEVNULL);
 
@@ -1799,7 +1805,7 @@ setup_dictionary(void)
 	 * We use -j here to avoid backslashing stuff
 	 */
 	snprintf(cmd, sizeof(cmd),
-			 "\"%s\" %s -j template1 >%s",
+			 "\"%s\" %s -j template1 >>%s 2>&1",
 			 backend_exec, backend_options,
 			 DEVNULL);
 
@@ -1850,7 +1856,7 @@ setup_privileges(void)
 	fflush(stdout);
 
 	snprintf(cmd, sizeof(cmd),
-			 "\"%s\" %s template1 >%s",
+			 "\"%s\" %s template1 >>%s 2>&1",
 			 backend_exec, backend_options,
 			 DEVNULL);
 
@@ -1913,7 +1919,7 @@ setup_schema(void)
 	 * We use -j here to avoid backslashing stuff in information_schema.sql
 	 */
 	snprintf(cmd, sizeof(cmd),
-			 "\"%s\" %s -j template1 >%s",
+			 "\"%s\" %s -j template1 >>%s 2>&1",
 			 backend_exec, backend_options,
 			 DEVNULL);
 
@@ -1930,7 +1936,7 @@ setup_schema(void)
 	PG_CMD_CLOSE;
 
 	snprintf(cmd, sizeof(cmd),
-			 "\"%s\" %s template1 >%s",
+			 "\"%s\" %s template1 >>%s 2>&1",
 			 backend_exec, backend_options,
 			 DEVNULL);
 
@@ -1964,7 +1970,7 @@ load_plpgsql(void)
 	fflush(stdout);
 
 	snprintf(cmd, sizeof(cmd),
-			 "\"%s\" %s template1 >%s",
+			 "\"%s\" %s template1 >>%s 2>&1",
 			 backend_exec, backend_options,
 			 DEVNULL);
 
@@ -1989,7 +1995,7 @@ vacuum_db(void)
 	fflush(stdout);
 
 	snprintf(cmd, sizeof(cmd),
-			 "\"%s\" %s template1 >%s",
+			 "\"%s\" %s template1 >>%s 2>&1",
 			 backend_exec, backend_options,
 			 DEVNULL);
 
@@ -2045,7 +2051,7 @@ make_template0(void)
 	fflush(stdout);
 
 	snprintf(cmd, sizeof(cmd),
-			 "\"%s\" %s template1 >%s",
+			 "\"%s\" %s template1 >>%s 2>&1",
 			 backend_exec, backend_options,
 			 DEVNULL);
 
@@ -2077,7 +2083,7 @@ make_postgres(void)
 	fflush(stdout);
 
 	snprintf(cmd, sizeof(cmd),
-			 "\"%s\" %s template1 >%s",
+			 "\"%s\" %s template1 >>%s 2>&1",
 			 backend_exec, backend_options,
 			 DEVNULL);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 4eee4be..42153de 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1300,7 +1300,8 @@ describeOneTableDetails(const char *schemaname,
 	if (verbose)
 	{
 		appendPQExpBuffer(&buf, ",\n  a.attstorage");
-		appendPQExpBuffer(&buf, ",\n  CASE WHEN a.attstattarget=-1 THEN NULL ELSE a.attstattarget END AS attstattarget");
+		/* FIXME: need stat target value here*/
+		appendPQExpBuffer(&buf, ",\n  NULL AS attstattarget");
 		/*
 		 * In 9.0+, we have column comments for: relations, views, composite
 		 * types, and foreign tables (c.f. CommentObject() in comment.c).
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 2055382..b0a3009 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -109,7 +109,12 @@ extern void RemoveAttributeById(Oid relid, AttrNumber attnum);
 extern void RemoveAttrDefault(Oid relid, AttrNumber attnum,
 				  DropBehavior behavior, bool complain, bool internal);
 extern void RemoveAttrDefaultById(Oid attrdefId);
-extern void RemoveStatistics(Oid relid, AttrNumber attnum);
+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 --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 450ec25..90a5c7b 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -218,8 +218,8 @@ DECLARE_INDEX(pg_shdepend_depender_index, 1232, on pg_shdepend using btree(dbid
 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_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 --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h
index 82391b0..b6d9cd6 100644
--- a/src/include/catalog/pg_amop.h
+++ b/src/include/catalog/pg_amop.h
@@ -185,6 +185,16 @@ DATA(insert (	1991   30 30 4 s	648 403 0 ));
 DATA(insert (	1991   30 30 5 s	646 403 0 ));
 
 /*
+ *	btree int2vector_ops
+ */
+
+DATA(insert (	3171   22 22 1 s   3166 403 0 ));
+DATA(insert (	3171   22 22 2 s   3169 403 0 ));
+DATA(insert (	3171   22 22 3 s	386 403 0 ));
+DATA(insert (	3171   22 22 4 s   3170 403 0 ));
+DATA(insert (	3171   22 22 5 s   3167 403 0 ));
+
+/*
  *	btree float_ops
  */
 
diff --git a/src/include/catalog/pg_amproc.h b/src/include/catalog/pg_amproc.h
index 72307de..191270c 100644
--- a/src/include/catalog/pg_amproc.h
+++ b/src/include/catalog/pg_amproc.h
@@ -133,6 +133,7 @@ DATA(insert (	2233   703 703 1  380 ));
 DATA(insert (	2234   704 704 1  381 ));
 DATA(insert (	2789   27 27 1 2794 ));
 DATA(insert (	2968   2950 2950 1 2960 ));
+DATA(insert (	3171   22 22 1 3168 ));
 DATA(insert (	3522   3500 3500 1 3514 ));
 
 
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index 45e38e4..083629c 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -48,15 +48,6 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK
 	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.
 	 */
@@ -180,28 +171,27 @@ typedef FormData_pg_attribute *Form_pg_attribute;
  * ----------------
  */
 
-#define Natts_pg_attribute				21
+#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_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
+#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 --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 1567206..4df03b4 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -132,7 +132,7 @@ typedef FormData_pg_class *Form_pg_class;
 /* 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 0 f f p r 30 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 0 f f p r 21 0 f f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 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 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
 DESCR("");
diff --git a/src/include/catalog/pg_opclass.h b/src/include/catalog/pg_opclass.h
index 96eaa60..94b74ce 100644
--- a/src/include/catalog/pg_opclass.h
+++ b/src/include/catalog/pg_opclass.h
@@ -165,6 +165,7 @@ DATA(insert (	403		bpchar_pattern_ops	PGNSP PGUID 2097 1042 f 0 ));
 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 3171   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 --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index 48ddd16..c0b19b2 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -134,6 +134,19 @@ DATA(insert OID =  98 ( "="		   PGNSP PGUID b t t	25	25	16	98 531 texteq eqsel e
 DESCR("equal");
 #define TextEqualOperator	98
 
+DATA(insert OID = 386 (  "="	   PGNSP PGUID b t t	22	22	16 386 3165 int2vectoreq eqsel eqjoinsel ));
+DESCR("equal");
+DATA(insert OID = 3165 ( "<>"	   PGNSP PGUID b f f	22	22	16 3165 386 int2vectorne neqsel neqjoinsel ));
+DESCR("not equal");
+DATA(insert OID = 3166 ( "<"	   PGNSP PGUID b f f	22	22	16 3167 3170 int2vectorlt scalarltsel scalarltjoinsel ));
+DESCR("less than");
+DATA(insert OID = 3167 ( ">"	   PGNSP PGUID b f f	22	22	16 3166 3169 int2vectorgt scalargtsel scalargtjoinsel ));
+DESCR("greater than");
+DATA(insert OID = 3169 ( "<="	   PGNSP PGUID b f f	22	22	16 3170 3167 int2vectorle scalarltsel scalarltjoinsel ));
+DESCR("less than or equal");
+DATA(insert OID = 3170 (  ">="	   PGNSP PGUID b f f	22	22	16 3169 3166 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  -	   -	 ));
@@ -151,8 +164,6 @@ DATA(insert OID = 389 (  "!!"	   PGNSP PGUID l f f	 0	20	1700  0  0 numeric_fac
 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 ));
 DESCR("equal");
diff --git a/src/include/catalog/pg_opfamily.h b/src/include/catalog/pg_opfamily.h
index 41ebccc..69b67ec 100644
--- a/src/include/catalog/pg_opfamily.h
+++ b/src/include/catalog/pg_opfamily.h
@@ -114,6 +114,7 @@ DATA(insert OID = 2099 (	403		money_ops		PGNSP PGUID ));
 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 = 3171 (	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 --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 2db8489..541f487 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -220,6 +220,13 @@ DESCR("length");
 DATA(insert OID = 1258 (  textcat		   PGNSP PGUID 12 1 0 0 0 f 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 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 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 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 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 f t f i 2 0 16 "22 22" _null_ _null_ _null_ _null_ int2vectorge _null_ _null_ _null_ ));
+DATA(insert OID = 3172 (  int2vectorgt	   PGNSP PGUID 12 1 0 0 0 f 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 f t f s 0 0 25 "" _null_ _null_ _null_ _null_ pgsql_version _null_ _null_ _null_ ));
 DESCR("PostgreSQL version string");
 
@@ -574,6 +581,8 @@ DATA(insert OID = 350 (  btint2cmp		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0
 DESCR("less-equal-greater");
 DATA(insert OID = 3129 ( btint2sortsupport PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2278 "2281" _null_ _null_ _null_ _null_ btint2sortsupport _null_ _null_ _null_ ));
 DESCR("sort support");
+DATA(insert OID = 3168 ( btint2vectorcmp   PGNSP PGUID 12 1 0 0 0 f 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 f t f i 2 0 23 "23 23" _null_ _null_ _null_ _null_ btint4cmp _null_ _null_ _null_ ));
 DESCR("less-equal-greater");
 DATA(insert OID = 3130 ( btint4sortsupport PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2278 "2281" _null_ _null_ _null_ _null_ btint4sortsupport _null_ _null_ _null_ ));
@@ -2936,7 +2945,7 @@ DESCR("xlog filename and byte offset, given an xlog location");
 DATA(insert OID = 2851 ( pg_xlogfile_name			PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 25 "25" _null_ _null_ _null_ _null_ pg_xlogfile_name _null_ _null_ _null_ ));
 DESCR("xlog filename, given an xlog location");
 
-DATA(insert OID = 3165 ( pg_xlog_location_diff		PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "25 25" _null_ _null_ _null_ _null_ pg_xlog_location_diff _null_ _null_ _null_ ));
+DATA(insert OID = 3160 ( pg_xlog_location_diff		PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 1700 "25 25" _null_ _null_ _null_ _null_ pg_xlog_location_diff _null_ _null_ _null_ ));
 DESCR("difference in bytes, given two xlog locations");
 
 DATA(insert OID = 3809 ( pg_export_snapshot		PGNSP PGUID 12 1 0 0 0 f f f f t f v 0 0 25 "" _null_ _null_ _null_ _null_ pg_export_snapshot _null_ _null_ _null_ ));
@@ -4246,7 +4255,7 @@ DATA(insert OID = 3686 (  tsmatchsel		PGNSP PGUID 12 1 0 0 0 f f f f t f s 4 0 7
 DESCR("restriction selectivity of tsvector @@ tsquery");
 DATA(insert OID = 3687 (  tsmatchjoinsel	PGNSP PGUID 12 1 0 0 0 f 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 f t f s 1 0 16 "2281" _null_ _null_ _null_ _null_ ts_typanalyze _null_ _null_ _null_ ));
+DATA(insert OID = 3688 (  ts_typanalyze		PGNSP PGUID 12 1 0 0 0 f 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 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_ ));
@@ -4515,7 +4524,7 @@ DATA(insert OID = 3881 (  range_gist_same		PGNSP PGUID 12 1 0 0 0 f f f f t f i
 DESCR("GiST support");
 DATA(insert OID = 3902 (  hash_range		 	PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "3831" _null_ _null_ _null_ _null_ hash_range _null_ _null_ _null_ ));
 DESCR("hash a range");
-DATA(insert OID = 3916 (  range_typanalyze		PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 16 "2281" _null_ _null_ _null_ _null_ range_typanalyze _null_ _null_ _null_ ));
+DATA(insert OID = 3916 (  range_typanalyze		PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 16 "2281 23" _null_ _null_ _null_ _null_ range_typanalyze _null_ _null_ _null_ ));
 DESCR("range typanalyze");
 
 DATA(insert OID = 3914 (  int4range_canonical		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 3904 "3904" _null_ _null_ _null_ _null_ int4range_canonical _null_ _null_ _null_ ));
diff --git a/src/include/catalog/pg_statistic.h b/src/include/catalog/pg_statistic.h
index 383cc01..c35198a 100644
--- a/src/include/catalog/pg_statistic.h
+++ b/src/include/catalog/pg_statistic.h
@@ -30,11 +30,24 @@
 
 CATALOG(pg_statistic,2619) BKI_WITHOUT_OIDS
 {
-	/* These fields form the unique key for the entry: */
+	/*
+	 * These fields (together with the staattnums field below in the variable 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 */
 
+	/* 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;
 
@@ -96,7 +109,15 @@ CATALOG(pg_statistic,2619) BKI_WITHOUT_OIDS
 	Oid			staop4;
 	Oid			staop5;
 
-#ifdef CATALOG_VARLEN			/* variable-length fields start here */
+#ifdef CATALOG_VARLEN                   /* variable-length fields start here */
+	/*
+	 * THE REST OF THESE ARE VARIABLE LENGTH FIELDS, and may even be absent
+	 * (NULL). They cannot be accessed as C struct entries; you have to use
+	 * 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];
 	float4		stanumbers3[1];
@@ -131,33 +152,35 @@ typedef FormData_pg_statistic *Form_pg_statistic;
  *		compiler constants for pg_statistic
  * ----------------
  */
-#define Natts_pg_statistic				26
+#define Natts_pg_statistic				28
 #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_stakind5		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_staop5		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_stanumbers5	21
-#define Anum_pg_statistic_stavalues1	22
-#define Anum_pg_statistic_stavalues2	23
-#define Anum_pg_statistic_stavalues3	24
-#define Anum_pg_statistic_stavalues4	25
-#define Anum_pg_statistic_stavalues5	26
+#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_stakind5		12
+#define Anum_pg_statistic_staop1                13
+#define Anum_pg_statistic_staop2                14
+#define Anum_pg_statistic_staop3                15
+#define Anum_pg_statistic_staop4                16
+#define Anum_pg_statistic_staop5                17
+#define Anum_pg_statistic_staattnums            18
+#define Anum_pg_statistic_stanumbers1	19
+#define Anum_pg_statistic_stanumbers2	20
+#define Anum_pg_statistic_stanumbers3	21
+#define Anum_pg_statistic_stanumbers4	22
+#define Anum_pg_statistic_stanumbers5   23
+#define Anum_pg_statistic_stavalues1    24
+#define Anum_pg_statistic_stavalues2    25
+#define Anum_pg_statistic_stavalues3    26
+#define Anum_pg_statistic_stavalues4    27
+#define Anum_pg_statistic_stavalues5    28
 
 /*
  * Currently, five statistical slot "kinds" are defined by core PostgreSQL,
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 163b2ea..f57be6a 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -91,6 +91,9 @@ extern void DefineAggregate(List *name, List *args, bool oldstyle,
 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 --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 3deee66..45bb99e 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -56,17 +56,29 @@
  * This might change in some future release.
  *----------
  */
-typedef struct VacAttrStats *VacAttrStatsP;
+typedef struct VacStats *VacStatsP;
 
-typedef Datum (*AnalyzeAttrFetchFunc) (VacAttrStatsP stats, int rownum,
+typedef Datum (*AnalyzeAttrFetchFunc) (VacStatsP stats, int rownum, AttrNumber tupattnum,
 												   bool *isNull);
 
-typedef void (*AnalyzeAttrComputeStatsFunc) (VacAttrStatsP stats,
-											 AnalyzeAttrFetchFunc fetchfunc,
-											 int samplerows,
-											 double totalrows);
+typedef void (*ComputeStatsFunc) (VacStatsP stats,
+												   int index,
+												   AnalyzeAttrFetchFunc fetchfunc,
+												   int samplerows,
+												   double totalrows);
 
-typedef struct VacAttrStats
+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
@@ -78,34 +90,36 @@ typedef struct VacAttrStats
 	 * 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 */
+	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.
 	 */
-	AnalyzeAttrComputeStatsFunc compute_stats;	/* function pointer */
+	StatsFuncStruct		statfuncs[STATISTIC_NUM_SLOTS];
 	int			minrows;		/* Minimum # of rows wanted for stats */
-	void	   *extra_data;		/* for extra type-specific data */
+	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;	/* 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];
+	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
@@ -113,22 +127,21 @@ typedef struct VacAttrStats
 	 * 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];
+	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.
 	 */
-	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;
+} VacStats;
 
 
 /* GUC parameters */
@@ -169,6 +182,6 @@ extern void lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt,
 /* in commands/analyze.c */
 extern void analyze_rel(Oid relid, VacuumStmt *vacstmt,
 			BufferAccessStrategy bstrategy);
-extern bool std_typanalyze(VacAttrStats *stats);
+extern bool std_typanalyze(VacStats *stats, int index);
 
 #endif   /* VACUUM_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 905458f..3f1ff9c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -355,6 +355,7 @@ typedef enum NodeTag
 	T_CreateExtensionStmt,
 	T_AlterExtensionStmt,
 	T_AlterExtensionContentsStmt,
+	T_ExtraStatStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ab55639..8c4340c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1162,6 +1162,20 @@ typedef enum DropBehavior
 } 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 --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4ad793a..541d8d1 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -25,4 +25,7 @@ extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 				  List **actions, Node **whereClause);
 extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt);
 
+extern ExtraStatStmt *transformExtraStatistics(ExtraStatStmt *stmt,
+						const char *queryString);
+
 #endif   /* PARSE_UTILCMD_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 9fda7ad..0a34097 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -180,6 +180,11 @@ extern Datum int2vectorout(PG_FUNCTION_ARGS);
 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);
@@ -289,6 +294,7 @@ extern void pg_lltoa(int64 ll, char *a);
  */
 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 --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
index 4529f27..0b90312 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -107,6 +107,10 @@ typedef bool (*get_index_stats_hook_type) (PlannerInfo *root,
 												  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);
 /* Functions in selfuncs.c */
 
 extern void examine_variable(PlannerInfo *root, Node *node, int varRelid,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index aaf0cca..92a141e 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1317,7 +1317,7 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem
  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 = 1) THEN s.stavalues1 WHEN (s.stakind2 = 1) THEN s.stavalues2 WHEN (s.stakind3 = 1) THEN s.stavalues3 WHEN (s.stakind4 = 1) THEN s.stavalues4 WHEN (s.stakind5 = 1) THEN s.stavalues5 ELSE NULL::anyarray END AS most_common_vals, CASE WHEN (s.stakind1 = 1) THEN s.stanumbers1 WHEN (s.stakind2 = 1) THEN s.stanumbers2 WHEN (s.stakind3 = 1) THEN s.stanumbers3 WHEN (s.stakind4 = 1) THEN s.stanumbers4 WHEN (s.stakind5 = 1) THEN s.stanumbers5 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 WHEN (s.stakind5 = 2) THEN s.stavalues5 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] WHEN (s.stakind5 = 3) THEN s.stanumbers5[1] ELSE NULL::real END AS correlation, CASE WHEN (s.stakind1 = 4) THEN s.stavalues1 WHEN (s.stakind2 = 4) THEN s.stavalues2 WHEN (s.stakind3 = 4) THEN s.stavalues3 WHEN (s.stakind4 = 4) THEN s.stavalues4 WHEN (s.stakind5 = 4) THEN s.stavalues5 ELSE NULL::anyarray END AS most_common_elems, CASE WHEN (s.stakind1 = 4) THEN s.stanumbers1 WHEN (s.stakind2 = 4) THEN s.stanumbers2 WHEN (s.stakind3 = 4) THEN s.stanumbers3 WHEN (s.stakind4 = 4) THEN s.stanumbers4 WHEN (s.stakind5 = 4) THEN s.stanumbers5 ELSE NULL::real[] END AS most_common_elem_freqs, CASE WHEN (s.stakind1 = 5) THEN s.stanumbers1 WHEN (s.stakind2 = 5) THEN s.stanumbers2 WHEN (s.stakind3 = 5) THEN s.stanumbers3 WHEN (s.stakind4 = 5) THEN s.stanumbers4 WHEN (s.stakind5 = 5) THEN s.stanumbers5 ELSE NULL::real[] END AS elem_count_histogram 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_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 = 1) THEN s.stavalues1 WHEN (s.stakind2 = 1) THEN s.stavalues2 WHEN (s.stakind3 = 1) THEN s.stavalues3 WHEN (s.stakind4 = 1) THEN s.stavalues4 WHEN (s.stakind5 = 1) THEN s.stavalues5 ELSE NULL::anyarray END AS most_common_vals, CASE WHEN (s.stakind1 = 1) THEN s.stanumbers1 WHEN (s.stakind2 = 1) THEN s.stanumbers2 WHEN (s.stakind3 = 1) THEN s.stanumbers3 WHEN (s.stakind4 = 1) THEN s.stanumbers4 WHEN (s.stakind5 = 1) THEN s.stanumbers5 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 WHEN (s.stakind5 = 2) THEN s.stavalues5 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] WHEN (s.stakind5 = 3) THEN s.stanumbers5[1] ELSE NULL::real END AS correlation, CASE WHEN (s.stakind1 = 4) THEN s.stavalues1 WHEN (s.stakind2 = 4) THEN s.stavalues2 WHEN (s.stakind3 = 4) THEN s.stavalues3 WHEN (s.stakind4 = 4) THEN s.stavalues4 WHEN (s.stakind5 = 4) THEN s.stavalues5 ELSE NULL::anyarray END AS most_common_elems, CASE WHEN (s.stakind1 = 4) THEN s.stanumbers1 WHEN (s.stakind2 = 4) THEN s.stanumbers2 WHEN (s.stakind3 = 4) THEN s.stanumbers3 WHEN (s.stakind4 = 4) THEN s.stanumbers4 WHEN (s.stakind5 = 4) THEN s.stanumbers5 ELSE NULL::real[] END AS most_common_elem_freqs, CASE WHEN (s.stakind1 = 5) THEN s.stanumbers1 WHEN (s.stakind2 = 5) THEN s.stanumbers2 WHEN (s.stakind3 = 5) THEN s.stanumbers3 WHEN (s.stakind4 = 5) THEN s.stanumbers4 WHEN (s.stakind5 = 5) THEN s.stanumbers5 ELSE NULL::real[] END AS elem_count_histogram 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.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);
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index 70eab92..c983a11 100644
--- a/src/test/regress/expected/type_sanity.out
+++ b/src/test/regress/expected/type_sanity.out
@@ -368,8 +368,9 @@ WHERE p1.typarray = p2.oid AND
 SELECT p1.oid, p1.typname, p2.oid, p2.proname
 FROM pg_type AS p1, pg_proc AS p2
 WHERE p1.typanalyze = p2.oid AND NOT
-    (p2.pronargs = 1 AND
+    (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 --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index 413b220..a8d2a8a 100644
--- a/src/test/regress/sql/type_sanity.sql
+++ b/src/test/regress/sql/type_sanity.sql
@@ -268,8 +268,9 @@ WHERE p1.typarray = p2.oid AND
 SELECT p1.oid, p1.typname, p2.oid, p2.proname
 FROM pg_type AS p1, pg_proc AS p2
 WHERE p1.typanalyze = p2.oid AND NOT
-    (p2.pronargs = 1 AND
+    (p2.pronargs = 2 AND
      p2.proargtypes[0] = 'internal'::regtype AND
+     p2.proargtypes[1] = 'int4'::regtype AND
      p2.prorettype = 'bool'::regtype AND NOT p2.proretset);
 
 -- domains inherit their base type's typanalyze