diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index e890770..5e3c06f 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -20,6 +20,7 @@
 #include "commands/copy.h"
 #include "commands/defrem.h"
 #include "commands/explain.h"
+#include "commands/vacuum.h"
 #include "foreign/fdwapi.h"
 #include "foreign/foreign.h"
 #include "miscadmin.h"
@@ -123,6 +124,9 @@ static void fileBeginForeignScan(ForeignScanState *node, int eflags);
 static TupleTableSlot *fileIterateForeignScan(ForeignScanState *node);
 static void fileReScanForeignScan(ForeignScanState *node);
 static void fileEndForeignScan(ForeignScanState *node);
+static void fileAnalyzeForeignTable(Relation onerel,
+									VacuumStmt *vacstmt,
+									int elevel);
 
 /*
  * Helper functions
@@ -136,6 +140,9 @@ static void estimate_size(PlannerInfo *root, RelOptInfo *baserel,
 static void estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
 			   FileFdwPlanState *fdw_private,
 			   Cost *startup_cost, Cost *total_cost);
+static int acquire_sample_rows(Relation onerel, HeapTuple *rows, int targrows,
+							   double *totalrows, double *totaldeadrows,
+							   BlockNumber *totalpages, int elevel);
 
 
 /*
@@ -155,6 +162,7 @@ file_fdw_handler(PG_FUNCTION_ARGS)
 	fdwroutine->IterateForeignScan = fileIterateForeignScan;
 	fdwroutine->ReScanForeignScan = fileReScanForeignScan;
 	fdwroutine->EndForeignScan = fileEndForeignScan;
+	fdwroutine->AnalyzeForeignTable = fileAnalyzeForeignTable;
 
 	PG_RETURN_POINTER(fdwroutine);
 }
@@ -645,6 +653,18 @@ fileReScanForeignScan(ForeignScanState *node)
 }
 
 /*
+ * fileAnalyzeForeignTable
+ *		Analyze foreign table
+ */
+static void
+fileAnalyzeForeignTable(Relation onerel, VacuumStmt *vacstmt, int elevel)
+{
+	do_analyze_rel(onerel, vacstmt,
+				   elevel, false,
+				   acquire_sample_rows);
+}
+
+/*
  * Estimate size of a foreign table.
  *
  * The main result is returned in baserel->rows.  We also set
@@ -657,7 +677,6 @@ estimate_size(PlannerInfo *root, RelOptInfo *baserel,
 {
 	struct stat stat_buf;
 	BlockNumber pages;
-	int			tuple_width;
 	double		ntuples;
 	double		nrows;
 
@@ -677,16 +696,31 @@ estimate_size(PlannerInfo *root, RelOptInfo *baserel,
 
 	fdw_private->pages = pages;
 
-	/*
-	 * Estimate the number of tuples in the file.  We back into this estimate
-	 * using the planner's idea of the relation width; which is bogus if not
-	 * all columns are being read, not to mention that the text representation
-	 * of a row probably isn't the same size as its internal representation.
-	 * FIXME later.
-	 */
-	tuple_width = MAXALIGN(baserel->width) + MAXALIGN(sizeof(HeapTupleHeaderData));
+	if (baserel->pages > 0)
+	{
+		double		density;
 
-	ntuples = clamp_row_est((double) stat_buf.st_size / (double) tuple_width);
+		density = baserel->tuples / (double) baserel->pages;
+
+		ntuples = clamp_row_est(density * (double) pages);
+	}
+	else
+	{
+		int			tuple_width;
+
+		/*
+		 * Estimate the number of tuples in the file.  We back into this
+		 * estimate using the planner's idea of the relation width; which is
+		 * bogus if not all columns are being read, not to mention that the text
+		 * representation of a row probably isn't the same size as its internal
+		 * representation.  FIXME later.
+		 */
+		tuple_width = MAXALIGN(baserel->width) +
+			MAXALIGN(sizeof(HeapTupleHeaderData));
+
+		ntuples = clamp_row_est((double) stat_buf.st_size /
+								(double) tuple_width);
+	}
 
 	fdw_private->ntuples = ntuples;
 
@@ -736,3 +770,156 @@ estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
 	run_cost += cpu_per_tuple * ntuples;
 	*total_cost = *startup_cost + run_cost;
 }
