Commit 263d9de6 authored by Tom Lane's avatar Tom Lane

Allow statistics to be collected for foreign tables.

ANALYZE now accepts foreign tables and allows the table's FDW to control
how the sample rows are collected.  (But only manual ANALYZEs will touch
foreign tables, for the moment, since among other things it's not very
clear how to handle remote permissions checks in an auto-analyze.)

contrib/file_fdw is extended to support this.

Etsuro Fujita, reviewed by Shigeru Hanada, some further tweaking by me.
parent 8cb53654
......@@ -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"
......@@ -28,6 +29,7 @@
#include "optimizer/pathnode.h"
#include "optimizer/planmain.h"
#include "optimizer/restrictinfo.h"
#include "utils/memutils.h"
#include "utils/rel.h"
PG_MODULE_MAGIC;
......@@ -123,6 +125,7 @@ static void fileBeginForeignScan(ForeignScanState *node, int eflags);
static TupleTableSlot *fileIterateForeignScan(ForeignScanState *node);
static void fileReScanForeignScan(ForeignScanState *node);
static void fileEndForeignScan(ForeignScanState *node);
static AcquireSampleRowsFunc fileAnalyzeForeignTable(Relation relation);
/*
* Helper functions
......@@ -136,6 +139,10 @@ 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 file_acquire_sample_rows(Relation onerel, int elevel,
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows,
BlockNumber *totalpages);
/*
......@@ -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);
}
......@@ -613,6 +621,23 @@ fileIterateForeignScan(ForeignScanState *node)
return slot;
}
/*
* fileReScanForeignScan
* Rescan table, possibly with new parameters
*/
static void
fileReScanForeignScan(ForeignScanState *node)
{
FileFdwExecutionState *festate = (FileFdwExecutionState *) node->fdw_state;
EndCopyFrom(festate->cstate);
festate->cstate = BeginCopyFrom(node->ss.ss_currentRelation,
festate->filename,
NIL,
festate->options);
}
/*
* fileEndForeignScan
* Finish scanning foreign table and dispose objects used for this scan
......@@ -628,20 +653,13 @@ fileEndForeignScan(ForeignScanState *node)
}
/*
* fileReScanForeignScan
* Rescan table, possibly with new parameters
* fileAnalyzeForeignTable
* Test whether analyzing this foreign table is supported
*/
static void
fileReScanForeignScan(ForeignScanState *node)
static AcquireSampleRowsFunc
fileAnalyzeForeignTable(Relation relation)
{
FileFdwExecutionState *festate = (FileFdwExecutionState *) node->fdw_state;
EndCopyFrom(festate->cstate);
festate->cstate = BeginCopyFrom(node->ss.ss_currentRelation,
festate->filename,
NIL,
festate->options);
return file_acquire_sample_rows;
}
/*
......@@ -657,7 +675,6 @@ estimate_size(PlannerInfo *root, RelOptInfo *baserel,
{
struct stat stat_buf;
BlockNumber pages;
int tuple_width;
double ntuples;
double nrows;
......@@ -674,26 +691,45 @@ estimate_size(PlannerInfo *root, RelOptInfo *baserel,
pages = (stat_buf.st_size + (BLCKSZ - 1)) / BLCKSZ;
if (pages < 1)
pages = 1;
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.
* Estimate the number of tuples in the file.
*/
tuple_width = MAXALIGN(baserel->width) + MAXALIGN(sizeof(HeapTupleHeaderData));
if (baserel->pages > 0)
{
/*
* We have # of pages and # of tuples from pg_class (that is, from a
* previous ANALYZE), so compute a tuples-per-page estimate and scale
* that by the current file size.
*/
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
{
/*
* Otherwise we have to fake it. 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. Possibly we could do something better, but the
* real answer to anyone who complains is "ANALYZE" ...
*/
int tuple_width;
tuple_width = MAXALIGN(baserel->width) +
MAXALIGN(sizeof(HeapTupleHeaderData));
ntuples = clamp_row_est((double) stat_buf.st_size /
(double) tuple_width);
}
fdw_private->ntuples = ntuples;
/*
* Now estimate the number of rows returned by the scan after applying the
* baserestrictinfo quals. This is pretty bogus too, since the planner
* will have no stats about the relation, but it's better than nothing.
* baserestrictinfo quals.
*/
nrows = ntuples *
clauselist_selectivity(root,
......@@ -736,3 +772,169 @@ estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
run_cost += cpu_per_tuple * ntuples;
*total_cost = *startup_cost + run_cost;
}
/*
* 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 total number of rows in the file and return it into
* *totalrows, and return the file's physical size in *totalpages.
* 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
* currently (the planner only pays attention to correlation for indexscans).
*/
static int
file_acquire_sample_rows(Relation onerel, int elevel,
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows,
BlockNumber *totalpages)
{
int numrows = 0;
double rowstoskip = -1; /* -1 means not set yet */
double rstate;
TupleDesc tupDesc;
Datum *values;
bool *nulls;
bool found;
char *filename;
struct stat stat_buf;
List *options;
CopyState cstate;
ErrorContextCallback errcontext;
MemoryContext oldcontext = CurrentMemoryContext;
MemoryContext tupcontext;
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.
*/
cstate = BeginCopyFrom(onerel, filename, NIL, options);
/*
* Use per-tuple memory context to prevent leak of memory used to read rows
* from the file with Copy routines.
*/
tupcontext = AllocSetContextCreate(CurrentMemoryContext,
"file_fdw temporary context",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
/* Prepare for sampling rows */
rstate = anl_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;
*totaldeadrows = 0;
for (;;)
{
/* Check for user-requested abort or sleep */
vacuum_delay_point();
/* Fetch next row */
MemoryContextReset(tupcontext);
MemoryContextSwitchTo(tupcontext);
found = NextCopyFrom(cstate, NULL, values, nulls, NULL);
MemoryContextSwitchTo(oldcontext);
if (!found)
break;
/*
* 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 more info in commands/analyze.c).
*/
if (numrows < targrows)
{
rows[numrows++] = heap_form_tuple(tupDesc, values, nulls);
}
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 totalrows as t.
*/
if (rowstoskip < 0)
rowstoskip = anl_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 * anl_random_fract());
Assert(k >= 0 && k < targrows);
heap_freetuple(rows[k]);
rows[k] = heap_form_tuple(tupDesc, values, nulls);
}
rowstoskip -= 1;
}
*totalrows += 1;
}
/* Remove error callback. */
error_context_stack = errcontext.previous;
/* Clean up. */
MemoryContextDelete(tupcontext);
EndCopyFrom(cstate);
pfree(values);
pfree(nulls);
/*
* Emit some interesting relation info
*/
ereport(elevel,
(errmsg("\"%s\": scanned %u pages containing %.0f rows; "
"%d rows in sample",
RelationGetRelationName(onerel),
*totalpages, *totalrows, numrows)));
return numrows;
}
......@@ -10,8 +10,8 @@
<para>
All operations on a foreign table are handled through its foreign data
wrapper, which consists of a set of functions that the planner and
executor call. The foreign data wrapper is responsible for fetching
wrapper, which consists of a set of functions that the core server
calls. The foreign data wrapper is responsible for fetching
data from the remote data source and returning it to the
<productname>PostgreSQL</productname> executor. This chapter outlines how
to write a new foreign data wrapper.
......@@ -47,7 +47,8 @@
<para>
The handler function simply returns a struct of function pointers to
callback functions that will be called by the planner and executor.
callback functions that will be called by the planner, executor, and
various maintenance commands.
Most of the effort in writing an FDW is in implementing these callback
functions.
The handler function must be registered with
......@@ -276,6 +277,41 @@ EndForeignScan (ForeignScanState *node);
to remote servers should be cleaned up.
</para>
<para>
<programlisting>
AcquireSampleRowsFunc
AnalyzeForeignTable (Relation relation);
</programlisting>
This function is called when <xref linkend="sql-analyze"> is executed on
a foreign table. If the FDW supports collecting statistics for this
foreign table, it should return a pointer to a function that will collect
sample rows from the table. Otherwise, return <literal>NULL</>. If the
FDW does not support collecting statistics for any tables, the
<function>AnalyzeForeignTable</> pointer can be set to <literal>NULL</>.
</para>
<para>
If provided, the sample collection function must have the signature
<programlisting>
int
AcquireSampleRowsFunc (Relation relation, int elevel,
HeapTuple *rows, int targrows,
double *totalrows,
double *totaldeadrows,
BlockNumber *totalpages);
</programlisting>
A random sample of up to <parameter>targrows</> rows should be collected
from the table and stored into the caller-provided <parameter>rows</>
array. The actual number of rows collected must be returned. In
addition, store estimates of the total numbers of live rows, dead rows,
and pages in the table into the output parameters
<parameter>totalrows</>, <parameter>totaldeadrows</>, and
<parameter>totalpages</>. These numbers will be recorded in the table's
<structname>pg_class</> entry for future use.
</para>
<para>
The <structname>FdwRoutine</> struct type is declared in
<filename>src/include/foreign/fdwapi.h</>, which see for additional
......
......@@ -332,6 +332,16 @@
plans that use the expression index.
</para>
</tip>
<tip>
<para>
The autovacuum daemon does not issue <command>ANALYZE</> commands for
foreign tables, since it has no means of determining how often that
might be useful. If your queries require statistics on foreign tables
for proper planning, it's a good idea to run manually-managed
<command>ANALYZE</> commands on those tables on a suitable schedule.
</para>
</tip>
</sect2>
<sect2 id="vacuum-for-visibility-map">
......
......@@ -36,6 +36,9 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
DROP [ COLUMN ] [ IF EXISTS ] <replaceable class="PARAMETER">column</replaceable> [ RESTRICT | CASCADE ]
ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> [ SET DATA ] TYPE <replaceable class="PARAMETER">type</replaceable>
ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> { SET | DROP } NOT NULL
ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> SET STATISTICS <replaceable class="PARAMETER">integer</replaceable>
ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
......@@ -103,6 +106,31 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
</listitem>
</varlistentry>
<varlistentry>
<term><literal>SET STATISTICS</literal></term>
<listitem>
<para>
This form
sets the per-column statistics-gathering target for subsequent
<xref linkend="sql-analyze"> operations.
See the similar form of <xref linkend="sql-altertable">
for more details.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )</literal></term>
<term><literal>RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )</literal></term>
<listitem>
<para>
This form sets or resets per-attribute options.
See the similar form of <xref linkend="sql-altertable">
for more details.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>OWNER</literal></term>
<listitem>
......
......@@ -63,7 +63,8 @@ ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table</replaceable> [ ( <re
<listitem>
<para>
The name (possibly schema-qualified) of a specific table to
analyze. Defaults to all tables in the current database.
analyze. If omitted, all regular tables (but not foreign tables)
in the current database are analyzed.
</para>
</listitem>
</varlistentry>
......@@ -92,6 +93,13 @@ ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table</replaceable> [ ( <re
<refsect1>
<title>Notes</title>
<para>
Foreign tables are analyzed only when explicitly selected. Not all
foreign data wrappers support <command>ANALYZE</>. If the table's
wrapper does not support <command>ANALYZE</>, the command prints a
warning and does nothing.
</para>
<para>
In the default <productname>PostgreSQL</productname> configuration,
the autovacuum daemon (see <xref linkend="autovacuum">)
......
This diff is collapsed.
......@@ -3054,7 +3054,7 @@ 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);
/* This command never recurses */
pass = AT_PASS_MISC;
break;
......@@ -5032,10 +5032,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 */
......
......@@ -1104,7 +1104,7 @@ describeOneTableDetails(const char *schemaname,
bool printTableInitialized = false;
int i;
char *view_def = NULL;
char *headers[6];
char *headers[9];
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);
......
......@@ -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 */
......
......@@ -170,5 +170,8 @@ extern void lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt,
extern void analyze_rel(Oid relid, VacuumStmt *vacstmt,
BufferAccessStrategy bstrategy);
extern bool std_typanalyze(VacAttrStats *stats);
extern double anl_random_fract(void);
extern double anl_init_selection_state(int n);
extern double anl_get_next_S(double t, int n, double *stateptr);
#endif /* VACUUM_H */
......@@ -50,20 +50,32 @@ typedef void (*ReScanForeignScan_function) (ForeignScanState *node);
typedef void (*EndForeignScan_function) (ForeignScanState *node);
typedef int (*AcquireSampleRowsFunc) (Relation relation, int elevel,
HeapTuple *rows, int targrows,
double *totalrows,
double *totaldeadrows,
BlockNumber *totalpages);
typedef AcquireSampleRowsFunc (*AnalyzeForeignTable_function) (Relation relation);
/*
* FdwRoutine is the struct returned by a foreign-data wrapper's handler
* function. It provides pointers to the callback functions needed by the
* planner and executor.
*
* Currently, all functions must be supplied. Later there may be optional
* additions. It's recommended that the handler initialize the struct with
* makeNode(FdwRoutine) so that all fields are set to zero.
* More function pointers are likely to be added in the future. Therefore
* it's recommended that the handler initialize the struct with
* makeNode(FdwRoutine) so that all fields are set to NULL. This will
* ensure that no fields are accidentally left undefined.
*/
typedef struct FdwRoutine
{
NodeTag type;
/*
* These functions are required.
*/
GetForeignRelSize_function GetForeignRelSize;
GetForeignPaths_function GetForeignPaths;
GetForeignPlan_function GetForeignPlan;
......@@ -72,6 +84,12 @@ typedef struct FdwRoutine
IterateForeignScan_function IterateForeignScan;
ReScanForeignScan_function ReScanForeignScan;
EndForeignScan_function EndForeignScan;
/*
* These functions are optional. Set the pointer to NULL for any
* that are not provided.
*/
AnalyzeForeignTable_function AnalyzeForeignTable;
} FdwRoutine;
......
......@@ -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,22 @@ 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 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
......
......@@ -307,6 +307,9 @@ 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 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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment