pgsql_fdw_analyze_v1.patch

text/plain

Filename: pgsql_fdw_analyze_v1.patch
Type: text/plain
Part: 2
Message: Re: pgsql_fdw, FDW for PostgreSQL server

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+
contrib/pgsql_fdw/expected/pgsql_fdw.out 0 0
contrib/pgsql_fdw/pgsql_fdw--1.0.sql 0 0
contrib/pgsql_fdw/pgsql_fdw.c 0 0
contrib/pgsql_fdw/sql/pgsql_fdw.sql 0 0
doc/src/sgml/pgsql-fdw.sgml 0 0
src/backend/commands/analyze.c 0 0
src/include/commands/vacuum.h 0 0
diff --git a/contrib/pgsql_fdw/expected/pgsql_fdw.out b/contrib/pgsql_fdw/expected/pgsql_fdw.out
index 5560931..bd6aa73 100644
*** a/contrib/pgsql_fdw/expected/pgsql_fdw.out
--- b/contrib/pgsql_fdw/expected/pgsql_fdw.out
*************** INSERT INTO "S 1"."T 2"
*** 75,80 ****
--- 75,81 ----
  	       'AAA' || to_char(id, 'FM000')
  	FROM generate_series(1, 100) id;
  COMMIT;
+ ANALYZE "S 1"."T 1";
  -- ===================================================================
  -- tests for pgsql_fdw_validator
  -- ===================================================================
*************** SELECT srvname, usename FROM pgsql_fdw_c
*** 584,589 ****
--- 585,606 ----
  (0 rows)
  
  -- ===================================================================
+ -- statistics management
+ -- ===================================================================
+ DELETE FROM pg_statistic WHERE starelid = '"S 1"."T 1"'::regclass AND staattnum IN (1, 3);
+ SELECT pgsql_fdw_analyze('ft1');
+  pgsql_fdw_analyze 
+ -------------------
+                  5
+ (1 row)
+ 
+ SELECT count(*) FROM pg_statistic WHERE starelid = 'ft1'::regclass;
+  count 
+ -------
+      5
+ (1 row)
+ 
+ -- ===================================================================
  -- subtransaction
  -- ===================================================================
  BEGIN;
diff --git a/contrib/pgsql_fdw/pgsql_fdw--1.0.sql b/contrib/pgsql_fdw/pgsql_fdw--1.0.sql
index b0ea2b2..033cd6f 100644
*** a/contrib/pgsql_fdw/pgsql_fdw--1.0.sql
--- b/contrib/pgsql_fdw/pgsql_fdw--1.0.sql
*************** SELECT c.srvid srvid,
*** 37,39 ****
--- 37,45 ----
             JOIN pg_catalog.pg_foreign_server s ON (s.oid = c.srvid);
  GRANT SELECT ON pgsql_fdw_connections TO public;
  
+ /* statistics management function */
+ CREATE FUNCTION pgsql_fdw_analyze(regclass)
+ RETURNS int
+ AS 'MODULE_PATHNAME'
+ LANGUAGE C;
+ 
diff --git a/contrib/pgsql_fdw/pgsql_fdw.c b/contrib/pgsql_fdw/pgsql_fdw.c
index 4916a45..06a3603 100644
*** a/contrib/pgsql_fdw/pgsql_fdw.c
--- b/contrib/pgsql_fdw/pgsql_fdw.c
***************
*** 13,32 ****
  #include "postgres.h"
  #include "fmgr.h"
  
  #include "catalog/pg_foreign_server.h"
  #include "catalog/pg_foreign_table.h"
  #include "commands/defrem.h"
  #include "commands/explain.h"
! #include "foreign/foreign.h"
  #include "funcapi.h"
  #include "miscadmin.h"
  #include "optimizer/cost.h"
  #include "optimizer/pathnode.h"
  #include "optimizer/planmain.h"
  #include "optimizer/restrictinfo.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/rel.h"
  
  #include "pgsql_fdw.h"
  #include "connection.h"
--- 13,37 ----
  #include "postgres.h"
  #include "fmgr.h"
  
+ #include "access/transam.h"
  #include "catalog/pg_foreign_server.h"
  #include "catalog/pg_foreign_table.h"
  #include "commands/defrem.h"
  #include "commands/explain.h"
! #include "commands/vacuum.h"
  #include "funcapi.h"
  #include "miscadmin.h"
  #include "optimizer/cost.h"
  #include "optimizer/pathnode.h"
  #include "optimizer/planmain.h"
  #include "optimizer/restrictinfo.h"
+ #include "parser/parse_type.h"
+ #include "utils/array.h"
+ #include "utils/builtins.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/rel.h"
+ #include "utils/syscache.h"
  
  #include "pgsql_fdw.h"
  #include "connection.h"
*************** typedef struct PgsqlFdwExecutionState
*** 125,130 ****
--- 130,137 ----
   */
  extern Datum pgsql_fdw_handler(PG_FUNCTION_ARGS);
  PG_FUNCTION_INFO_V1(pgsql_fdw_handler);