+
+/*
+ * acquire_sample_rows -- acquire a random sample of rows from the table
+ *
+ * Selected rows are returned in the caller-allocated array rows[], which
+ * must have at least targrows entries.
+ * The actual number of rows selected is returned as the function result.
+ * We also count the total number of rows in the file, and return it into
+ * *totalrows.  Note that *totaldeadrows is always set to 0.
+ *
+ * Note that the returned list of rows is not always in order by physical
+ * position in the file.  Therefore, correlation estimates derived later
+ * may be meaningless, but it's OK because we don't use the estimates for
+ * now.
+ */
+static int
+acquire_sample_rows(Relation onerel, HeapTuple *rows, int targrows,
+					double *totalrows, double *totaldeadrows,
+					BlockNumber *totalpages, int elevel)
+{
+	int			numrows = 0;
+	double		rowstoskip = -1; /* -1 means not set yet */
+	double		rstate;
+	HeapTuple	tuple;
+	TupleDesc	tupDesc;
+	Datum	   *values;
+	bool	   *nulls;
+	bool		found;
+	char	   *filename;
+	struct stat	stat_buf;
+	List	   *options;
+	CopyState	cstate;
+	ErrorContextCallback errcontext;
+
+	Assert(onerel);
+	Assert(targrows > 0);
+
+	tupDesc = RelationGetDescr(onerel);
+	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
+	nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
+
+	/* Fetch options of foreign table */
+	fileGetOptions(RelationGetRelid(onerel), &filename, &options);
+
+	/*
+	 * Get size of the file.
+	 */
+	if (stat(filename, &stat_buf) < 0)
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not stat file \"%s\": %m",
+						filename)));
+
+	/*
+	 * Convert size to pages for use in I/O cost estimate.
+	 */
+	*totalpages = (stat_buf.st_size + (BLCKSZ - 1)) / BLCKSZ;
+	if (*totalpages < 1)
+		*totalpages = 1;
+
+	/*
+	 * Create CopyState from FDW options.  We always acquire all columns, so
+	 * as to match the expected ScanTupleSlot signature.
+	 */
+	cstate = BeginCopyFrom(onerel, filename, NIL, options);
+
+	/* Prepare for sampling rows */
+	rstate = init_selection_state(targrows);
+
+	/* Set up callback to identify error line number. */
+	errcontext.callback = CopyFromErrorCallback;
+	errcontext.arg = (void *) cstate;
+	errcontext.previous = error_context_stack;
+	error_context_stack = &errcontext;
+
+	*totalrows = 0;
+	for (;;)
+	{
+		/*
+		 * Check for user-requested abort.
+		 */
+		CHECK_FOR_INTERRUPTS();
+
+		found = NextCopyFrom(cstate, NULL, values, nulls, NULL);
+		if (!found)
+			break;
+
+		tuple = heap_form_tuple(tupDesc, values, nulls);
+
+		/*
+		 * The first targrows sample rows are simply copied into the reservoir.
+		 * Then we start replacing tuples in the sample until we reach the end
+		 * of the relation.	 This algorithm is from Jeff Vitter's paper (see
+		 * full citation below). It works by repeatedly computing the number of
+		 * tuples to skip before selecting a tuple, which replaces a randomly
+		 * chosen element of the reservoir (current set of tuples).
+		 * At all times the reservoir is a true random sample of the tuples
+		 * we've passed over so far, so when we fall off the end of the relation
+		 * we're done.
+		 */
+		if (numrows < targrows)
+			rows[numrows++] = heap_copytuple(tuple);
+		else
+		{
+			/*
+			 * t in Vitter's paper is the number of records already
+			 * processed.  If we need to compute a new S value, we
+			 * must use the not-yet-incremented value of samplerows as
+			 * t.
+			 */
+			if (rowstoskip < 0)
+				rowstoskip = get_next_S(*totalrows, targrows, &rstate);
+
+			if (rowstoskip <= 0)
+			{
+				/*
+				 * Found a suitable tuple, so save it, replacing one
+				 * old tuple at random
+				 */
+				int k = (int) (targrows * random_fract());
+
+				Assert(k >= 0 && k < targrows);
+				heap_freetuple(rows[k]);
+				rows[k] = heap_copytuple(tuple);
+			}
+
+			rowstoskip -= 1;
+		}
+
+		*totalrows += 1;
+		heap_freetuple(tuple);
+	}
+	*totaldeadrows = 0;
+
+	/* Remove error callback. */
+	error_context_stack = errcontext.previous;
+
+	EndCopyFrom(cstate);
+
+	pfree(values);
+	pfree(nulls);
+
+	/*
+	 * Emit some interesting relation info
+	 */
+	ereport(elevel,
+			(errmsg("\"%s\": scanned, "
+					"%d rows in sample, %d total rows",
+					RelationGetRelationName(onerel),
+					numrows, (int) *totalrows)));
+
+	return numrows;
+}
diff --git a/contrib/file_fdw/input/file_fdw.source b/contrib/file_fdw/input/file_fdw.source
index 8e3d553..21b6fb4 100644
--- a/contrib/file_fdw/input/file_fdw.source
+++ b/contrib/file_fdw/input/file_fdw.source
@@ -111,6 +111,11 @@ EXECUTE st(100);
 EXECUTE st(100);
 DEALLOCATE st;
 
+-- statistics collection tests
+ANALYZE agg_csv;
+SELECT relpages, reltuples FROM pg_class WHERE relname = 'agg_csv';
+SELECT * FROM pg_stats WHERE tablename = 'agg_csv';
+
 -- tableoid
 SELECT tableoid::regclass, b FROM agg_csv;
 
