diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index e890770..5e3c06f 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -20,6 +20,7 @@
#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"
@@ -123,6 +124,9 @@ static void fileBeginForeignScan(ForeignScanState *node, int eflags);
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
@@ -136,6 +140,9 @@ static void estimate_size(PlannerInfo *root, RelOptInfo *baserel,
static void estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
FileFdwPlanState *fdw_private,
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);
/*
@@ -155,6 +162,7 @@ file_fdw_handler(PG_FUNCTION_ARGS)
fdwroutine->IterateForeignScan = fileIterateForeignScan;
fdwroutine->ReScanForeignScan = fileReScanForeignScan;
fdwroutine->EndForeignScan = fileEndForeignScan;
+ fdwroutine->AnalyzeForeignTable = fileAnalyzeForeignTable;
PG_RETURN_POINTER(fdwroutine);
}
@@ -645,6 +653,18 @@ fileReScanForeignScan(ForeignScanState *node)
}
/*
+ * fileAnalyzeForeignTable
+ * Analyze foreign table
+ */
+static void
+fileAnalyzeForeignTable(Relation onerel, VacuumStmt *vacstmt, int elevel)
+{
+ do_analyze_rel(onerel, vacstmt,
+ elevel, false,
+ acquire_sample_rows);
+}
+
+/*
* Estimate size of a foreign table.
*
* The main result is returned in baserel->rows. We also set
@@ -657,7 +677,6 @@ estimate_size(PlannerInfo *root, RelOptInfo *baserel,
{
struct stat stat_buf;
BlockNumber pages;
- int tuple_width;
double ntuples;
double nrows;
@@ -677,16 +696,31 @@ estimate_size(PlannerInfo *root, RelOptInfo *baserel,
fdw_private->pages = pages;
- /*
- * 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));
+ if (baserel->pages > 0)
+ {
+ double density;
- ntuples = clamp_row_est((double) stat_buf.st_size / (double) tuple_width);
+ density = baserel->tuples / (double) baserel->pages;
+
+ 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);
+ }
fdw_private->ntuples = ntuples;
@@ -736,3 +770,156 @@ estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
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 total number of rows in the file, and return it into
+ * *totalrows. Note that *totaldeadrows is always set to 0.
+ *
+ * Note that the returned list of rows is not always in order by physical
+ * position in the file. Therefore, correlation estimates derived later
+ * may be meaningless, but it's OK because we don't use the estimates for
+ * now.
+ */
+static int
+acquire_sample_rows(Relation onerel, HeapTuple *rows, int targrows,
+ double *totalrows, double *totaldeadrows,
+ BlockNumber *totalpages, int elevel)
+{
+ int numrows = 0;
+ double rowstoskip = -1; /* -1 means not set yet */
+ double rstate;
+ HeapTuple tuple;
+ TupleDesc tupDesc;
+ Datum *values;
+ bool *nulls;
+ bool found;
+ char *filename;
+ struct stat stat_buf;
+ List *options;
+ CopyState cstate;
+ ErrorContextCallback errcontext;
+
+ Assert(onerel);
+ Assert(targrows > 0);
+
+ tupDesc = RelationGetDescr(onerel);
+ 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;
+
+ *totalrows = 0;
+ for (;;)
+ {
+ /*
+ * 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);
+
+ /*
+ * 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)
+ 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(*totalrows, 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]);
+ rows[k] = heap_copytuple(tuple);
+ }
+
+ rowstoskip -= 1;
+ }
+
+ *totalrows += 1;
+ heap_freetuple(tuple);
+ }
+ *totaldeadrows = 0;
+
+ /* Remove error callback. */
+ error_context_stack = errcontext.previous;
+
+ 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;
+}
diff --git a/contrib/file_fdw/input/file_fdw.source b/contrib/file_fdw/input/file_fdw.source
index 8e3d553..21b6fb4 100644
--- a/contrib/file_fdw/input/file_fdw.source
+++ b/contrib/file_fdw/input/file_fdw.source
@@ -111,6 +111,11 @@ EXECUTE st(100);
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 --git a/contrib/file_fdw/output/file_fdw.source b/contrib/file_fdw/output/file_fdw.source
index 84f0750..c2daafe 100644
--- a/contrib/file_fdw/output/file_fdw.source
+++ b/contrib/file_fdw/output/file_fdw.source
@@ -174,6 +174,21 @@ EXECUTE st(100);
(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 | most_common_elems | most_common_elem_freqs | elem_count_histogram
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-------------------------+-------------+-------------------+------------------------+----------------------
+ 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 --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index f7bf3d8..0a70b47 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -299,6 +299,30 @@ EndForeignScan (ForeignScanState *node);
+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 optional, and if implemented, called when ANALYZE
+ > command is run.
+ The statistics are used by the query planner in order to make good
+ choices of query plans.
+ The function can be implemented by writing a sampling function that
+ acquires a random sample of rows from an external data source and
+ then by calling do_analyze_rel>, where you should pass
+ the sampling function as an argument.
+ Or the function can be directly implemented to get the statistics
+ from an external data source, transform it if neccesary, and store
+ it in the pg_class and pg_statistics system catalogs.
+ The function must be set to NULL if it isn't implemented.
+
+
+
+
ForeignDataWrapper *
GetForeignDataWrapper(Oid fdwid);
diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index 93c3ff5..54d0838 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -284,6 +284,10 @@
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.
diff --git a/doc/src/sgml/ref/alter_foreign_table.sgml b/doc/src/sgml/ref/alter_foreign_table.sgml
index c4cdaa8..3819007 100644
--- a/doc/src/sgml/ref/alter_foreign_table.sgml
+++ b/doc/src/sgml/ref/alter_foreign_table.sgml
@@ -36,6 +36,9 @@ ALTER FOREIGN TABLE [ IF EXISTS ] namecolumn [ 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'] [, ... ])
@@ -104,6 +107,45 @@ ALTER FOREIGN TABLE [ IF EXISTS ] name
+ 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
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 8c9057b..524a1c9 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -39,9 +39,11 @@ 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.
+ current database except for foreign tables. With a parameter,
+ ANALYZE examines only that table. For a foreign table, it is
+ necessary to specify 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,7 +65,8 @@ ANALYZE [ VERBOSE ] [ table [ (
The name (possibly schema-qualified) of a specific table to
- analyze. Defaults to all tables in the current database.
+ analyze. Defaults to all tables in the current database except
+ for foreign tables.
@@ -137,7 +140,9 @@ ANALYZE [ VERBOSE ] [ table [ ( ANALYZE is run.
To avoid this, raise the amount of statistics collected by
- ANALYZE, as described below.
+ 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.
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 9cd6e67..442ee2f 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -23,6 +23,7 @@
#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,6 +31,8 @@
#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,14 +81,11 @@ 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);
@@ -96,15 +96,15 @@ 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_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);
+ double *totalrows, double *totaldeadrows,
+ BlockNumber *totalpages, int elevel);
+static int compare_rows(const void *a, const void *b);
static void update_attstats(Oid relid, bool inh,
int natts, VacAttrStats **vacattrstats);
static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
@@ -117,7 +117,9 @@ static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
void
analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
{
+ int elevel;
Relation onerel;
+ FdwRoutine *fdwroutine;
/* Set up static variables */
if (vacstmt->options & VACOPT_VERBOSE)
@@ -182,10 +184,12 @@ 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.
+ * 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)
+ 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))
@@ -209,7 +213,9 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
}
/*
- * We can ANALYZE any table except pg_statistic. See update_attstats
+ * We can ANALYZE any table except pg_statistic. See update_attstats.
+ * A foreign table can be ANALYZEed if the AnalyzeForeignTable callback
+ * routine is provided.
*/
if (RelationGetRelid(onerel) == StatisticRelationId)
{
@@ -217,6 +223,20 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
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.
*/
@@ -227,13 +247,32 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
/*
* Do the normal non-recursive ANALYZE.
*/
- do_analyze_rel(onerel, vacstmt, false);
+ ereport(elevel,
+ (errmsg("analyzing \"%s.%s\"",
+ get_namespace_name(RelationGetNamespace(onerel)),
+ RelationGetRelationName(onerel))));
+
+ if (onerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ fdwroutine->AnalyzeForeignTable(onerel, vacstmt, elevel);
+ else
+ do_analyze_rel(onerel, vacstmt, elevel, false, acquire_sample_rows);
/*
* If there are child tables, do recursive ANALYZE.
*/
if (onerel->rd_rel->relhassubclass)
- do_analyze_rel(onerel, vacstmt, true);
+ {
+ Assert(onerel->rd_rel->relkind != RELKIND_FOREIGN_TABLE);
+
+ 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
@@ -255,8 +294,9 @@ 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)
+void
+do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, int elevel,
+ bool inh, AcquireSampleRowFunc acquirefunc)
{
int attr_cnt,
tcnt,
@@ -271,6 +311,7 @@ do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh)
numrows;
double totalrows,
totaldeadrows;
+ BlockNumber totalpages;
HeapTuple *rows;
PGRUsage ru0;
TimestampTz starttime = 0;
@@ -279,17 +320,6 @@ 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.
@@ -447,11 +477,13 @@ 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);
+ numrows = acquirefunc(onerel, rows, targrows,
+ &totalrows, &totaldeadrows,
+ NULL, elevel);
else
- numrows = acquire_sample_rows(onerel, rows, targrows,
- &totalrows, &totaldeadrows);
+ numrows = acquirefunc(onerel, rows, targrows,
+ &totalrows, &totaldeadrows,
+ &totalpages, elevel);
/*
* Compute the statistics. Temporary results during the calculations for
@@ -532,7 +564,7 @@ do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, bool inh)
*/
if (!inh)
vac_update_relstats(onerel,
- RelationGetNumberOfBlocks(onerel),
+ totalpages,
totalrows,
visibilitymap_count(onerel),
hasindex,
@@ -1015,7 +1047,8 @@ BlockSampler_Next(BlockSampler bs)
*/
static int
acquire_sample_rows(Relation onerel, HeapTuple *rows, int targrows,
- double *totalrows, double *totaldeadrows)
+ double *totalrows, double *totaldeadrows,
+ BlockNumber *totalpages, int elevel)
{
int numrows = 0; /* # rows now in reservoir */
double samplerows = 0; /* total # rows collected */
@@ -1030,6 +1063,8 @@ acquire_sample_rows(Relation onerel, HeapTuple *rows, int targrows,
Assert(targrows > 0);
totalblocks = RelationGetNumberOfBlocks(onerel);
+ if (totalpages)
+ *totalpages = totalblocks;
/* Need a cutoff xmin for HeapTupleSatisfiesVacuum */
OldestXmin = GetOldestXmin(onerel->rd_rel->relisshared, true);
@@ -1252,7 +1287,7 @@ acquire_sample_rows(Relation onerel, HeapTuple *rows, int targrows,
}
/* Select a random value R uniformly distributed in (0 - 1) */
-static double
+double
random_fract(void)
{
return ((double) random() + 1) / ((double) MAX_RANDOM_VALUE + 2);
@@ -1272,14 +1307,14 @@ random_fract(void)
* determines the number of records to skip before the next record is
* processed.
*/
-static double
+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
+double
get_next_S(double t, int n, double *stateptr)
{
double S;
@@ -1395,7 +1430,8 @@ compare_rows(const void *a, const void *b)
*/
static int
acquire_inherited_sample_rows(Relation onerel, HeapTuple *rows, int targrows,
- double *totalrows, double *totaldeadrows)
+ double *totalrows, double *totaldeadrows,
+ BlockNumber *totalpages, int elevel)
{
List *tableOIDs;
Relation *rels;
@@ -1458,6 +1494,8 @@ acquire_inherited_sample_rows(Relation onerel, HeapTuple *rows, int targrows,
totalblocks += relblocks[nrels];
nrels++;
}
+ if (totalpages)
+ *totalpages = totalblocks;
/*
* Now sample rows from each relation, proportionally to its fraction of
@@ -1491,7 +1529,9 @@ acquire_inherited_sample_rows(Relation onerel, HeapTuple *rows, int targrows,
rows + numrows,
childtargrows,
&trows,
- &tdrows);
+ &tdrows,
+ NULL,
+ elevel);
/* We may need to convert from child's rowtype to parent's */
if (childrows > 0 &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 9853686..3031496 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -320,6 +320,8 @@ static void ATPrepSetStatistics(Relation rel, const char *colName,
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,
@@ -3021,7 +3023,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break;
case AT_SetOptions: /* ALTER COLUMN SET ( options ) */
case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */
- ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX);
+ ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX | ATT_FOREIGN_TABLE);
+ ATPrepSetOptions(rel, cmd->name, cmd->def, lockmode);
/* This command never recurses */
pass = AT_PASS_MISC;
break;
@@ -4999,10 +5002,11 @@ 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)
+ 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 or index",
+ errmsg("\"%s\" is not a table, index or foreign table",
RelationGetRelationName(rel))));
/* Permissions checks */
@@ -5071,6 +5075,26 @@ ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
}
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_strcasecmp(def->defname, "n_distinct_inherited") == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot set \"n_distinct_inherited\" for foreign tables")));
+ }
+ }
+}
+
+static void
ATExecSetOptions(Relation rel, const char *colName, Node *options,
bool isReset, LOCKMODE lockmode)
{
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index dc2248b..b3d2078 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1104,7 +1104,7 @@ describeOneTableDetails(const char *schemaname,
bool printTableInitialized = false;
int i;
char *view_def = NULL;
- char *headers[6];
+ char *headers[7];
char **seq_values = NULL;
char **modifiers = NULL;
char **ptr;
@@ -1395,7 +1395,7 @@ describeOneTableDetails(const char *schemaname,
if (verbose)
{
headers[cols++] = gettext_noop("Storage");
- if (tableinfo.relkind == 'r')
+ if (tableinfo.relkind == 'r' || tableinfo.relkind == 'f')
headers[cols++] = gettext_noop("Stats target");
/* Column comments, if the relkind supports this feature. */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
@@ -1498,7 +1498,7 @@ describeOneTableDetails(const char *schemaname,
false, false);
/* Statistics target, if the relkind supports this feature */
- if (tableinfo.relkind == 'r')
+ if (tableinfo.relkind == 'r' || tableinfo.relkind == 'f')
{
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
false, false);
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 975d655..d113adf 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -409,6 +409,21 @@ static const SchemaQuery Query_for_list_of_tsvf = {
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",
@@ -2833,7 +2848,7 @@ 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);
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tf, NULL);
/* WHERE */
/* Simple case of the word before the where being the table name */
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 3deee66..f6a3c59 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -58,6 +58,14 @@
*/
typedef struct VacAttrStats *VacAttrStatsP;
+typedef int (*AcquireSampleRowFunc) (Relation onerel,
+ HeapTuple *rows,
+ int targrows,
+ double *totalrows,
+ double *totaldeadrows,
+ BlockNumber *totalpages,
+ int elevel);
+
typedef Datum (*AnalyzeAttrFetchFunc) (VacAttrStatsP stats, int rownum,
bool *isNull);
@@ -139,6 +147,14 @@ extern int vacuum_freeze_table_age;
/* in commands/vacuum.c */
+extern void analyze_rel(Oid relid, VacuumStmt *vacstmt,
+ BufferAccessStrategy bstrategy);
+extern void do_analyze_rel(Relation onerel, VacuumStmt *vacstmt, int elevel,
+ bool inh, AcquireSampleRowFunc acquirefunc);
+extern double random_fract(void);
+extern double init_selection_state(int n);
+extern double get_next_S(double t, int n, double *stateptr);
+
extern void vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 854f177..fbb5b0b 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -12,8 +12,10 @@
#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;
@@ -50,6 +52,9 @@ typedef void (*ReScanForeignScan_function) (ForeignScanState *node);
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
@@ -64,6 +69,11 @@ typedef struct FdwRoutine
{
NodeTag type;
+ /*
+ * These handlers are required to execute simple scans on a foreign
+ * table. If any of them was set to NULL, scans on a foreign table
+ * managed by FDW would fail.
+ */
GetForeignRelSize_function GetForeignRelSize;
GetForeignPaths_function GetForeignPaths;
GetForeignPlan_function GetForeignPlan;
@@ -72,6 +82,12 @@ typedef struct FdwRoutine
IterateForeignScan_function IterateForeignScan;
ReScanForeignScan_function ReScanForeignScan;
EndForeignScan_function EndForeignScan;
+
+ /*
+ * Handlers below are optional. You can set any of them to NULL to
+ * tell PostgreSQL that FDW doesn't have the capability.
+ */
+ AnalyzeForeignTable_function AnalyzeForeignTable;
} FdwRoutine;
diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out
index ba86883..f1379a6 100644
--- a/src/test/regress/expected/foreign_data.out
+++ b/src/test/regress/expected/foreign_data.out
@@ -679,12 +679,12 @@ CREATE FOREIGN TABLE ft1 (
COMMENT ON FOREIGN TABLE ft1 IS 'ft1';
COMMENT ON COLUMN ft1.c1 IS 'ft1.c1';
\d+ ft1
- Foreign table "public.ft1"
- Column | Type | Modifiers | FDW Options | Storage | Description
---------+---------+-----------+--------------------------------+----------+-------------
- c1 | integer | not null | ("param 1" 'val1') | plain | ft1.c1
- c2 | text | | (param2 'val2', param3 'val3') | extended |
- c3 | date | | | plain |
+ Foreign table "public.ft1"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+--------+---------+-----------+--------------------------------+----------+--------------+-------------
+ c1 | integer | not null | ("param 1" 'val1') | plain | | ft1.c1
+ c2 | text | | (param2 'val2', param3 'val3') | extended | |
+ c3 | date | | | plain | |
Server: s0
FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
Has OIDs: no
@@ -730,19 +730,24 @@ ERROR: cannot alter system column "xmin"
ALTER FOREIGN TABLE ft1 ALTER COLUMN c7 OPTIONS (ADD p1 'v1', ADD p2 'v2'),
ALTER COLUMN c8 OPTIONS (ADD p1 'v1', ADD p2 'v2');
ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 OPTIONS (SET p2 'V2', DROP p1);
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 SET STATISTICS 10000;
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 SET (n_distinct = 100);
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 SET (n_distinct_inherited = 100); -- ERROR
+ERROR: cannot set "n_distinct_inherited" for foreign tables
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET STATISTICS -1;
\d+ ft1
- Foreign table "public.ft1"
- Column | Type | Modifiers | FDW Options | Storage | Description
---------+---------+-----------+--------------------------------+----------+-------------
- c1 | integer | not null | ("param 1" 'val1') | plain |
- c2 | text | | (param2 'val2', param3 'val3') | extended |
- c3 | date | | | plain |
- c4 | integer | | | plain |
- c6 | integer | not null | | plain |
- c7 | integer | | (p1 'v1', p2 'v2') | plain |
- c8 | text | | (p2 'V2') | extended |
- c9 | integer | | | plain |
- c10 | integer | | (p1 'v1') | plain |
+ Foreign table "public.ft1"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+--------+---------+-----------+--------------------------------+----------+--------------+-------------
+ c1 | integer | not null | ("param 1" 'val1') | plain | 10000 |
+ c2 | text | | (param2 'val2', param3 'val3') | extended | |
+ c3 | date | | | plain | |
+ c4 | integer | | | plain | |
+ c6 | integer | not null | | plain | |
+ c7 | integer | | (p1 'v1', p2 'v2') | plain | |
+ c8 | text | | (p2 'V2') | extended | |
+ c9 | integer | | | plain | |
+ c10 | integer | | (p1 'v1') | plain | |
Server: s0
FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
Has OIDs: no
diff --git a/src/test/regress/sql/foreign_data.sql b/src/test/regress/sql/foreign_data.sql
index 0c95672..03b5680 100644
--- a/src/test/regress/sql/foreign_data.sql
+++ b/src/test/regress/sql/foreign_data.sql
@@ -307,6 +307,10 @@ ALTER FOREIGN TABLE ft1 ALTER COLUMN xmin OPTIONS (ADD p1 'v1'); -- ERROR
ALTER FOREIGN TABLE ft1 ALTER COLUMN c7 OPTIONS (ADD p1 'v1', ADD p2 'v2'),
ALTER COLUMN c8 OPTIONS (ADD p1 'v1', ADD p2 'v2');
ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 OPTIONS (SET p2 'V2', DROP p1);
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 SET STATISTICS 10000;
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 SET (n_distinct = 100);
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 SET (n_distinct_inherited = 100); -- ERROR
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET STATISTICS -1;
\d+ ft1
-- can't change the column type if it's used elsewhere
CREATE TABLE use_ft1_column_type (x ft1);