+ extern Datum pgsql_fdw_analyze(PG_FUNCTION_ARGS);
+ PG_FUNCTION_INFO_V1(pgsql_fdw_analyze);
  
  /*
   * FDW callback routines
*************** static void adjust_costs(double rows, in
*** 155,160 ****
--- 162,168 ----
  static void execute_query(ForeignScanState *node);
  static PGresult *fetch_result(ForeignScanState *node);
  static void store_result(ForeignScanState *node, PGresult *res);
+ static void store_remote_stats(PGresult *res, VacAttrStats *stats);
  
  /* Exported functions, but not written in pgsql_fdw.h. */
  void _PG_init(void);
*************** pgsql_fdw_handler(PG_FUNCTION_ARGS)
*** 211,216 ****
--- 219,229 ----
  /*
   * pgsqlGetForeignRelSize
   *		Estimate # of rows and width of the result of the scan
+  *
+  * Since we would have enough statistics about foreign data on local side by
+  * calling pgsql_fdw_analyze() for foreign table of pgsql_fdw, we can get good
+  * estimate of rows and width of this scan by calling
+  * set_baserel_size_estimates().
   */
  static void
  pgsqlGetForeignRelSize(PlannerInfo *root,
*************** store_result(ForeignScanState *node, PGr
*** 922,924 ****
--- 935,1523 ----
  	tuplestore_donestoring(festate->tuples);
  }
  
+ /*
+  * Update statistics of a foreign table managed by pgsql_fdw by copying remote
+  * statistics into local pg_statistic and pg_class.  If remote version is lower
+  * than 9.2, columns for slot 5 are ignored.
+  */ 
+ Datum
+ pgsql_fdw_analyze(PG_FUNCTION_ARGS)
+ {
+ 	Oid				relid = PG_GETARG_OID(0);
+ 	Relation		relation;
+ 	ListCell	   *lc;
+ 	int				nstats = 0;
+ 	MemoryContext	anl_context;
+ 	MemoryContext	caller_context;
+ 	const char	   *nspname;
+ 	const char	   *relname;
+ 	ForeignTable   *table;
+ 	ForeignServer  *server;
+ 	ForeignDataWrapper *wrapper;
+ 	FdwRoutine	   *routine;
+ 	UserMapping	   *user;
+ 	PGconn		   *conn;
+ 	PGresult	   *res = NULL;
+ 	StringInfoData	sql;
+ 	char		   *endp;
+ 	double			reltuples;
+ 	int				relpages;
+ 	int				attr_cnt;
+ 	int				tcnt;
+ 	int				i;
+ 	VacAttrStats  **vacattrstats;
+ 
+ 	/* Relation oid is required. */
+ 	if (PG_ARGISNULL(0))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ 				 errmsg("relation oid must not be NULL")));
+ 
+ 	/* Use short-lived expression context. */
+ 	anl_context = AllocSetContextCreate(CurrentMemoryContext,
+ 										"Analyze",
+ 										ALLOCSET_DEFAULT_MINSIZE,
+ 										ALLOCSET_DEFAULT_INITSIZE,
+ 										ALLOCSET_DEFAULT_MAXSIZE);
+ 	caller_context = MemoryContextSwitchTo(anl_context);
+ 
+ 	/*
+ 	 * Open the relation, getting ShareUpdateExclusiveLock to ensure that two
+ 	 * ANALYZE function don't run on it concurrently.  Of course, target must
+ 	 * be a foreign table of pgsql_fdw.
+ 	 */
+ 	relation = relation_open(relid, ShareUpdateExclusiveLock);
+ 	if (get_rel_relkind(relid) != RELKIND_FOREIGN_TABLE)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 				 errmsg("\"%s\" is not a foreign table",
+ 						RelationGetRelationName(relation))));
+ 
+ 	table = GetForeignTable(relid);
+ 	server = GetForeignServer(table->serverid);
+ 	wrapper = GetForeignDataWrapper(server->fdwid);
+ 	routine = GetFdwRoutine(wrapper->fdwhandler);
+ 	if (routine != &fdwroutine)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FDW_TABLE_NOT_FOUND),
+ 				 errmsg("foreign table \"%s\" cannot handled by pgsql_fdw",
+ 						RelationGetRelationName(relation))));
+ 
+ 	/*
+ 	 * Establish connection against the foreign server to retrieve remote
+ 	 * statistics data.
+ 	 */
+ 	user = GetUserMapping(GetOuterUserId(), table->serverid);
+ 	conn = GetConnection(server, user, false);
+ 
+ 	/* Construct SQL statement to retrieve per-relation statistics. */
+ 	foreach(lc, table->options)
+ 	{
+ 		DefElem	   *def = (DefElem *) lfirst(lc);
+ 
+ 		if (strcmp(def->defname, "nspname") == 0)
+ 			nspname = defGetString(def);
+ 		else if (strcmp(def->defname, "relname") == 0)
+ 			relname = defGetString(def);
+ 		
+ 	}
+ 	if (nspname == NULL)
+ 		nspname = get_namespace_name(get_rel_namespace(relid));
+ 	nspname = quote_identifier(nspname);
+ 	if (relname == NULL)
+ 		relname = get_rel_name(relid);
+ 	relname = quote_identifier(relname);
+ 
+ 	/*
+ 	 * Get per-relation statistics from remote pg_class.  We don't fetch
+ 	 * relallvisible, which is used for estimation about index-only scan,
+ 	 * because index-only scan is not available on foreign tables.
+ 	 * 
+ 	 * XXX Should we get summary of whole inherit-tree for inherited table?
+ 	 */
+ 	initStringInfo(&sql);
+ 	appendStringInfo(&sql,
+ 					 "SELECT relpages, "
+ 					 "       reltuples "
+ 					 "  FROM pg_catalog.pg_class "
+ 					 " WHERE oid = '%s.%s'::regclass",
+ 					 nspname, relname);
+ 
+ 	PG_TRY();
+ 	{
+ 		res = PQexec(conn, sql.data);
+ 
+ 		if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ 			ereport(ERROR,
+ 					(errmsg("could not get per-table statistics"),
+ 					 errdetail("%s", PQerrorMessage(conn))));
+ 		if (PQntuples(res) != 1)
+ 			ereport(ERROR,
+ 					(errmsg("invalid number of tuples: %d", PQntuples(res))));
+ 
+ 		relpages = strtol(PGRES_VAL0(0), &endp, 10);
+ 		if (*endp != '\0')
+ 			ereport(ERROR,
+ 					(errmsg("invalid format for relpages: %s", PGRES_VAL0(0))));
+ 		reltuples = strtod(PGRES_VAL0(1), &endp);
+ 		if (*endp != '\0')
+ 			ereport(ERROR,
+ 					(errmsg("invalid format for reltuples: %s", PGRES_VAL0(1))));
+ 		PQclear(res);
+ 	}
+ 	PG_CATCH();
+ 	{
+ 		PQclear(res);
+ 		PG_RE_THROW();
+ 	}
+ 	PG_END_TRY();
+ 
+ 	/*
+ 	 * Get per-column statistics from remote pg_statistic.  Note that some of
+ 	 * local columns might be dropped.
+ 	 *
+ 	 * FIXME When porting this routine as ANALYZE handler, we need to care
+ 	 * vacstmt->va_cols here.
+ 	 *
+ 	 */
+ #ifdef NOT_USED
+ 	if (va_cols != NIL)
+ 	{
+ 		ListCell   *le;
+ 
+ 		vacattrstats = (VacAttrStats **) palloc(list_length(va_cols) *
+ 												sizeof(VacAttrStats *));
+ 		tcnt = 0;
+ 		foreach(le, va_cols)
+ 		{
+ 			char	   *col = strVal(lfirst(le));
+ 
+ 			i = attnameAttNum(relation, col, false);
+ 			if (i == InvalidAttrNumber)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_UNDEFINED_COLUMN),
+ 					errmsg("column \"%s\" of relation \"%s\" does not exist",
+ 						   col, RelationGetRelationName(relation))));
+ 			vacattrstats[tcnt] = examine_attribute(relation, i, NULL);
+ 			if (vacattrstats[tcnt] != NULL)
+ 				tcnt++;
+ 		}
+ 		attr_cnt = tcnt;
+ 	}
+ 	else
+ #endif
+ 	{
+ 		attr_cnt = relation->rd_att->natts;
+ 		vacattrstats = (VacAttrStats **)
+ 			palloc(attr_cnt * sizeof(VacAttrStats *));
+ 		tcnt = 0;
+ 		for (i = 1; i <= attr_cnt; i++)
+ 		{
+ 			vacattrstats[tcnt] = examine_attribute(relation, i, NULL);
+ 			if (vacattrstats[tcnt] != NULL)
+ 				tcnt++;
+ 		}
+ 		attr_cnt = tcnt;
+ 	}
+ 
+ 	/* Retrieve statistics for each requested column from foreign server. */
+ 	for (i = 0; i < attr_cnt; i++)
+ 	{
+ 		List		   *options;
+ 		ListCell	   *lc;
+ 		const char	   *colname = NULL;
+ 		StringInfoData	sql_stat;
+ 
+ 		/* Use colname FDW option to determine remote attribute, if any. */
+ 		options = GetForeignColumnOptions(relid,
+ 										  vacattrstats[i]->attr->attnum);
+ 		foreach(lc, options)
+ 		{
+ 			DefElem	   *def = (DefElem *) lfirst(lc);
+ 
+ 			if (strcmp(def->defname, "colname") == 0)
+ 			{
+ 				colname = defGetString(def);
+ 				break;
+ 			}
+ 		}
+ 		if (colname == NULL)
+ 			colname = get_attname(relation->rd_id,
+ 								  vacattrstats[i]->attr->attnum);
+ 
+ 		/* No need to quote column name here. */
+ 		initStringInfo(&sql_stat);
+ 		if (PQserverVersion(conn) >= 90200)
+ 		{
+ 			appendStringInfo(&sql_stat,
+ 							 "SELECT starelid,"
+ 							 "       staattnum,"
+ 							 /* fixed value is used for stainherit, see below */
+ 							 "       stanullfrac,"
+ 							 "       stawidth,"
+ 							 "       stadistinct,"
+ 							 "       stakind1,"
+ 							 "       stakind2,"
+ 							 "       stakind3,"
+ 							 "       stakind4,"
+ 							 "       stakind5,"
+ 							 "       staop1,"
+ 							 "       staop2,"
+ 							 "       staop3,"
+ 							 "       staop4,"
+ 							 "       staop5,"
+ 							 "       stanumbers1,"
+ 							 "       stanumbers2,"
+ 							 "       stanumbers3,"
+ 							 "       stanumbers4,"
+ 							 "       stanumbers5,"
+ 							 "       stavalues1,"
+ 							 "       stavalues2,"
+ 							 "       stavalues3,"
+ 							 "       stavalues4,"
+ 							 "       stavalues5"
+ 							 "  FROM pg_catalog.pg_statistic "
+ 							 " WHERE starelid = '%s.%s'::regclass "
+ 							 "   AND staattnum = ("
+ 							 "           SELECT attnum "
+ 							 "             FROM pg_catalog.pg_attribute "
+ 							 "            WHERE attrelid = '%s.%s'::regclass "
+ 							 "              AND attname = '%s')",
+ 							 nspname, relname, nspname, relname, colname);
+ 		}
+ 		else
+ 		{
+ 			appendStringInfo(&sql_stat,
+ 							 "SELECT starelid,"
+ 							 "       staattnum,"
+ 							 /* fixed value is used for stainherit, see below */
+ 							 "       stanullfrac,"
+ 							 "       stawidth,"
+ 							 "       stadistinct,"
+ 							 "       stakind1,"
+ 							 "       stakind2,"
+ 							 "       stakind3,"
+ 							 "       stakind4,"
+ 							 "       staop1,"
+ 							 "       staop2,"
+ 							 "       staop3,"
+ 							 "       staop4,"
+ 							 "       stanumbers1,"
+ 							 "       stanumbers2,"
+ 							 "       stanumbers3,"
+ 							 "       stanumbers4,"
+ 							 "       stavalues1,"
+ 							 "       stavalues2,"
+ 							 "       stavalues3,"
+ 							 "       stavalues4"
+ 							 "  FROM pg_catalog.pg_statistic "
+ 							 " WHERE starelid = '%s.%s'::regclass "
+ 							 "   AND staattnum = ("
+ 							 "           SELECT attnum "
+ 							 "             FROM pg_catalog.pg_attribute "
+ 							 "            WHERE attrelid = '%s.%s'::regclass "
+ 							 "              AND attname = '%s')",
+ 							 nspname, relname, nspname, relname, colname);
+ 		}
+ 
+ 		/*
+ 		 * If foreign server is PG 9.0 or later, inherited tables might have
+ 		 * two sets of statistics; one contains information about the table
+ 		 * itself, and another contains information about whole inherit-tree
+ 		 * topped by the table.
+ 		 *
+ 		 * We prefer statistics of whole inherit-tree if any, because parent
+ 		 * table returns contents of descendants too.  However, we overwrite
+ 		 * stainherit by "false" for local statistics, because foreign tables
+ 		 * can't be parent table.
+ 		 */
+ 		if (PQserverVersion(conn) >= 90000)
+ 			appendStringInfo(&sql_stat, "ORDER BY stainherit DESC LIMIT 1");
+ 
+ 		res = NULL;
+ 		PG_TRY();
+ 		{
+ 			res = PQexec(conn, sql_stat.data);
+ 			pfree(sql_stat.data);
+ 			if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ 				ereport(ERROR,
+ 						(errmsg("could not get per-column statistics"),
+ 						 errdetail("%s", PQerrorMessage(conn))));
+ 
+ 			/* If remote table doesn't have any statistic, skip this column. */
+ 			if (PQntuples(res) > 0)
+ 			{
+ 				store_remote_stats(res, vacattrstats[i]);
+ 				nstats++;
+ 			}
+ 
+ 			PQclear(res);
+ 		}
+ 		PG_CATCH();
+ 		{
+ 			PQclear(res);
+ 			PG_RE_THROW();
+ 		}
+ 		PG_END_TRY();
+ 	}
+ 
+ 	/* Disconnect from foreign server. */
+ 	ReleaseConnection(conn);
+ 
+ 	/* Update local pg_statistic with retrieved statistics. */
+ 	vac_update_attstats(RelationGetRelid(relation),
+ 						false,
+ 						attr_cnt,
+ 						vacattrstats);
+ 
+ 	/*
+ 	 * Update local pg_class with retrieved statistics.  We set relallvisible
+ 	 * to 0 always.
+ 	 */
+ 	vac_update_relstats(relation,
+ 						relpages,
+ 						reltuples,
+ 						0,
+ 						false,
+ 						InvalidTransactionId);
+ 
+ 	/* Restore memory context. */
+ 	MemoryContextSwitchTo(caller_context);
+ 	MemoryContextDelete(anl_context);
+ 
+ #ifdef NOT_USED
+ 	/* Print verbose messages. */
+ 	if (vacstmt->options & VACOPT_VERBOSE)
+ 		elog(INFO, "%s: relpages=%d, reltuples=%f, %d per-column stats copied",
+ 			 vacstmt->relation->relname,
+ 			 relpages,
+ 			 reltuples,
+ 			 nstats);
+ #endif
+ 
+ 	/* Close the relation with releasing the lock. */
+ 	relation_close(relation, ShareUpdateExclusiveLock);
+ 
+ 	PG_RETURN_INT32(nstats);
+ }
+ 
+ /*
+  * Read text representation of remote statistics from res and store them into
+  * stats with conversion to internal representation.
+  */
+ static void
+ store_remote_stats(PGresult *res, VacAttrStats *stats)
+ {
+ 	Type			float4array_type;
+ 	Type			textarray_type;
+ 	Oid				elemtypeid;
+ 	Type			elemtype;
+ 	Oid				in_func_oid;
+ 	Form_pg_type	attrtype;
+ 	Oid				typeioparam;
+ 	FmgrInfo		in_function;
+ 
+ 	int				i_stanullfrac;
+ 	int				i_stawidth;
+ 	int				i_stadistinct;
+ 	int				i_stakind[STATISTIC_NUM_SLOTS];
+ 	int				i_staop[STATISTIC_NUM_SLOTS];
+ 	int				i_stanumbers[STATISTIC_NUM_SLOTS];
+ 	int				i_stavalues[STATISTIC_NUM_SLOTS];
+ 	int				i;
+ 
+ 	/* Get information of actual type of array element. */
+ 	elemtypeid = stats->attr->atttypid;
+ 	elemtype = typeidType(elemtypeid);
+ 	getTypeInputInfo(elemtypeid, &in_func_oid, &typeioparam);
+ 	fmgr_info(in_func_oid, &in_function);
+ 
+ 	attrtype = (Form_pg_type) palloc(sizeof(FormData_pg_type));
+ 	memcpy(attrtype, GETSTRUCT(elemtype), sizeof(FormData_pg_type));
+ 
+ 	i_stanullfrac = PQfnumber(res, "stanullfrac");
+ 	i_stawidth = PQfnumber(res, "stawidth");
+ 	i_stadistinct = PQfnumber(res, "stadistinct");
+ 	i_stakind[0] = PQfnumber(res, "stakind1");
+ 	i_stakind[1] = PQfnumber(res, "stakind2");
+ 	i_stakind[2] = PQfnumber(res, "stakind3");
+ 	i_stakind[3] = PQfnumber(res, "stakind4");
+ 	i_stakind[4] = PQfnumber(res, "stakind5");
+ 	i_staop[0] = PQfnumber(res, "staop1");
+ 	i_staop[1] = PQfnumber(res, "staop2");
+ 	i_staop[2] = PQfnumber(res, "staop3");
+ 	i_staop[3] = PQfnumber(res, "staop4");
+ 	i_staop[4] = PQfnumber(res, "staop5");
+ 	i_stanumbers[0] = PQfnumber(res, "stanumbers1");
+ 	i_stanumbers[1] = PQfnumber(res, "stanumbers2");
+ 	i_stanumbers[2] = PQfnumber(res, "stanumbers3");
+ 	i_stanumbers[3] = PQfnumber(res, "stanumbers4");
+ 	i_stanumbers[4] = PQfnumber(res, "stanumbers5");
+ 	i_stavalues[0] = PQfnumber(res, "stavalues1");
+ 	i_stavalues[1] = PQfnumber(res, "stavalues2");
+ 	i_stavalues[2] = PQfnumber(res, "stavalues3");
+ 	i_stavalues[3] = PQfnumber(res, "stavalues4");
+ 	i_stavalues[4] = PQfnumber(res, "stavalues5");
+ 
+ 	/*
+ 	 * If statistics are found, mark this column to be updated later
+ 	 * and fill with retrieved remote statistics.
+ 	 *
+ 	 * For scalar values, just copying values is enough, but we have to
+ 	 * be conscious the types of elements for array values.  libpq
+ 	 * gives us an array value in a string representation, such as
+ 	 * "{foo, bar}", so we need to (1)separate an array string into
+ 	 * elements, (2)convert each element into Datum representation, and
+ 	 * (3) create a Datum representation of array from Datum elements.
+ 	 *
+ 	 * Note: OID of operator (staopN) MUST match between remote and local.
+ 	 */
+ 	stats->stats_valid = true;
+ 	stats->stanullfrac = strtof(PGRES_VAL0(i_stanullfrac), NULL);
+ 	stats->stawidth = strtol(PGRES_VAL0(i_stawidth), NULL, 10);
+ 	stats->stadistinct = strtof(PGRES_VAL0(i_stadistinct), NULL);
+ 
+ 	/*
+ 	 * Get type information for stanumbers and stavalues.  We assume stavalues
+ 	 * as array of text during extracting elements, and then extracted
+ 	 * elements are treated as the type of foreign table attributes.
+ 	 */
+ 	float4array_type = typeidType(FLOAT4ARRAYOID);
+ 	textarray_type = typeidType(TEXTARRAYOID);
+ 
+ 	for (i = 0; i < STATISTIC_NUM_SLOTS; i++)
+ 	{
+ 		int			atttypmod = 0;
+ 		int			j;
+ 		Datum		tnumbers = 0;
+ 		ArrayType  *array;
+ 		int			nitems;
+ 		bits8	   *bitmap;
+ 		int			bitmask;
+ 		char	   *p;
+ 
+ 		/* PostgreSQL 9.1 and older don't have stakind5. */
+ 		if (i_stakind[i] < 0 || i_staop[i] < 0 ||
+ 			i_stanumbers[i] < 0 || i_stavalues[i] < 0)
+ 			continue;
+ 
+ 		stats->stakind[i] = strtol(PGRES_VAL0(i_stakind[i]), NULL, 10);
+ 		stats->staop[i] = strtol(PGRES_VAL0(i_staop[i]), NULL, 10);
+ 
+ 		/*
+ 		 * Extract element values of stanumbersN from text representation of
+ 		 * array, if any.
+ 		 */
+ 		if (!PGRES_NULL0(i_stanumbers[i]))
+ 		{
+ 			/* Separate array string into each float4 elements. */
+ 			tnumbers = stringTypeDatum(float4array_type,
+ 									   PGRES_VAL0(i_stanumbers[i]),
+ 									   -1);
+ 			array = DatumGetArrayTypeP(tnumbers);
+ 			nitems = ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array));
+ 
+ 			bitmap = ARR_NULLBITMAP(array);
+ 			bitmask = 1;
+ 
+ 			stats->numnumbers[i] = nitems;
+ 			stats->stanumbers[i] = (float4 *) palloc(nitems * sizeof(float4));
+ 			for (j = 0; j < nitems; j++)
+ 			{
+ 				p = ARR_DATA_PTR(array);
+ 
+ 				if (bitmap && (*bitmap & bitmask) == 0)
+ 				{
+ 					/* nulls can be ignored */
+ 				}
+ 				else
+ 				{
+ 					stats->stanumbers[i][j] = * (float4 *) ARR_DATA_PTR(array);
+ 					p = p + sizeof(float4);
+ 					p = (char *) att_align_nominal(p, 'i');
+ 				}
+ 
+ 				/* advance bitmap pointer if any */
+ 				if (bitmap)
+ 				{
+ 					bitmask <<= 1;
+ 					if (bitmask == 0x100)
+ 					{
+ 						bitmap++;
+ 						bitmask = 1;
+ 					}
+ 				}
+ 			}
+ 		}
+ 		else
+ 		{
+ 			stats->numnumbers[i] = 0;
+ 			stats->stanumbers[i] = NULL;
+ 		}
+ 
+ 		/*
+ 		 * Extract element values of stavaluesN from text representation of
+ 		 * array, if any.
+ 		 */
+ 		if (!PGRES_NULL0(i_stavalues[i]))
+ 		{
+ 			/*
+ 			 * Separate array string into each elements string, and convert
+ 			 * each element to actual column data type.
+ 			 */
+ 			tnumbers = stringTypeDatum(textarray_type,
+ 									   PGRES_VAL0(i_stavalues[i]),
+ 									   -1);
+ 			array = DatumGetArrayTypeP(tnumbers);
+ 			nitems = ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array));
+ 
+ 			bitmap = ARR_NULLBITMAP(array);
+ 			bitmask = 1;
+ 			p = ARR_DATA_PTR(array);
+ 
+ 			stats->numvalues[i] = nitems;
+ 			stats->stavalues[i] = (Datum *) palloc(nitems * sizeof(Datum));
+ 
+ 			for (j = 0; j < nitems; j++)
+ 			{
+ 				char	   *string;
+ 
+ 				if (bitmap && (*bitmap & bitmask) == 0)
+ 				{
+ 					/* nulls can be ignored */
+ 				}
+ 				else
+ 				{
+ 					string = text_to_cstring((text *) p);
+ 					stats->stavalues[i][j] = InputFunctionCall(&in_function,
+ 															   string,
+ 															   typeioparam,
+ 															   atttypmod);
+ 					p = p + VARSIZE_ANY(p);
+ 					p = (char *) att_align_nominal(p, 'i');
+ 				}
+ 
+ 				/* advance bitmap pointer if any */
+ 				if (bitmap)
+ 				{
+ 					bitmask <<= 1;
+ 					if (bitmask == 0x100)
+ 					{
+ 						bitmap++;
+ 						bitmask = 1;
+ 					}
+ 				}
+ 			}
+ 		}
+ 		else
+ 		{
+ 			stats->numvalues[i] = 0;
+ 			stats->stavalues[i] = NULL;
+ 		}
+ 	}
+ 
+ 	/* Clean up */
+ 	ReleaseSysCache(elemtype);
+ 	ReleaseSysCache(textarray_type);
+ 	ReleaseSysCache(float4array_type);
+ }
diff --git a/contrib/pgsql_fdw/sql/pgsql_fdw.sql b/contrib/pgsql_fdw/sql/pgsql_fdw.sql
index b7cd71d..eef1cfa 100644
*** a/contrib/pgsql_fdw/sql/pgsql_fdw.sql
--- b/contrib/pgsql_fdw/sql/pgsql_fdw.sql
*************** INSERT INTO "S 1"."T 2"
*** 85,90 ****
--- 85,91 ----
  	       'AAA' || to_char(id, 'FM000')
  	FROM generate_series(1, 100) id;
  COMMIT;