diff --git a/contrib/file_fdw/output/file_fdw.source b/contrib/file_fdw/output/file_fdw.source
index 84f0750..c2daafe 100644
--- a/contrib/file_fdw/output/file_fdw.source
+++ b/contrib/file_fdw/output/file_fdw.source
@@ -174,6 +174,21 @@ EXECUTE st(100);
 (1 row)
 
 DEALLOCATE st;
+-- statistics collection tests
+ANALYZE agg_csv;
+SELECT relpages, reltuples FROM pg_class WHERE relname = 'agg_csv';
+ relpages | reltuples 
+----------+-----------
+        1 |         3
+(1 row)
+
+SELECT * FROM pg_stats WHERE tablename = 'agg_csv';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |    histogram_bounds     | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-------------------------+-------------+-------------------+------------------------+----------------------
+ public     | agg_csv   | a       | f         |         0 |         2 |         -1 |                  |                   | {0,42,100}              |        -0.5 |                   |                        | 
+ public     | agg_csv   | b       | f         |         0 |         4 |         -1 |                  |                   | {0.09561,99.097,324.78} |         0.5 |                   |                        | 
+(2 rows)
+
 -- tableoid
 SELECT tableoid::regclass, b FROM agg_csv;
  tableoid |    b    
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index f7bf3d8..0a70b47 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -299,6 +299,30 @@ EndForeignScan (ForeignScanState *node);
 
     <para>
 <programlisting>
+void
+AnalyzeForeignTable (Relation onerel,
+                     VacuumStmt *vacstmt, 
+                     int elevel);
+</programlisting>
+
+     Collect statistics on a foreign table and store the results in the
+     pg_class and pg_statistics system catalogs.
+     This is optional, and if implemented, called when <command>ANALYZE
+     </> command is run.
+     The statistics are used by the query planner in order to make good
+     choices of query plans.
+     The function can be implemented by writing a sampling function that
+     acquires a random sample of rows from an external data source and
+     then by calling <function>do_analyze_rel</>, where you should pass
+     the sampling function as an argument.
+     Or the function can be directly implemented to get the statistics
+     from an external data source, transform it if neccesary, and store
+     it in the pg_class and pg_statistics system catalogs.
+     The function must be set to NULL if it isn't implemented.
+    </para>
+
+    <para>
+<programlisting>
 ForeignDataWrapper *
 GetForeignDataWrapper(Oid fdwid);
 </programlisting>
diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index 93c3ff5..54d0838 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -284,6 +284,10 @@
     <command>ANALYZE</> strictly as a function of the number of rows
     inserted or updated; it has no knowledge of whether that will lead
     to meaningful statistical changes.
+    Note that the autovacuum daemon does not issue <command>ANALYZE</>
+    commands on foreign tables.  It is recommended to run manually-managed
+    <command>ANALYZE</> commands as needed, which typically are executed
+    according to a schedule by cron or Task Scheduler scripts.
    </para>
 
    <para>
diff --git a/doc/src/sgml/ref/alter_foreign_table.sgml b/doc/src/sgml/ref/alter_foreign_table.sgml
index c4cdaa8..3819007 100644
--- a/doc/src/sgml/ref/alter_foreign_table.sgml
+++ b/doc/src/sgml/ref/alter_foreign_table.sgml
@@ -36,6 +36,9 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
     DROP [ COLUMN ] [ IF EXISTS ] <replaceable class="PARAMETER">column</replaceable> [ RESTRICT | CASCADE ]
     ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> [ SET DATA ] TYPE <replaceable class="PARAMETER">type</replaceable>
     ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> { SET | DROP } NOT NULL
+    ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> SET STATISTICS <replaceable class="PARAMETER">integer</replaceable>
+    ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
+    ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
     ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
     OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
     OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
@@ -104,6 +107,45 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
    </varlistentry>
 
    <varlistentry>
