*** a/contrib/file_fdw/file_fdw.c --- b/contrib/file_fdw/file_fdw.c *************** *** 16,31 **** #include #include "access/reloptions.h" #include "catalog/pg_foreign_table.h" #include "commands/copy.h" #include "commands/defrem.h" #include "commands/explain.h" #include "foreign/fdwapi.h" #include "foreign/foreign.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "optimizer/cost.h" ! #include "utils/rel.h" #include "utils/syscache.h" PG_MODULE_MAGIC; --- 16,36 ---- #include #include "access/reloptions.h" + #include "access/transam.h" #include "catalog/pg_foreign_table.h" #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" #include "nodes/makefuncs.h" #include "optimizer/cost.h" ! #include "parser/parse_relation.h" ! #include "pgstat.h" ! #include "utils/attoptcache.h" ! #include "utils/memutils.h" #include "utils/syscache.h" PG_MODULE_MAGIC; *************** *** 101,106 **** static void fileBeginForeignScan(ForeignScanState *node, int eflags); --- 106,112 ---- 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 *************** *** 112,118 **** static List *get_file_fdw_attribute_options(Oid relid); static void estimate_costs(PlannerInfo *root, RelOptInfo *baserel, const char *filename, Cost *startup_cost, Cost *total_cost); ! /* * Foreign-data wrapper handler function: return a struct with pointers --- 118,127 ---- static void estimate_costs(PlannerInfo *root, RelOptInfo *baserel, const char *filename, 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); /* * Foreign-data wrapper handler function: return a struct with pointers *************** *** 129,134 **** file_fdw_handler(PG_FUNCTION_ARGS) --- 138,144 ---- fdwroutine->IterateForeignScan = fileIterateForeignScan; fdwroutine->ReScanForeignScan = fileReScanForeignScan; fdwroutine->EndForeignScan = fileEndForeignScan; + fdwroutine->AnalyzeForeignTable = fileAnalyzeForeignTable; PG_RETURN_POINTER(fdwroutine); } *************** *** 575,580 **** fileReScanForeignScan(ForeignScanState *node) --- 585,602 ---- } /* + * fileAnalyzeForeignTable + * Analyze table + */ + static void + fileAnalyzeForeignTable(Relation onerel, VacuumStmt *vacstmt, int elevel) + { + do_analyze_rel(onerel, vacstmt, + elevel, false, + acquire_sample_rows); + } + + /* * Estimate costs of scanning a foreign table. */ static void *************** *** 584,590 **** estimate_costs(PlannerInfo *root, RelOptInfo *baserel, { struct stat stat_buf; BlockNumber pages; ! int tuple_width; double ntuples; double nrows; Cost run_cost = 0; --- 606,613 ---- { struct stat stat_buf; BlockNumber pages; ! BlockNumber relpages; ! double reltuples; double ntuples; double nrows; Cost run_cost = 0; *************** *** 604,619 **** estimate_costs(PlannerInfo *root, RelOptInfo *baserel, if (pages < 1) pages = 1; ! /* ! * 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); /* * Now estimate the number of rows returned by the scan after applying the --- 627,658 ---- if (pages < 1) pages = 1; ! relpages = baserel->pages; ! reltuples = baserel->tuples; ! ! if (relpages > 0) ! { ! double density; ! density = reltuples / (double) relpages; ! ! 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); ! } /* * Now estimate the number of rows returned by the scan after applying the *************** *** 645,647 **** estimate_costs(PlannerInfo *root, RelOptInfo *baserel, --- 684,888 ---- 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 number of rows in the table, and return it into *totalrows. + * + * The returned list of tuples is in order by physical position in the table. + * (We will rely on this later to derive correlation estimates.) + */ + static int + acquire_sample_rows(Relation onerel, HeapTuple *rows, int targrows, + double *totalrows, double *totaldeadrows, + BlockNumber *totalpages, int elevel) + { + int numrows = 0; + double samplerows = 0; /* total # rows collected */ + double rowstoskip = -1; /* -1 means not set yet */ + double rstate; + HeapTuple tuple; + TupleDesc tupDesc; + TupleConstr *constr; + int natts; + int attrChk; + Datum *values; + bool *nulls; + bool found; + bool sample_it = false; + BlockNumber blknum; + OffsetNumber offnum; + char *filename; + struct stat stat_buf; + List *options; + CopyState cstate; + ErrorContextCallback errcontext; + + Assert(onerel); + Assert(targrows > 0); + + tupDesc = RelationGetDescr(onerel); + constr = tupDesc->constr; + natts = tupDesc->natts; + 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; + + for (;;) + { + sample_it = true; + + /* + * 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); + + if (constr && constr->has_not_null) + { + for (attrChk = 1; attrChk <= natts; attrChk++) + { + if (onerel->rd_att->attrs[attrChk - 1]->attnotnull && + heap_attisnull(tuple, attrChk)) + { + sample_it = false; + break; + } + } + } + + if (!sample_it) + { + heap_freetuple(tuple); + continue; + } + + /* + * 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) + { + blknum = (BlockNumber) samplerows / MaxOffsetNumber; + offnum = (OffsetNumber) samplerows % MaxOffsetNumber + 1; + ItemPointerSet(&tuple->t_self, blknum, offnum); + 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(samplerows, 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]); + + blknum = (BlockNumber) samplerows / MaxOffsetNumber; + offnum = (OffsetNumber) samplerows % MaxOffsetNumber + 1; + ItemPointerSet(&tuple->t_self, blknum, offnum); + rows[k] = heap_copytuple(tuple); + } + + rowstoskip -= 1; + } + + samplerows += 1; + heap_freetuple(tuple); + } + + /* Remove error callback. */ + error_context_stack = errcontext.previous; + + /* + * If we didn't find as many tuples as we wanted then we're done. No sort + * is needed, since they're already in order. + * + * Otherwise we need to sort the collected tuples by position + * (itempointer). It's not worth worrying about corner cases where the + * tuples are already sorted. + */ + if (numrows == targrows) + qsort((void *) rows, numrows, sizeof(HeapTuple), compare_rows); + + *totalrows = samplerows; + *totaldeadrows = 0; + + 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; + } *** a/contrib/file_fdw/input/file_fdw.source --- b/contrib/file_fdw/input/file_fdw.source *************** *** 111,116 **** EXECUTE st(100); --- 111,121 ---- 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; *** a/contrib/file_fdw/output/file_fdw.source --- b/contrib/file_fdw/output/file_fdw.source *************** *** 174,179 **** EXECUTE st(100); --- 174,194 ---- (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 + ------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-------------------------+------------- + 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 *** a/doc/src/sgml/fdwhandler.sgml --- b/doc/src/sgml/fdwhandler.sgml *************** *** 228,233 **** EndForeignScan (ForeignScanState *node); --- 228,246 ---- + + void + AnalyzeForeignTable (Relation onerel, + VacuumStmt *vacstmt, + int elevel); + + + Collect statistics on a foreign table and store the results in the + pg_class and pg_statistics system catalogs. + This is called when ANALYZE command is run. + + + The FdwRoutine and FdwPlan struct types are declared in src/include/foreign/fdwapi.h, which see for additional details. *** a/doc/src/sgml/maintenance.sgml --- b/doc/src/sgml/maintenance.sgml *************** *** 279,284 **** --- 279,288 ---- 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 ANALYZE + commands on foreign tables. It is recommended to run manually-managed + ANALYZE commands as needed, which typically are executed + according to a schedule by cron or Task Scheduler scripts. *** a/doc/src/sgml/ref/alter_foreign_table.sgml --- b/doc/src/sgml/ref/alter_foreign_table.sgml *************** *** 36,41 **** ALTER FOREIGN TABLE name --- 36,44 ---- DROP [ COLUMN ] [ IF EXISTS ] column [ RESTRICT | CASCADE ] ALTER [ COLUMN ] column [ SET DATA ] TYPE type ALTER [ COLUMN ] column { SET | DROP } NOT NULL + ALTER [ COLUMN ] column SET STATISTICS integer + ALTER [ COLUMN ] column SET ( attribute_option = value [, ... ] ) + ALTER [ COLUMN ] column RESET ( attribute_option [, ... ] ) ALTER [ COLUMN ] column OPTIONS ( [ ADD | SET | DROP ] option ['value'] [, ... ]) OWNER TO new_owner OPTIONS ( [ ADD | SET | DROP ] option ['value'] [, ... ]) *************** *** 94,99 **** ALTER FOREIGN TABLE name --- 97,146 ---- + SET STATISTICS + + + This form + sets the per-column statistics-gathering target for subsequent + 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 (). + + + + + + SET ( attribute_option = value [, ... ] ) + RESET ( attribute_option [, ... ] ) + + + This form + sets or resets a per-attribute option. Currently, the only defined + per-attribute option is n_distinct, which overrides + the number-of-distinct-values estimates made by subsequent + operations. + When set to a positive value, 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, 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. + + + + + OWNER *** a/doc/src/sgml/ref/analyze.sgml --- b/doc/src/sgml/ref/analyze.sgml *************** *** 39,47 **** ANALYZE [ VERBOSE ] [ table [ ( With no parameter, ANALYZE examines every table in the ! current database. With a parameter, ANALYZE 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. --- 39,49 ---- With no parameter, ANALYZE examines every table in the ! current database except for foreign tables. With a parameter, ! ANALYZE examines only that table. For a foreign table, it is ! necessary to spcify 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. *************** *** 63,69 **** ANALYZE [ VERBOSE ] [ table [ ( The name (possibly schema-qualified) of a specific table to ! analyze. Defaults to all tables in the current database. --- 65,72 ---- The name (possibly schema-qualified) of a specific table to ! analyze. Defaults to all tables in the current database except ! for foreign tables. *************** *** 137,143 **** ANALYZE [ VERBOSE ] [ table [ ( ANALYZE is run. To avoid this, raise the amount of statistics collected by ! ANALYZE, as described below. --- 140,148 ---- In rare situations, this non-determinism will cause the planner's choices of query plans to change after ANALYZE is run. To avoid this, raise the amount of statistics collected by ! ANALYZE, 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. *** a/src/backend/commands/analyze.c --- b/src/backend/commands/analyze.c *************** *** 23,28 **** --- 23,29 ---- #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,35 **** --- 31,38 ---- #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,91 **** 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); --- 81,91 ---- *************** *** 96,110 **** 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_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); --- 96,109 ---- 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, ! BlockNumber *totalpages, int elevel); static int acquire_inherited_sample_rows(Relation onerel, HeapTuple *rows, int targrows, ! double *totalrows, double *totaldeadrows, ! BlockNumber *totalpages, int elevel); static void update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats); static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull); *************** *** 119,125 **** static bool std_typanalyze(VacAttrStats *stats); --- 118,126 ---- void analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy) { + int elevel; Relation onerel; + FdwRoutine *fdwroutine; /* Set up static variables */ if (vacstmt->options & VACOPT_VERBOSE) *************** *** 184,193 **** 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. */ ! if (onerel->rd_rel->relkind != RELKIND_RELATION) { /* No need for a WARNING if we already complained during VACUUM */ if (!(vacstmt->options & VACOPT_VACUUM)) --- 185,195 ---- } /* ! * 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 && ! onerel->rd_rel->relkind != RELKIND_FOREIGN_TABLE) { /* No need for a WARNING if we already complained during VACUUM */ if (!(vacstmt->options & VACOPT_VACUUM)) *************** *** 212,217 **** analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy) --- 214,221 ---- /* * We can ANALYZE any table except pg_statistic. See update_attstats + * We can ANALYZE foreign tables if the underlying foreign-data wrappers + * have their AnalyzeForeignTable callback routines. */ if (RelationGetRelid(onerel) == StatisticRelationId) { *************** *** 219,224 **** analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy) --- 223,242 ---- 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. */ *************** *** 226,241 **** analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy) MyProc->vacuumFlags |= PROC_IN_ANALYZE; LWLockRelease(ProcArrayLock); ! /* ! * Do the normal non-recursive ANALYZE. ! */ ! do_analyze_rel(onerel, vacstmt, false); ! /* ! * If there are child tables, do recursive ANALYZE. ! */ ! if (onerel->rd_rel->relhassubclass) ! do_analyze_rel(onerel, vacstmt, true); /* * Close source relation now, but keep lock so that no one deletes it --- 244,280 ---- MyProc->vacuumFlags |= PROC_IN_ANALYZE; LWLockRelease(ProcArrayLock); ! if (onerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) ! { ! ereport(elevel, ! (errmsg("analyzing \"%s.%s\"", ! get_namespace_name(RelationGetNamespace(onerel)), ! RelationGetRelationName(onerel)))); ! fdwroutine->AnalyzeForeignTable(onerel, vacstmt, elevel); ! } ! else ! { ! /* ! * Do the normal non-recursive ANALYZE. ! */ ! ereport(elevel, ! (errmsg("analyzing \"%s.%s\"", ! get_namespace_name(RelationGetNamespace(onerel)), ! RelationGetRelationName(onerel)))); ! do_analyze_rel(onerel, vacstmt, elevel, false, acquire_sample_rows); ! /* ! * If there are child tables, do recursive ANALYZE. ! */ ! if (onerel->rd_rel->relhassubclass) ! { ! 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 *************** *** 257,264 **** 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) { int attr_cnt, tcnt, --- 296,304 ---- /* * do_analyze_rel() -- analyze one relation, recursively or not */ ! void ! do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, int elevel, ! bool inh, int (*sample_row_acquirer) ()) { int attr_cnt, tcnt, *************** *** 273,278 **** do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh) --- 313,319 ---- numrows; double totalrows, totaldeadrows; + BlockNumber totalpages; HeapTuple *rows; PGRUsage ru0; TimestampTz starttime = 0; *************** *** 281,297 **** 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. --- 322,327 ---- *************** *** 449,459 **** 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); else ! numrows = acquire_sample_rows(onerel, rows, targrows, ! &totalrows, &totaldeadrows); /* * Compute the statistics. Temporary results during the calculations for --- 479,491 ---- */ rows = (HeapTuple *) palloc(targrows * sizeof(HeapTuple)); if (inh) ! numrows = sample_row_acquirer(onerel, rows, targrows, ! &totalrows, &totaldeadrows, ! NULL, elevel); else ! numrows = sample_row_acquirer(onerel, rows, targrows, ! &totalrows, &totaldeadrows, ! &totalpages, elevel); /* * Compute the statistics. Temporary results during the calculations for *************** *** 534,540 **** do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh) */ if (!inh) vac_update_relstats(onerel, ! RelationGetNumberOfBlocks(onerel), totalrows, visibilitymap_count(onerel), hasindex, --- 566,572 ---- */ if (!inh) vac_update_relstats(onerel, ! totalpages, totalrows, visibilitymap_count(onerel), hasindex, *************** *** 1017,1023 **** BlockSampler_Next(BlockSampler bs) */ static int acquire_sample_rows(Relation onerel, HeapTuple *rows, int targrows, ! double *totalrows, double *totaldeadrows) { int numrows = 0; /* # rows now in reservoir */ double samplerows = 0; /* total # rows collected */ --- 1049,1056 ---- */ static int acquire_sample_rows(Relation onerel, HeapTuple *rows, int targrows, ! double *totalrows, double *totaldeadrows, ! BlockNumber *totalpages, int elevel) { int numrows = 0; /* # rows now in reservoir */ double samplerows = 0; /* total # rows collected */ *************** *** 1032,1037 **** acquire_sample_rows(Relation onerel, HeapTuple *rows, int targrows, --- 1065,1072 ---- Assert(targrows > 0); totalblocks = RelationGetNumberOfBlocks(onerel); + if (totalpages) + *totalpages = totalblocks; /* Need a cutoff xmin for HeapTupleSatisfiesVacuum */ OldestXmin = GetOldestXmin(onerel->rd_rel->relisshared, true); *************** *** 1254,1260 **** acquire_sample_rows(Relation onerel, HeapTuple *rows, int targrows, } /* Select a random value R uniformly distributed in (0 - 1) */ ! static double random_fract(void) { return ((double) random() + 1) / ((double) MAX_RANDOM_VALUE + 2); --- 1289,1295 ---- } /* Select a random value R uniformly distributed in (0 - 1) */ ! double random_fract(void) { return ((double) random() + 1) / ((double) MAX_RANDOM_VALUE + 2); *************** *** 1274,1287 **** random_fract(void) * determines the number of records to skip before the next record is * processed. */ ! static 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 get_next_S(double t, int n, double *stateptr) { double S; --- 1309,1322 ---- * determines the number of records to skip before the next record is * processed. */ ! double init_selection_state(int n) { /* Initial value of W (for use when Algorithm Z is first applied) */ return exp(-log(random_fract()) / n); } ! double get_next_S(double t, int n, double *stateptr) { double S; *************** *** 1366,1372 **** get_next_S(double t, int n, double *stateptr) /* * qsort comparator for sorting rows[] array */ ! static int compare_rows(const void *a, const void *b) { HeapTuple ha = *(const HeapTuple *) a; --- 1401,1407 ---- /* * qsort comparator for sorting rows[] array */ ! int compare_rows(const void *a, const void *b) { HeapTuple ha = *(const HeapTuple *) a; *************** *** 1397,1403 **** compare_rows(const void *a, const void *b) */ static int acquire_inherited_sample_rows(Relation onerel, HeapTuple *rows, int targrows, ! double *totalrows, double *totaldeadrows) { List *tableOIDs; Relation *rels; --- 1432,1439 ---- */ static int acquire_inherited_sample_rows(Relation onerel, HeapTuple *rows, int targrows, ! double *totalrows, double *totaldeadrows, ! BlockNumber *totalpages, int elevel) { List *tableOIDs; Relation *rels; *************** *** 1460,1465 **** acquire_inherited_sample_rows(Relation onerel, HeapTuple *rows, int targrows, --- 1496,1503 ---- totalblocks += relblocks[nrels]; nrels++; } + if (totalpages) + *totalpages = totalblocks; /* * Now sample rows from each relation, proportionally to its fraction of *************** *** 1493,1499 **** acquire_inherited_sample_rows(Relation onerel, HeapTuple *rows, int targrows, rows + numrows, childtargrows, &trows, ! &tdrows); /* We may need to convert from child's rowtype to parent's */ if (childrows > 0 && --- 1531,1539 ---- rows + numrows, childtargrows, &trows, ! &tdrows, ! NULL, ! elevel); /* We may need to convert from child's rowtype to parent's */ if (childrows > 0 && *** a/src/backend/commands/tablecmds.c --- b/src/backend/commands/tablecmds.c *************** *** 311,316 **** static void ATPrepSetStatistics(Relation rel, const char *colName, --- 311,318 ---- 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, *************** *** 2887,2892 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, --- 2889,2895 ---- case AT_SetOptions: /* ALTER COLUMN SET ( options ) */ case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */ ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX); + ATPrepSetOptions(rel, cmd->name, cmd->def, lockmode); /* This command never recurses */ pass = AT_PASS_MISC; break; *************** *** 4822,4831 **** 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) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), ! errmsg("\"%s\" is not a table or index", RelationGetRelationName(rel)))); /* Permissions checks */ --- 4825,4835 ---- * allowSystemTableMods to be turned on. */ if (rel->rd_rel->relkind != RELKIND_RELATION && ! 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, index or foreign table", RelationGetRelationName(rel)))); /* Permissions checks */ *************** *** 4894,4899 **** ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE --- 4898,4923 ---- } 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_strncasecmp(def->defname, "n_distinct_inherited", strlen("n_distinct_inherited")) == 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot support option \"n_distinct_inherited\" for foreign tables"))); + } + } + } + + static void ATExecSetOptions(Relation rel, const char *colName, Node *options, bool isReset, LOCKMODE lockmode) { *** a/src/bin/psql/tab-complete.c --- b/src/bin/psql/tab-complete.c *************** *** 399,404 **** static const SchemaQuery Query_for_list_of_tsvf = { --- 399,419 ---- 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", *************** *** 2769,2775 **** 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); /* WHERE */ /* Simple case of the word before the where being the table name */ --- 2784,2790 ---- /* 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_tf, NULL); /* WHERE */ /* Simple case of the word before the where being the table name */ *** a/src/include/commands/vacuum.h --- b/src/include/commands/vacuum.h *************** *** 167,171 **** extern void lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt, --- 167,178 ---- /* in commands/analyze.c */ extern void analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy); + extern void do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, int elevel, + bool inh, int (*sample_row_acquirer) ()); + extern double random_fract(void); + extern double init_selection_state(int n); + extern double get_next_S(double t, int n, double *stateptr); + extern int compare_rows(const void *a, const void *b); + #endif /* VACUUM_H */ *** a/src/include/foreign/fdwapi.h --- b/src/include/foreign/fdwapi.h *************** *** 12,19 **** --- 12,21 ---- #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; *************** *** 68,73 **** typedef void (*ReScanForeignScan_function) (ForeignScanState *node); --- 70,78 ---- 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 *************** *** 88,93 **** typedef struct FdwRoutine --- 93,99 ---- IterateForeignScan_function IterateForeignScan; ReScanForeignScan_function ReScanForeignScan; EndForeignScan_function EndForeignScan; + AnalyzeForeignTable_function AnalyzeForeignTable; } FdwRoutine;