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 ----
+ Statistics Management
+
+ pgsql_fdw provides a function
+ pgsql_fdw_analyze() which updates local statistics
+ stored in pg_statistic and
+ pg_class 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.
+
+
+
+ Number of statistics slots in pg_statistic is
+ increased to 5 in 9.2, so this function copies only slot 1 to 4 from older
+ versions.
+
+
+
+
+
Estimation of Costs and Rows
The pgsql_fdw 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 */