+    <term><literal>SET STATISTICS</literal></term>
+    <listitem>
+     <para>
+      This form sets the per-column statistics-gathering target for
+      subsequent <xref linkend="sql-analyze"> operations.
+      The target can be set in the range 0 to 10000; alternatively,
+      set it to -1 to revert to using the system default statistics
+      target (<xref linkend="guc-default-statistics-target">).
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )</literal></term>
+    <term><literal>RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )</literal></term>
+    <listitem>
+     <para>
+      This form sets or resets a per-attribute option.  Currently, the only
+      defined per-attribute option is <literal>n_distinct</>, which overrides
+      the number-of-distinct-values estimates made by subsequent
+      <xref linkend="sql-analyze"> operations.  When set to a positive value,
+      <command>ANALYZE</> will assume that the column contains exactly the
+      specified number of distinct nonnull values.  When set to a negative
+      value, which must be greater than or equal to -1, <command>ANALYZE</>
+      will assume that the number of distinct nonnull values in the column is
+      linear in the size of the foreign table; the exact count is to be computed
+      by multiplying the estimated foreign table size by the absolute value of
+      the given number.  For example, a value of -1 implies that all values in
+      the column are distinct, while a value of -0.5 implies that each value
+      appears twice on the average.  This can be useful when the size of the
+      foreign table changes over time, since the multiplication by the number of
+      rows in the foreign table is not performed until query planning time.
+      Specify a value of 0 to revert to estimating the number of distinct values
+      normally.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>OWNER</literal></term>
     <listitem>
      <para>
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 8c9057b..524a1c9 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -39,9 +39,11 @@ ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table</replaceable> [ ( <re
 
   <para>
    With no parameter, <command>ANALYZE</command> examines every table in the
-   current database.  With a parameter, <command>ANALYZE</command> examines
-   only that table.  It is further possible to give a list of column names,
-   in which case only the statistics for those columns are collected.
+   current database except for foreign tables.  With a parameter, <command>
+   ANALYZE</command> examines only that table.  For a foreign table, it is
+   necessary to specify the name of that table.  It is further possible to 
+   give a list of column names, in which case only the statistics for those
+   columns are collected.
   </para>
  </refsect1>
 
@@ -63,7 +65,8 @@ ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table</replaceable> [ ( <re
     <listitem>
      <para>
       The name (possibly schema-qualified) of a specific table to
-      analyze. Defaults to all tables in the current database.
+      analyze. Defaults to all tables in the current database except
+      for foreign tables.
      </para>
     </listitem>
    </varlistentry>
@@ -137,7 +140,9 @@ ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table</replaceable> [ ( <re
    In rare situations, this non-determinism will cause the planner's
    choices of query plans to change after <command>ANALYZE</command> is run.
    To avoid this, raise the amount of statistics collected by
-   <command>ANALYZE</command>, as described below.
+   <command>ANALYZE</command>, as described below.  Note that the time
+   needed to analyze on foreign tables depends on the implementation of
+   the foreign data wrapper via which such tables are attached.
   </para>
 
   <para>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 9cd6e67..442ee2f 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -23,6 +23,7 @@
 #include "access/xact.h"
 #include "catalog/index.h"
 #include "catalog/indexing.h"
+#include "catalog/pg_class.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
@@ -30,6 +31,8 @@
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
+#include "foreign/foreign.h"
+#include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parse_oper.h"
@@ -78,14 +81,11 @@ typedef struct AnlIndexData
 int			default_statistics_target = 100;
 
 /* A few variables that don't seem worth passing around as parameters */
-static int	elevel = -1;
-
 static MemoryContext anl_context = NULL;
 
 static BufferAccessStrategy vac_strategy;
 
 
-static void do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh);
 static void BlockSampler_Init(BlockSampler bs, BlockNumber nblocks,
 				  int samplesize);
 static bool BlockSampler_HasMore(BlockSampler bs);
@@ -96,15 +96,15 @@ static void compute_index_stats(Relation onerel, double totalrows,
 					MemoryContext col_context);
 static VacAttrStats *examine_attribute(Relation onerel, int attnum,
 				  Node *index_expr);
-static int acquire_sample_rows(Relation onerel, HeapTuple *rows,
-					int targrows, double *totalrows, double *totaldeadrows);
-static double random_fract(void);
-static double init_selection_state(int n);
-static double get_next_S(double t, int n, double *stateptr);
-static int	compare_rows(const void *a, const void *b);
+static int acquire_sample_rows(Relation onerel,
+							   HeapTuple *rows, int targrows,
+							   double *totalrows, double *totaldeadrows,
+							   BlockNumber *totalpages, int elevel);
 static int acquire_inherited_sample_rows(Relation onerel,
 							  HeapTuple *rows, int targrows,
-							  double *totalrows, double *totaldeadrows);
+							  double *totalrows, double *totaldeadrows,
+							  BlockNumber *totalpages, int elevel);
+static int	compare_rows(const void *a, const void *b);
 static void update_attstats(Oid relid, bool inh,
 				int natts, VacAttrStats **vacattrstats);
 static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
@@ -117,7 +117,9 @@ static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
 void
 analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
 {
+	int			elevel;
 	Relation	onerel;
+	FdwRoutine *fdwroutine;
 
 	/* Set up static variables */
 	if (vacstmt->options & VACOPT_VERBOSE)
@@ -182,10 +184,12 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
 	}
 
 	/*
-	 * Check that it's a plain table; we used to do this in get_rel_oids() but
-	 * seems safer to check after we've locked the relation.
+	 * Check that it's a plain table or foreign table; we used to do this
+	 * in get_rel_oids() but seems safer to check after we've locked the
+	 * relation.
 	 */
-	if (onerel->rd_rel->relkind != RELKIND_RELATION)
+	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+		onerel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 	{
 		/* No need for a WARNING if we already complained during VACUUM */
 		if (!(vacstmt->options & VACOPT_VACUUM))
@@ -209,7 +213,9 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
 	}
 
 	/*
-	 * We can ANALYZE any table except pg_statistic. See update_attstats
+	 * We can ANALYZE any table except pg_statistic.  See update_attstats.
+	 * A foreign table can be ANALYZEed if the AnalyzeForeignTable callback
+	 * routine is provided.
 	 */
 	if (RelationGetRelid(onerel) == StatisticRelationId)
 	{
@@ -217,6 +223,20 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
 		return;
 	}
 
+	if (onerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+	{
+ 		fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(onerel));
+
+		if (fdwroutine->AnalyzeForeignTable == NULL)
+		{
+			ereport(WARNING,
+					(errmsg("skipping \"%s\" --- underlying foreign-data wrapper cannot analyze it",
+							RelationGetRelationName(onerel))));
+			relation_close(onerel, ShareUpdateExclusiveLock);
+			return;
+		}
+	}
+
 	/*
 	 * OK, let's do it.  First let other backends know I'm in ANALYZE.
 	 */
@@ -227,13 +247,32 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
 	/*
 	 * Do the normal non-recursive ANALYZE.
 	 */
-	do_analyze_rel(onerel, vacstmt, false);
+	ereport(elevel,
+			(errmsg("analyzing \"%s.%s\"",
+					get_namespace_name(RelationGetNamespace(onerel)),
+					RelationGetRelationName(onerel))));
+
+	if (onerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+		fdwroutine->AnalyzeForeignTable(onerel, vacstmt, elevel);
+	else
+		do_analyze_rel(onerel, vacstmt, elevel, false, acquire_sample_rows);
 
 	/*
 	 * If there are child tables, do recursive ANALYZE.
 	 */
 	if (onerel->rd_rel->relhassubclass)
-		do_analyze_rel(onerel, vacstmt, true);
+	{
+		Assert(onerel->rd_rel->relkind != RELKIND_FOREIGN_TABLE);
+
+		ereport(elevel,
+				(errmsg("analyzing \"%s.%s\" inheritance tree",
+						get_namespace_name(RelationGetNamespace(onerel)),
+						RelationGetRelationName(onerel))));
+
+		do_analyze_rel(onerel, vacstmt, elevel, true,
+					   acquire_inherited_sample_rows);
+	}
+
 
 	/*
 	 * Close source relation now, but keep lock so that no one deletes it
@@ -255,8 +294,9 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
 /*
  *	do_analyze_rel() -- analyze one relation, recursively or not
  */
-static void
-do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh)
+void
+do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, int elevel, 
+			   bool inh, AcquireSampleRowFunc acquirefunc)
 {
 	int			attr_cnt,
 				tcnt,
@@ -271,6 +311,7 @@ do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh)
 				numrows;
 	double		totalrows,
 				totaldeadrows;
+	BlockNumber totalpages;
 	HeapTuple  *rows;
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
@@ -279,17 +320,6 @@ do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh)
 	int			save_sec_context;
 	int			save_nestlevel;
 
-	if (inh)
-		ereport(elevel,
-				(errmsg("analyzing \"%s.%s\" inheritance tree",
-						get_namespace_name(RelationGetNamespace(onerel)),
-						RelationGetRelationName(onerel))));
-	else
-		ereport(elevel,
-				(errmsg("analyzing \"%s.%s\"",
-						get_namespace_name(RelationGetNamespace(onerel)),
-						RelationGetRelationName(onerel))));
-
 	/*
 	 * Set up a working context so that we can easily free whatever junk gets
 	 * created.
@@ -447,11 +477,13 @@ do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh)
 	 */
 	rows = (HeapTuple *) palloc(targrows * sizeof(HeapTuple));
 	if (inh)
-		numrows = acquire_inherited_sample_rows(onerel, rows, targrows,
-												&totalrows, &totaldeadrows);
+		numrows = acquirefunc(onerel, rows, targrows,
+							  &totalrows, &totaldeadrows,
+							  NULL, elevel);
 	else
-		numrows = acquire_sample_rows(onerel, rows, targrows,
-									  &totalrows, &totaldeadrows);
+		numrows = acquirefunc(onerel, rows, targrows,
+							  &totalrows, &totaldeadrows,
+							  &totalpages, elevel);
 
 	/*
 	 * Compute the statistics.	Temporary results during the calculations for
@@ -532,7 +564,7 @@ do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh)
 	 */
 	if (!inh)
 		vac_update_relstats(onerel,
-							RelationGetNumberOfBlocks(onerel),
+							totalpages,
 							totalrows,
 							visibilitymap_count(onerel),
 							hasindex,
@@ -1015,7 +1047,8 @@ BlockSampler_Next(BlockSampler bs)
  */
 static int
 acquire_sample_rows(Relation onerel, HeapTuple *rows, int targrows,
-					double *totalrows, double *totaldeadrows)
+					double *totalrows, double *totaldeadrows,
+					BlockNumber *totalpages, int elevel)
 {
 	int			numrows = 0;	/* # rows now in reservoir */
 	double		samplerows = 0; /* total # rows collected */
@@ -1030,6 +1063,8 @@ acquire_sample_rows(Relation onerel, HeapTuple *rows, int targrows,
 	Assert(targrows > 0);
 
 	totalblocks = RelationGetNumberOfBlocks(onerel);
+	if (totalpages)
+		*totalpages = totalblocks;
 
 	/* Need a cutoff xmin for HeapTupleSatisfiesVacuum */
 	OldestXmin = GetOldestXmin(onerel->rd_rel->relisshared, true);
@@ -1252,7 +1287,7 @@ acquire_sample_rows(Relation onerel, HeapTuple *rows, int targrows,
 }
 
 /* Select a random value R uniformly distributed in (0 - 1) */
-static double
+double
 random_fract(void)
 {
 	return ((double) random() + 1) / ((double) MAX_RANDOM_VALUE + 2);
@@ -1272,14 +1307,14 @@ random_fract(void)
  * determines the number of records to skip before the next record is
  * processed.
  */
-static double
+double
 init_selection_state(int n)
 {
 	/* Initial value of W (for use when Algorithm Z is first applied) */
 	return exp(-log(random_fract()) / n);
 }
 
-static double
+double
 get_next_S(double t, int n, double *stateptr)
 {
 	double		S;
@@ -1395,7 +1430,8 @@ compare_rows(const void *a, const void *b)
  */
 static int
 acquire_inherited_sample_rows(Relation onerel, HeapTuple *rows, int targrows,
-							  double *totalrows, double *totaldeadrows)
+							  double *totalrows, double *totaldeadrows,
+							  BlockNumber *totalpages, int elevel)
 {
 	List	   *tableOIDs;
 	Relation   *rels;
@@ -1458,6 +1494,8 @@ acquire_inherited_sample_rows(Relation onerel, HeapTuple *rows, int targrows,
 		totalblocks += relblocks[nrels];
 		nrels++;
 	}
+	if (totalpages)
+	  *totalpages = totalblocks;
 
 	/*
 	 * Now sample rows from each relation, proportionally to its fraction of
@@ -1491,7 +1529,9 @@ acquire_inherited_sample_rows(Relation onerel, HeapTuple *rows, int targrows,
 												rows + numrows,
 												childtargrows,
 												&trows,
-												&tdrows);
+												&tdrows,
+												NULL,
+												elevel);
 
 				/* We may need to convert from child's rowtype to parent's */
 				if (childrows > 0 &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 9853686..3031496 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -320,6 +320,8 @@ static void ATPrepSetStatistics(Relation rel, const char *colName,
 					Node *newValue, LOCKMODE lockmode);
 static void ATExecSetStatistics(Relation rel, const char *colName,
 					Node *newValue, LOCKMODE lockmode);
+static void ATPrepSetOptions(Relation rel, const char *colName,
+				 Node *options, LOCKMODE lockmode);
 static void ATExecSetOptions(Relation rel, const char *colName,
 				 Node *options, bool isReset, LOCKMODE lockmode);
 static void ATExecSetStorage(Relation rel, const char *colName,
@@ -3021,7 +3023,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			break;
 		case AT_SetOptions:		/* ALTER COLUMN SET ( options ) */
 		case AT_ResetOptions:	/* ALTER COLUMN RESET ( options ) */
-			ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX);
+			ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX | ATT_FOREIGN_TABLE);
+			ATPrepSetOptions(rel, cmd->name, cmd->def, lockmode);
 			/* This command never recurses */
 			pass = AT_PASS_MISC;
 			break;
@@ -4999,10 +5002,11 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	 * allowSystemTableMods to be turned on.
 	 */
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
-		rel->rd_rel->relkind != RELKIND_INDEX)
+		rel->rd_rel->relkind != RELKIND_INDEX &&
+		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table or index",
+				 errmsg("\"%s\" is not a table, index or foreign table",
 						RelationGetRelationName(rel))));
 
 	/* Permissions checks */
@@ -5071,6 +5075,26 @@ ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 }
 
 static void
+ATPrepSetOptions(Relation rel, const char *colName, Node *options,
+				 LOCKMODE lockmode)
+{
+	if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		ListCell   *cell;
+
+		foreach(cell, (List *) options)
+		{
+			DefElem    *def = (DefElem *) lfirst(cell);
+
+			if (pg_strcasecmp(def->defname, "n_distinct_inherited") == 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot set \"n_distinct_inherited\" for foreign tables")));
+		}
+	}
+}
+
+static void
 ATExecSetOptions(Relation rel, const char *colName, Node *options,
 				 bool isReset, LOCKMODE lockmode)
 {
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index dc2248b..b3d2078 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1104,7 +1104,7 @@ describeOneTableDetails(const char *schemaname,
 	bool		printTableInitialized = false;
 	int			i;
 	char	   *view_def = NULL;
-	char	   *headers[6];
+	char	   *headers[7];
 	char	  **seq_values = NULL;
 	char	  **modifiers = NULL;
 	char	  **ptr;
@@ -1395,7 +1395,7 @@ describeOneTableDetails(const char *schemaname,
 	if (verbose)
 	{
 		headers[cols++] = gettext_noop("Storage");
-		if (tableinfo.relkind == 'r')
+		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'f')
 			headers[cols++] = gettext_noop("Stats target");
 		/* Column comments, if the relkind supports this feature. */
 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
@@ -1498,7 +1498,7 @@ describeOneTableDetails(const char *schemaname,
 							  false, false);
 
 			/* Statistics target, if the relkind supports this feature */
-			if (tableinfo.relkind == 'r')
+			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'f')
 			{
 				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
 								  false, false);
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 975d655..d113adf 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -409,6 +409,21 @@ static const SchemaQuery Query_for_list_of_tsvf = {
 	NULL
 };
 
+static const SchemaQuery Query_for_list_of_tf = {
+	/* catname */
+	"pg_catalog.pg_class c",
+	/* selcondition */
+	"c.relkind IN ('r', 'f')",
+	/* viscondition */
+	"pg_catalog.pg_table_is_visible(c.oid)",
+	/* namespace */
+	"c.relnamespace",
+	/* result */
+	"pg_catalog.quote_ident(c.relname)",
+	/* qualresult */
+	NULL
+};
+
 static const SchemaQuery Query_for_list_of_views = {
 	/* catname */
 	"pg_catalog.pg_class c",
@@ -2833,7 +2848,7 @@ psql_completion(char *text, int start, int end)
 /* ANALYZE */
 	/* If the previous word is ANALYZE, produce list of tables */
 	else if (pg_strcasecmp(prev_wd, "ANALYZE") == 0)
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tf, NULL);
 
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 3deee66..f6a3c59 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -58,6 +58,14 @@
  */
 typedef struct VacAttrStats *VacAttrStatsP;
 
+typedef int (*AcquireSampleRowFunc) (Relation onerel,
+									 HeapTuple *rows,
+									 int targrows,
+									 double *totalrows,
+									 double *totaldeadrows,
+									 BlockNumber *totalpages,
+									 int elevel);
+
 typedef Datum (*AnalyzeAttrFetchFunc) (VacAttrStatsP stats, int rownum,
 												   bool *isNull);
 
@@ -139,6 +147,14 @@ extern int	vacuum_freeze_table_age;
 
 
 /* in commands/vacuum.c */
+extern void analyze_rel(Oid relid, VacuumStmt *vacstmt,
+						BufferAccessStrategy bstrategy);
+extern void do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, int elevel, 
+						   bool inh, AcquireSampleRowFunc acquirefunc);
+extern double random_fract(void);
+extern double init_selection_state(int n);
+extern double get_next_S(double t, int n, double *stateptr);
+
 extern void vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
 	   BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel);
 extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 854f177..fbb5b0b 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -12,8 +12,10 @@
 #ifndef FDWAPI_H
 #define FDWAPI_H
 