+ ANALYZE "S 1"."T 1";
  
  -- ===================================================================
  -- tests for pgsql_fdw_validator
*************** SELECT pgsql_fdw_disconnect(srvid, usesy
*** 235,240 ****
--- 236,248 ----
  SELECT srvname, usename FROM pgsql_fdw_connections;
  
  -- ===================================================================
+ -- statistics management
+ -- ===================================================================
+ DELETE FROM pg_statistic WHERE starelid = '"S 1"."T 1"'::regclass AND staattnum IN (1, 3);
+ SELECT pgsql_fdw_analyze('ft1');
+ SELECT count(*) FROM pg_statistic WHERE starelid = 'ft1'::regclass;
+ 
+ -- ===================================================================
  -- subtransaction
  -- ===================================================================
  BEGIN;
diff --git a/doc/src/sgml/pgsql-fdw.sgml b/doc/src/sgml/pgsql-fdw.sgml
index eb69f01..2c16c43 100644
*** a/doc/src/sgml/pgsql-fdw.sgml
--- b/doc/src/sgml/pgsql-fdw.sgml
*************** postgres=# SELECT pgsql_fdw_disconnect(s
*** 218,223 ****
--- 218,244 ----
   </sect2>
  
   <sect2>
+   <title>Statistics Management</title>
+   <para>
+    <application>pgsql_fdw</application> provides a function
+    <function>pgsql_fdw_analyze()</function> which updates local statistics
+    stored in <structname>pg_statistic</structname> and
+    <structname>pg_class</structname> by copying remote statistics.  This
+    function takes a regclass argument of target foreign table, and returns
+    number of attributes of which per-attribute statistics are updated.  Thus,
+    this function may return number less than attribute count when remote table
+    doesn't have statistics for all attributes.
+   </para>
+   <note>
+    <para>
+     Number of statistics slots in <structname>pg_statistic</structname> is
+     increased to 5 in 9.2, so this function copies only slot 1 to 4 from older
+     versions. 
+    </para>
+   </note>
+  </sect2>
+ 
+  <sect2>
    <title>Estimation of Costs and Rows</title>
    <para>
     The <application>pgsql_fdw</application> estimates the costs of a foreign
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 9cd6e67..21b1bf1 100644
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
*************** static void compute_index_stats(Relation
*** 94,101 ****
  					AnlIndexData *indexdata, int nindexes,
  					HeapTuple *rows, int numrows,
  					MemoryContext col_context);
- static VacAttrStats *examine_attribute(Relation onerel, int attnum,
- 				  Node *index_expr);
  static int acquire_sample_rows(Relation onerel, HeapTuple *rows,
  					int targrows, double *totalrows, double *totaldeadrows);
  static double random_fract(void);
--- 94,99 ----
*************** static int	compare_rows(const void *a, c
*** 105,112 ****
  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);
  
--- 103,108 ----
*************** analyze_rel(Oid relid, VacuumStmt *vacst
*** 209,215 ****
  	}
  
  	/*
! 	 * We can ANALYZE any table except pg_statistic. See update_attstats
  	 */
  	if (RelationGetRelid(onerel) == StatisticRelationId)
  	{
--- 205,211 ----
  	}
  
  	/*
! 	 * We can ANALYZE any table except pg_statistic. See vac_update_attstats
  	 */
  	if (RelationGetRelid(onerel) == StatisticRelationId)
  	{
*************** analyze_rel(Oid relid, VacuumStmt *vacst
*** 239,245 ****
  	 * Close source relation now, but keep lock so that no one deletes it
  	 * before we commit.  (If someone did, they'd fail to clean up the entries
  	 * we made in pg_statistic.  Also, releasing the lock before commit would
! 	 * expose us to concurrent-update failures in update_attstats.)
  	 */
  	relation_close(onerel, NoLock);
  
--- 235,241 ----
  	 * Close source relation now, but keep lock so that no one deletes it
  	 * before we commit.  (If someone did, they'd fail to clean up the entries
  	 * we made in pg_statistic.  Also, releasing the lock before commit would
! 	 * expose us to concurrent-update failures in vac_update_attstats.)
  	 */
  	relation_close(onerel, NoLock);
  
*************** do_analyze_rel(Relation onerel, VacuumSt
*** 514,528 ****
  		 * previous statistics for the target columns.	(If there are stats in
  		 * pg_statistic for columns we didn't process, we leave them alone.)
  		 */
! 		update_attstats(RelationGetRelid(onerel), inh,
! 						attr_cnt, vacattrstats);
  
  		for (ind = 0; ind < nindexes; ind++)
  		{
  			AnlIndexData *thisdata = &indexdata[ind];
  
! 			update_attstats(RelationGetRelid(Irel[ind]), false,
! 							thisdata->attr_cnt, thisdata->vacattrstats);
  		}
  	}
  
--- 510,524 ----
  		 * previous statistics for the target columns.	(If there are stats in
  		 * pg_statistic for columns we didn't process, we leave them alone.)
  		 */
! 		vac_update_attstats(RelationGetRelid(onerel), inh,
! 							attr_cnt, vacattrstats);
  
  		for (ind = 0; ind < nindexes; ind++)
  		{
  			AnlIndexData *thisdata = &indexdata[ind];
  
! 			vac_update_attstats(RelationGetRelid(Irel[ind]), false,
! 								thisdata->attr_cnt, thisdata->vacattrstats);
  		}
  	}
  
*************** compute_index_stats(Relation onerel, dou
*** 805,811 ****
   * If index_expr isn't NULL, then we're trying to analyze an expression index,
   * and index_expr is the expression tree representing the column's data.
   */
! static VacAttrStats *
  examine_attribute(Relation onerel, int attnum, Node *index_expr)
  {
  	Form_pg_attribute attr = onerel->rd_att->attrs[attnum - 1];
--- 801,807 ----
   * 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.
   */
! VacAttrStats *
  examine_attribute(Relation onerel, int attnum, Node *index_expr)
  {
  	Form_pg_attribute attr = onerel->rd_att->attrs[attnum - 1];
*************** acquire_inherited_sample_rows(Relation o
*** 1538,1544 ****
  
  
  /*
!  *	update_attstats() -- update attribute statistics for one relation
   *
   *		Statistics are stored in several places: the pg_class row for the
   *		relation has stats about the whole relation, and there is a
--- 1534,1540 ----
  
  
  /*
!  *	vac_update_attstats() -- update attribute statistics for one relation
   *
   *		Statistics are stored in several places: the pg_class row for the
   *		relation has stats about the whole relation, and there is a
*************** acquire_inherited_sample_rows(Relation o
*** 1559,1566 ****
   *		ANALYZE the same table concurrently.  Presently, we lock that out
   *		by taking a self-exclusive lock on the relation in analyze_rel().
   */
! static void
! update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
  {
  	Relation	sd;
  	int			attno;
--- 1555,1562 ----
   *		ANALYZE the same table concurrently.  Presently, we lock that out
   *		by taking a self-exclusive lock on the relation in analyze_rel().
   */
! void
! vac_update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
  {
  	Relation	sd;
  	int			attno;
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 3deee66..da9bf8a 100644
*** a/src/include/commands/vacuum.h
--- b/src/include/commands/vacuum.h
*************** extern void vac_update_relstats(Relation
*** 154,159 ****
--- 154,161 ----
  					BlockNumber num_all_visible_pages,
  					bool hasindex,
  					TransactionId frozenxid);
+ extern void vac_update_attstats(Oid relid, bool inh,
+ 					int natts, VacAttrStats **vacattrstats);
  extern void vacuum_set_xid_limits(int freeze_min_age, int freeze_table_age,
  					  bool sharedRel,
  					  TransactionId *oldestXmin,
*************** extern void lazy_vacuum_rel(Relation one
*** 170,174 ****
--- 172,178 ----
  extern void analyze_rel(Oid relid, VacuumStmt *vacstmt,
  			BufferAccessStrategy bstrategy);
  extern bool std_typanalyze(VacAttrStats *stats);
+ extern VacAttrStats *examine_attribute(Relation onerel, int attnum,
+ 			Node *index_expr);
  
  #endif   /* VACUUM_H */