diff --git a/contrib/pgsql_fdw/expected/pgsql_fdw.out b/contrib/pgsql_fdw/expected/pgsql_fdw.out index cdbdd63..62a2c0e 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 -- =================================================================== *************** EXECUTE st1(101, 101); *** 459,478 **** -- subquery using stable function (can't be pushed down) PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND EXTRACT(dow FROM c4) = 6) ORDER BY c1; EXPLAIN (COSTS false) EXECUTE st2(10, 20); ! QUERY PLAN ! ----------------------------------------------------------------------------------------------------------------------------------------------- Sort Sort Key: t1.c1 ! -> Hash Join ! Hash Cond: (t1.c3 = t2.c3) -> Foreign Scan on ft1 t1 Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.<) 20)) ! -> Hash ! -> HashAggregate ! -> Foreign Scan on ft2 t2 ! Filter: (date_part('dow'::text, c4) = 6::double precision) ! Remote SQL: SELECT "C 1", NULL, c3, c4, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.>) 10)) ! (11 rows) EXECUTE st2(10, 20); c1 | c2 | c3 | c4 | c5 | c6 | c7 --- 460,478 ---- -- subquery using stable function (can't be pushed down) PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND EXTRACT(dow FROM c4) = 6) ORDER BY c1; EXPLAIN (COSTS false) EXECUTE st2(10, 20); ! QUERY PLAN ! ----------------------------------------------------------------------------------------------------------------------------------------- Sort Sort Key: t1.c1 ! -> Nested Loop Semi Join ! Join Filter: (t1.c3 = t2.c3) -> Foreign Scan on ft1 t1 Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.<) 20)) ! -> Materialize ! -> Foreign Scan on ft2 t2 ! Filter: (date_part('dow'::text, c4) = 6::double precision) ! Remote SQL: SELECT "C 1", NULL, c3, c4, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.>) 10)) ! (10 rows) EXECUTE st2(10, 20); c1 | c2 | c3 | c4 | c5 | c6 | c7 *************** EXPLAIN (COSTS false) EXECUTE st4(1); *** 552,561 **** (2 rows) EXPLAIN (COSTS false) EXECUTE st4(1); ! QUERY PLAN ! --------------------------------------------------------------------------------------------------------------- Foreign Scan on ft1 t1 ! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) $1)) (2 rows) -- cleanup --- 552,561 ---- (2 rows) EXPLAIN (COSTS false) EXECUTE st4(1); ! QUERY PLAN ! -------------------------------------------------------------------------------------------------------------- Foreign Scan on ft1 t1 ! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1)) (2 rows) -- cleanup *************** SELECT srvname, usename FROM pgsql_fdw_c *** 584,589 **** --- 584,605 ---- (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 2424fb4..22675e1 100644 *** a/contrib/pgsql_fdw/pgsql_fdw.c --- b/contrib/pgsql_fdw/pgsql_fdw.c *************** *** 13,31 **** --- 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 *** 142,147 **** --- 148,155 ---- */ 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 *** 178,183 **** --- 186,192 ---- 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); *************** store_result(ForeignScanState *node, PGr *** 1085,1087 **** --- 1094,1682 ---- 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 23a44df..ec6f292 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 */