+#include "foreign/foreign.h"
 #include "nodes/execnodes.h"
 #include "nodes/relation.h"
+#include "utils/rel.h"
 
 /* To avoid including explain.h here, reference ExplainState thus: */
 struct ExplainState;
@@ -50,6 +52,9 @@ typedef void (*ReScanForeignScan_function) (ForeignScanState *node);
 
 typedef void (*EndForeignScan_function) (ForeignScanState *node);
 
+typedef void (*AnalyzeForeignTable_function) (Relation relation,
+											  VacuumStmt *vacstmt,
+											  int elevel);
 
 /*
  * FdwRoutine is the struct returned by a foreign-data wrapper's handler
@@ -64,6 +69,11 @@ typedef struct FdwRoutine
 {
 	NodeTag		type;
 
+	/*
+	 * These handlers are required to execute simple scans on a foreign
+	 * table.  If any of them was set to NULL, scans on a foreign table
+	 * managed by FDW would fail.
+	 */
 	GetForeignRelSize_function GetForeignRelSize;
 	GetForeignPaths_function GetForeignPaths;
 	GetForeignPlan_function GetForeignPlan;
@@ -72,6 +82,12 @@ typedef struct FdwRoutine
 	IterateForeignScan_function IterateForeignScan;
 	ReScanForeignScan_function ReScanForeignScan;
 	EndForeignScan_function EndForeignScan;
