diff -crNB original/postgresql-9.1beta1/contrib/file_fdw/file_fdw.c changed/postgresql-9.1beta1/contrib/file_fdw/file_fdw.c *** original/postgresql-9.1beta1/contrib/file_fdw/file_fdw.c 2011-04-28 06:17:22.000000000 +0900 --- changed/postgresql-9.1beta1/contrib/file_fdw/file_fdw.c 2011-09-12 15:19:28.000000000 +0900 *************** *** 15,29 **** --- 15,41 ---- #include #include + #include "access/htup.h" #include "access/reloptions.h" + #include "access/transam.h" #include "catalog/pg_foreign_table.h" #include "commands/copy.h" + #include "commands/dbcommands.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 "optimizer/cost.h" + #include "optimizer/plancat.h" + #include "pgstat.h" + #include "parser/parse_relation.h" + #include "utils/attoptcache.h" + #include "utils/guc.h" + #include "utils/lsyscache.h" + #include "utils/memutils.h" + /* #include "utils/pg_rusage.h" */ PG_MODULE_MAGIC; *************** *** 101,106 **** --- 113,119 ---- static TupleTableSlot *fileIterateForeignScan(ForeignScanState *node); static void fileReScanForeignScan(ForeignScanState *node); static void fileEndForeignScan(ForeignScanState *node); + static void fileAnalyzeForeignTable(Relation relation, VacuumStmt *vacstmt, int elevel); /* * Helper functions *************** *** 111,117 **** 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 --- 124,131 ---- static void estimate_costs(PlannerInfo *root, RelOptInfo *baserel, const char *filename, Cost *startup_cost, Cost *total_cost); ! static void file_do_analyze_rel(Relation relation, VacuumStmt *vacstmt, int elevel, const char *filename, CopyState cstate); ! static int file_acquire_sample_rows(Relation onerel, int elevel, CopyState cstate, HeapTuple *rows, int targrows, double *totalrows); /* * Foreign-data wrapper handler function: return a struct with pointers *************** *** 128,133 **** --- 142,148 ---- fdwroutine->IterateForeignScan = fileIterateForeignScan; fdwroutine->ReScanForeignScan = fileReScanForeignScan; fdwroutine->EndForeignScan = fileEndForeignScan; + fdwroutine->AnalyzeForeignTable = fileAnalyzeForeignTable; PG_RETURN_POINTER(fdwroutine); } *************** *** 464,469 **** --- 479,509 ---- } /* + * fileAnalyzeForeignTable + * Analyze table + */ + static void + fileAnalyzeForeignTable(Relation relation, VacuumStmt *vacstmt, int elevel) + { + char *filename; + List *options; + CopyState cstate; + + /* Fetch options of foreign table */ + fileGetOptions(RelationGetRelid(relation), &filename, &options); + + /* + * Create CopyState from FDW options. We always acquire all columns, so + * as to match the expected ScanTupleSlot signature. + */ + cstate = BeginCopyFrom(relation, filename, NIL, options); + + file_do_analyze_rel(relation, vacstmt, elevel, filename, cstate); + + EndCopyFrom(cstate); + } + + /* * Estimate costs of scanning a foreign table. */ static void *************** *** 473,479 **** { struct stat stat_buf; BlockNumber pages; ! int tuple_width; double ntuples; double nrows; Cost run_cost = 0; --- 513,520 ---- { struct stat stat_buf; BlockNumber pages; ! BlockNumber relpages; ! double reltuples; double ntuples; double nrows; Cost run_cost = 0; *************** *** 493,508 **** 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 --- 534,565 ---- 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 *************** *** 534,536 **** --- 591,960 ---- run_cost += cpu_per_tuple * ntuples; *total_cost = *startup_cost + run_cost; } + + /* + * file_do_analyze_rel() -- analyze one foreign table + */ + static void + file_do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, int elevel, const char *filename, CopyState cstate) + { + int i, + attr_cnt, + tcnt, + numrows = 0, + targrows; + double totalrows = 0; + HeapTuple *rows; + struct stat stat_buf; + BlockNumber pages; + VacAttrStats **vacattrstats; + MemoryContext caller_context; + MemoryContext anl_context; + + 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. + */ + anl_context = AllocSetContextCreate(CurrentMemoryContext, + "Analyze", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + caller_context = MemoryContextSwitchTo(anl_context); + + /* + * Switch to the table owner's userid, so that any index functions are run + * as that user. Also lock down security-restricted operations and + * arrange to make GUC variable changes local to this command. + */ + /* + GetUserIdAndSecContext(&save_userid, &save_sec_context); + SetUserIdAndSecContext(onerel->rd_rel->relowner, + save_sec_context | SECURITY_RESTRICTED_OPERATION); + save_nestlevel = NewGUCNestLevel(); + */ + + /* + * Determine which columns to analyze + * + * Note that system attributes are never analyzed. + */ + if (vacstmt->va_cols != NIL) + { + ListCell *le; + + vacattrstats = (VacAttrStats **) palloc(list_length(vacstmt->va_cols) * + sizeof(VacAttrStats *)); + tcnt = 0; + foreach(le, vacstmt->va_cols) + { + char *col = strVal(lfirst(le)); + + i = attnameAttNum(onerel, col, false); + if (i == InvalidAttrNumber) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + col, RelationGetRelationName(onerel)))); + vacattrstats[tcnt] = examine_attribute(onerel, i, NULL, anl_context); + if (vacattrstats[tcnt] != NULL) + tcnt++; + } + attr_cnt = tcnt; + } + else + { + attr_cnt = onerel->rd_att->natts; + vacattrstats = (VacAttrStats **) palloc(attr_cnt * sizeof(VacAttrStats *)); + tcnt = 0; + for (i = 1; i <= attr_cnt; i++) + { + vacattrstats[tcnt] = examine_attribute(onerel, i, NULL, anl_context); + if (vacattrstats[tcnt] != NULL) + tcnt++; + } + attr_cnt = tcnt; + } + + /* + * Quit if no analyzable columns. + */ + if (attr_cnt <= 0) + goto cleanup; + + /* + * Determine how many rows we need to sample, using the worst case from + * all analyzable columns. We use a lower bound of 100 rows to avoid + * possible overflow in Vitter's algorithm. + */ + targrows = 100; + for (i = 0; i < attr_cnt; i++) + { + if (targrows < vacattrstats[i]->minrows) + targrows = vacattrstats[i]->minrows; + } + + /* + * Acquire the sample rows + */ + rows = (HeapTuple *) palloc(targrows * sizeof(HeapTuple)); + numrows = file_acquire_sample_rows(onerel, elevel, cstate, rows, targrows, &totalrows); + + /* + * Compute the statistics. Temporary results during the calculations for + * each column are stored in a child context. The calc routines are + * responsible to make sure that whatever they store into the VacAttrStats + * structure is allocated in anl_context. + */ + if (numrows > 0) + { + MemoryContext col_context, old_context; + + col_context = AllocSetContextCreate(anl_context, + "Analyze Column", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + old_context = MemoryContextSwitchTo(col_context); + + for (i = 0; i < attr_cnt; i++) + { + VacAttrStats *stats = vacattrstats[i]; + AttributeOpts *aopt = get_attribute_options(onerel->rd_id, stats->attr->attnum); + + stats->rows = rows; + stats->tupDesc = onerel->rd_att; + (*stats->compute_stats) (stats, + std_fetch_func, + numrows, + totalrows); + + /* + * If the appropriate flavor of the n_distinct option is + * specified, override with the corresponding value. + */ + if (aopt != NULL) + { + float8 n_distinct = aopt->n_distinct; + + if (n_distinct != 0.0) + stats->stadistinct = n_distinct; + } + + MemoryContextResetAndDeleteChildren(col_context); + } + + MemoryContextSwitchTo(old_context); + MemoryContextDelete(col_context); + + /* + * Emit the completed stats rows into pg_statistic, replacing any + * 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(onerel->rd_id, false, attr_cnt, vacattrstats); + } + + /* + * Get size of the file. It might not be there at plan time, though, in + * which case we have to use a default estimate. + */ + if (stat(filename, &stat_buf) < 0) + stat_buf.st_size = 10 * BLCKSZ; + + /* + * Convert size to pages for use in I/O cost estimate below. + */ + pages = (stat_buf.st_size + (BLCKSZ - 1)) / BLCKSZ; + if (pages < 1) + pages = 1; + + /* + * Update pages/tuples stats in pg_class. + */ + vac_update_relstats(onerel, pages, totalrows, false, InvalidTransactionId); + + /* + * Report ANALYZE to the stats collector, too; likewise, tell it to adopt + * these numbers only if we're not inside a VACUUM that got a better + * number. However, a call with inh = true shouldn't reset the stats. + */ + pgstat_report_analyze(onerel, true, totalrows, 0); + + /* We skip to here if there were no analyzable columns */ + cleanup: + + /* Restore current context and release memory */ + MemoryContextSwitchTo(caller_context); + MemoryContextDelete(anl_context); + anl_context = NULL; + } + + /* + * file_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 + file_acquire_sample_rows(Relation onerel, int elevel, CopyState cstate, HeapTuple *rows, int targrows, double *totalrows) + { + 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; + 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)); + + /* Prepare for sampling rows */ + rstate = init_selection_state(targrows); + + for (;;) + { + sample_it = true; + + /* Set up callback to identify error line number. */ + errcontext.callback = CopyFromErrorCallback; + errcontext.arg = (void *) cstate; + errcontext.previous = error_context_stack; + error_context_stack = &errcontext; + + found = NextCopyFrom(cstate, NULL, values, nulls, NULL); + + /* Remove error callback. */ + error_context_stack = errcontext.previous; + + 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); + } + + /* + * 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; + + pfree(values); + pfree(nulls); + + /* + * Emit some interesting relation info + */ + ereport(elevel, + (errmsg("\"%s\": scanned, " + "containing %d rows; " + "%d rows in sample", + RelationGetRelationName(onerel), (int) samplerows, numrows))); + + return numrows; + } diff -crNB original/postgresql-9.1beta1/contrib/file_fdw/input/file_fdw.source changed/postgresql-9.1beta1/contrib/file_fdw/input/file_fdw.source *** original/postgresql-9.1beta1/contrib/file_fdw/input/file_fdw.source 2011-04-28 06:17:22.000000000 +0900 --- changed/postgresql-9.1beta1/contrib/file_fdw/input/file_fdw.source 2011-09-04 19:29:23.000000000 +0900 *************** *** 94,99 **** --- 94,104 ---- EXECUTE st(100); DEALLOCATE st; + -- statistics collection tests + ANALYZE agg_csv; + SELECT relpages, reltuples FROM pg_class WHERE relname = 'agg_csv'; + SELECT * FROM pg_stats WHERE tablename = 'agg_csv'; + -- tableoid SELECT tableoid::regclass, b FROM agg_csv; diff -crNB original/postgresql-9.1beta1/contrib/file_fdw/output/file_fdw.source changed/postgresql-9.1beta1/contrib/file_fdw/output/file_fdw.source *** original/postgresql-9.1beta1/contrib/file_fdw/output/file_fdw.source 2011-04-28 06:17:22.000000000 +0900 --- changed/postgresql-9.1beta1/contrib/file_fdw/output/file_fdw.source 2011-09-04 19:31:15.000000000 +0900 *************** *** 141,146 **** --- 141,161 ---- (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 diff -crNB original/postgresql-9.1beta1/src/backend/commands/analyze.c changed/postgresql-9.1beta1/src/backend/commands/analyze.c *** original/postgresql-9.1beta1/src/backend/commands/analyze.c 2011-04-28 06:17:22.000000000 +0900 --- changed/postgresql-9.1beta1/src/backend/commands/analyze.c 2011-09-12 13:21:04.000000000 +0900 *************** *** 24,35 **** --- 24,38 ---- #include "catalog/index.h" #include "catalog/indexing.h" #include "catalog/namespace.h" + #include "catalog/pg_class.h" #include "catalog/pg_collation.h" #include "catalog/pg_inherits_fn.h" #include "catalog/pg_namespace.h" #include "commands/dbcommands.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" *************** *** 94,114 **** 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); ! 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); ! static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull); static bool std_typanalyze(VacAttrStats *stats); --- 97,117 ---- 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); */ ! /* 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); */ ! /* static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull); */ static bool std_typanalyze(VacAttrStats *stats); *************** *** 129,134 **** --- 132,138 ---- BufferAccessStrategy bstrategy, bool update_reltuples) { Relation onerel; + MemoryContext caller_context; /* Set up static variables */ if (vacstmt->options & VACOPT_VERBOSE) *************** *** 196,202 **** * 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)) --- 200,207 ---- * 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 || ! onerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)) { /* No need for a WARNING if we already complained during VACUUM */ if (!(vacstmt->options & VACOPT_VACUUM)) *************** *** 238,250 **** /* * Do the normal non-recursive ANALYZE. */ ! do_analyze_rel(onerel, vacstmt, update_reltuples, false); ! /* ! * If there are child tables, do recursive ANALYZE. ! */ ! if (onerel->rd_rel->relhassubclass) ! do_analyze_rel(onerel, vacstmt, false, true); /* * Close source relation now, but keep lock so that no one deletes it --- 243,272 ---- /* * Do the normal non-recursive ANALYZE. */ ! if (onerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) ! { ! ForeignDataWrapper *wrapper; ! ForeignServer *server; ! ForeignTable *table; ! FdwRoutine *fdwroutine; ! ! table = GetForeignTable(RelationGetRelid(onerel)); ! server = GetForeignServer(table->serverid); ! wrapper = GetForeignDataWrapper(server->fdwid); ! fdwroutine = GetFdwRoutine(wrapper->fdwhandler); ! fdwroutine->AnalyzeForeignTable(onerel, vacstmt, elevel); ! } ! else ! { ! do_analyze_rel(onerel, vacstmt, update_reltuples, false); ! ! /* ! * If there are child tables, do recursive ANALYZE. ! */ ! if (onerel->rd_rel->relhassubclass) ! do_analyze_rel(onerel, vacstmt, false, true); ! } /* * Close source relation now, but keep lock so that no one deletes it *************** *** 354,360 **** (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("column \"%s\" of relation \"%s\" does not exist", col, RelationGetRelationName(onerel)))); ! vacattrstats[tcnt] = examine_attribute(onerel, i, NULL); if (vacattrstats[tcnt] != NULL) tcnt++; } --- 376,382 ---- (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("column \"%s\" of relation \"%s\" does not exist", col, RelationGetRelationName(onerel)))); ! vacattrstats[tcnt] = examine_attribute(onerel, i, NULL, anl_context); if (vacattrstats[tcnt] != NULL) tcnt++; } *************** *** 368,374 **** tcnt = 0; for (i = 1; i <= attr_cnt; i++) { ! vacattrstats[tcnt] = examine_attribute(onerel, i, NULL); if (vacattrstats[tcnt] != NULL) tcnt++; } --- 390,396 ---- tcnt = 0; for (i = 1; i <= attr_cnt; i++) { ! vacattrstats[tcnt] = examine_attribute(onerel, i, NULL, anl_context); if (vacattrstats[tcnt] != NULL) tcnt++; } *************** *** 423,429 **** indexkey = (Node *) lfirst(indexpr_item); indexpr_item = lnext(indexpr_item); thisdata->vacattrstats[tcnt] = ! examine_attribute(Irel[ind], i + 1, indexkey); if (thisdata->vacattrstats[tcnt] != NULL) { tcnt++; --- 445,451 ---- indexkey = (Node *) lfirst(indexpr_item); indexpr_item = lnext(indexpr_item); thisdata->vacattrstats[tcnt] = ! examine_attribute(Irel[ind], i + 1, indexkey, anl_context); if (thisdata->vacattrstats[tcnt] != NULL) { tcnt++; *************** *** 825,832 **** * 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]; HeapTuple typtuple; --- 847,854 ---- * 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, MemoryContext anl_context) { Form_pg_attribute attr = onerel->rd_att->attrs[attnum - 1]; HeapTuple typtuple; *************** *** 1272,1278 **** } /* Select a random value R uniformly distributed in (0 - 1) */ ! static double random_fract(void) { return ((double) random() + 1) / ((double) MAX_RANDOM_VALUE + 2); --- 1294,1300 ---- } /* Select a random value R uniformly distributed in (0 - 1) */ ! double random_fract(void) { return ((double) random() + 1) / ((double) MAX_RANDOM_VALUE + 2); *************** *** 1292,1305 **** * 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; --- 1314,1327 ---- * 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; *************** *** 1384,1390 **** /* * qsort comparator for sorting rows[] array */ ! static int compare_rows(const void *a, const void *b) { HeapTuple ha = *(HeapTuple *) a; --- 1406,1412 ---- /* * qsort comparator for sorting rows[] array */ ! int compare_rows(const void *a, const void *b) { HeapTuple ha = *(HeapTuple *) a; *************** *** 1578,1584 **** * 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; --- 1600,1606 ---- * ANALYZE the same table concurrently. Presently, we lock that out * by taking a self-exclusive lock on the relation in analyze_rel(). */ ! void update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats) { Relation sd; *************** *** 1712,1718 **** * This exists to provide some insulation between compute_stats routines * and the actual storage of the sample data. */ ! static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull) { int attnum = stats->tupattnum; --- 1734,1740 ---- * This exists to provide some insulation between compute_stats routines * and the actual storage of the sample data. */ ! Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull) { int attnum = stats->tupattnum; *************** *** 1728,1734 **** * We have not bothered to construct index tuples, instead the data is * just in Datum arrays. */ ! static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull) { int i; --- 1750,1756 ---- * We have not bothered to construct index tuples, instead the data is * just in Datum arrays. */ ! Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull) { int i; diff -crNB original/postgresql-9.1beta1/src/include/commands/vacuum.h changed/postgresql-9.1beta1/src/include/commands/vacuum.h *** original/postgresql-9.1beta1/src/include/commands/vacuum.h 2011-04-28 06:17:22.000000000 +0900 --- changed/postgresql-9.1beta1/src/include/commands/vacuum.h 2011-09-12 15:03:51.000000000 +0900 *************** *** 130,137 **** /* GUC parameters */ ! extern PGDLLIMPORT int default_statistics_target; /* PGDLLIMPORT for ! * PostGIS */ extern int vacuum_freeze_min_age; extern int vacuum_freeze_table_age; --- 130,137 ---- /* GUC parameters */ ! extern PGDLLIMPORT int default_statistics_target; /* PGDLLIMPORT for ! * PostGIS */ extern int vacuum_freeze_min_age; extern int vacuum_freeze_table_age; *************** *** 161,166 **** /* in commands/analyze.c */ extern void analyze_rel(Oid relid, VacuumStmt *vacstmt, ! BufferAccessStrategy bstrategy, bool update_reltuples); #endif /* VACUUM_H */ --- 161,175 ---- /* in commands/analyze.c */ extern void analyze_rel(Oid relid, VacuumStmt *vacstmt, ! BufferAccessStrategy bstrategy, bool update_reltuples); ! extern VacAttrStats * examine_attribute(Relation onerel, int attnum, Node *index_expr, ! MemoryContext anl_context); ! 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); ! extern void update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats); ! extern Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull); ! extern Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull); #endif /* VACUUM_H */ diff -crNB original/postgresql-9.1beta1/src/include/foreign/fdwapi.h changed/postgresql-9.1beta1/src/include/foreign/fdwapi.h *** original/postgresql-9.1beta1/src/include/foreign/fdwapi.h 2011-04-28 06:17:22.000000000 +0900 --- changed/postgresql-9.1beta1/src/include/foreign/fdwapi.h 2011-09-12 15:08:31.000000000 +0900 *************** *** 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 **** --- 69,77 ---- 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 **** --- 92,98 ---- IterateForeignScan_function IterateForeignScan; ReScanForeignScan_function ReScanForeignScan; EndForeignScan_function EndForeignScan; + AnalyzeForeignTable_function AnalyzeForeignTable; } FdwRoutine;