+
+	/*
+	 * Handlers below are optional.  You can set any of them to NULL to
+	 * tell PostgreSQL that FDW doesn't have the capability.
+	 */
+	AnalyzeForeignTable_function AnalyzeForeignTable;
 } FdwRoutine;
 
 
diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out
index ba86883..f1379a6 100644
--- a/src/test/regress/expected/foreign_data.out
+++ b/src/test/regress/expected/foreign_data.out
@@ -679,12 +679,12 @@ CREATE FOREIGN TABLE ft1 (
 COMMENT ON FOREIGN TABLE ft1 IS 'ft1';
 COMMENT ON COLUMN ft1.c1 IS 'ft1.c1';
 \d+ ft1
-                               Foreign table "public.ft1"
- Column |  Type   | Modifiers |          FDW Options           | Storage  | Description 
---------+---------+-----------+--------------------------------+----------+-------------
- c1     | integer | not null  | ("param 1" 'val1')             | plain    | ft1.c1
- c2     | text    |           | (param2 'val2', param3 'val3') | extended | 
- c3     | date    |           |                                | plain    | 
+                                      Foreign table "public.ft1"
+ Column |  Type   | Modifiers |          FDW Options           | Storage  | Stats target | Description 
+--------+---------+-----------+--------------------------------+----------+--------------+-------------
+ c1     | integer | not null  | ("param 1" 'val1')             | plain    |              | ft1.c1
+ c2     | text    |           | (param2 'val2', param3 'val3') | extended |              | 
+ c3     | date    |           |                                | plain    |              | 
 Server: s0
 FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
 Has OIDs: no
@@ -730,19 +730,24 @@ ERROR:  cannot alter system column "xmin"
 ALTER FOREIGN TABLE ft1 ALTER COLUMN c7 OPTIONS (ADD p1 'v1', ADD p2 'v2'),
                         ALTER COLUMN c8 OPTIONS (ADD p1 'v1', ADD p2 'v2');
 ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 OPTIONS (SET p2 'V2', DROP p1);
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 SET STATISTICS 10000;
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 SET (n_distinct = 100);
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 SET (n_distinct_inherited = 100); -- ERROR
+ERROR:  cannot set "n_distinct_inherited" for foreign tables
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET STATISTICS -1;
 \d+ ft1
-                               Foreign table "public.ft1"
- Column |  Type   | Modifiers |          FDW Options           | Storage  | Description 
---------+---------+-----------+--------------------------------+----------+-------------
- c1     | integer | not null  | ("param 1" 'val1')             | plain    | 
- c2     | text    |           | (param2 'val2', param3 'val3') | extended | 
- c3     | date    |           |                                | plain    | 
- c4     | integer |           |                                | plain    | 
- c6     | integer | not null  |                                | plain    | 
- c7     | integer |           | (p1 'v1', p2 'v2')             | plain    | 
- c8     | text    |           | (p2 'V2')                      | extended | 
- c9     | integer |           |                                | plain    | 
- c10    | integer |           | (p1 'v1')                      | plain    | 
+                                      Foreign table "public.ft1"
+ Column |  Type   | Modifiers |          FDW Options           | Storage  | Stats target | Description 
+--------+---------+-----------+--------------------------------+----------+--------------+-------------
+ c1     | integer | not null  | ("param 1" 'val1')             | plain    | 10000        | 
+ c2     | text    |           | (param2 'val2', param3 'val3') | extended |              | 
+ c3     | date    |           |                                | plain    |              | 
+ c4     | integer |           |                                | plain    |              | 
+ c6     | integer | not null  |                                | plain    |              | 
+ c7     | integer |           | (p1 'v1', p2 'v2')             | plain    |              | 
+ c8     | text    |           | (p2 'V2')                      | extended |              | 
+ c9     | integer |           |                                | plain    |              | 
+ c10    | integer |           | (p1 'v1')                      | plain    |              | 
 Server: s0
 FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
 Has OIDs: no
diff --git a/src/test/regress/sql/foreign_data.sql b/src/test/regress/sql/foreign_data.sql
index 0c95672..03b5680 100644
--- a/src/test/regress/sql/foreign_data.sql
+++ b/src/test/regress/sql/foreign_data.sql
@@ -307,6 +307,10 @@ ALTER FOREIGN TABLE ft1 ALTER COLUMN xmin OPTIONS (ADD p1 'v1'); -- ERROR
 ALTER FOREIGN TABLE ft1 ALTER COLUMN c7 OPTIONS (ADD p1 'v1', ADD p2 'v2'),
                         ALTER COLUMN c8 OPTIONS (ADD p1 'v1', ADD p2 'v2');
 ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 OPTIONS (SET p2 'V2', DROP p1);
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 SET STATISTICS 10000;
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 SET (n_distinct = 100);
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 SET (n_distinct_inherited = 100); -- ERROR
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET STATISTICS -1;
 \d+ ft1
 -- can't change the column type if it's used elsewhere
 CREATE TABLE use_ft1_column_type (x ft1);
