*** a/contrib/file_fdw/file_fdw.c --- b/contrib/file_fdw/file_fdw.c *************** *** 117,122 **** static void fileGetForeignRelSize(PlannerInfo *root, --- 117,127 ---- static void fileGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid); + static ForeignPath *fileReparameterizeForeignPath(PlannerInfo *root, + RelOptInfo *baserel, + Oid foreigntableid, + ForeignPath *path, + Relids required_outer); static ForeignScan *fileGetForeignPlan(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid, *************** *** 145,151 **** static bool check_selective_binary_conversion(RelOptInfo *baserel, static void estimate_size(PlannerInfo *root, RelOptInfo *baserel, FileFdwPlanState *fdw_private); 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, --- 150,156 ---- static void estimate_size(PlannerInfo *root, RelOptInfo *baserel, FileFdwPlanState *fdw_private); static void estimate_costs(PlannerInfo *root, RelOptInfo *baserel, ! FileFdwPlanState *fdw_private, List *join_conds, Cost *startup_cost, Cost *total_cost); static int file_acquire_sample_rows(Relation onerel, int elevel, HeapTuple *rows, int targrows, *************** *** 163,168 **** file_fdw_handler(PG_FUNCTION_ARGS) --- 168,174 ---- fdwroutine->GetForeignRelSize = fileGetForeignRelSize; fdwroutine->GetForeignPaths = fileGetForeignPaths; + fdwroutine->ReparameterizeForeignPath = fileReparameterizeForeignPath; fdwroutine->GetForeignPlan = fileGetForeignPlan; fdwroutine->ExplainForeignScan = fileExplainForeignScan; fdwroutine->BeginForeignScan = fileBeginForeignScan; *************** *** 517,523 **** fileGetForeignPaths(PlannerInfo *root, (Node *) columns)); /* Estimate costs */ ! estimate_costs(root, baserel, fdw_private, &startup_cost, &total_cost); /* --- 523,530 ---- (Node *) columns)); /* Estimate costs */ ! estimate_costs(root, baserel, ! fdw_private, NIL, &startup_cost, &total_cost); /* *************** *** 542,547 **** fileGetForeignPaths(PlannerInfo *root, --- 549,589 ---- } /* + * fileReparameterizeForeignPath + * Attempt to modify a given path to have greater parameterization + */ + static ForeignPath * + fileReparameterizeForeignPath(PlannerInfo *root, + RelOptInfo *baserel, + Oid foreigntableid, + ForeignPath *path, + Relids required_outer) + { + ParamPathInfo *param_info; + FileFdwPlanState *fdw_private = (FileFdwPlanState *) baserel->fdw_private; + Cost startup_cost; + Cost total_cost; + + /* Get the ParamPathInfo */ + param_info = get_baserel_parampathinfo(root, baserel, required_outer); + + /* Redo the cost estimates */ + estimate_costs(root, baserel, + fdw_private, + param_info->ppi_clauses, + &startup_cost, &total_cost); + + /* Make and return the new path */ + return create_foreignscan_path(root, baserel, + param_info->ppi_rows, + startup_cost, + total_cost, + NIL, /* no pathkeys */ + required_outer, + path->fdw_private); + } + + /* * fileGetForeignPlan * Create a ForeignScan plan node for scanning the foreign table */ *************** *** 970,981 **** estimate_size(PlannerInfo *root, RelOptInfo *baserel, */ static void estimate_costs(PlannerInfo *root, RelOptInfo *baserel, ! FileFdwPlanState *fdw_private, Cost *startup_cost, Cost *total_cost) { BlockNumber pages = fdw_private->pages; double ntuples = fdw_private->ntuples; Cost run_cost = 0; Cost cpu_per_tuple; /* --- 1012,1024 ---- */ static void estimate_costs(PlannerInfo *root, RelOptInfo *baserel, ! FileFdwPlanState *fdw_private, List *join_conds, Cost *startup_cost, Cost *total_cost) { BlockNumber pages = fdw_private->pages; double ntuples = fdw_private->ntuples; Cost run_cost = 0; + QualCost join_cost; Cost cpu_per_tuple; /* *************** *** 986,993 **** estimate_costs(PlannerInfo *root, RelOptInfo *baserel, */ run_cost += seq_page_cost * pages; ! *startup_cost = baserel->baserestrictcost.startup; ! cpu_per_tuple = cpu_tuple_cost * 10 + baserel->baserestrictcost.per_tuple; run_cost += cpu_per_tuple * ntuples; *total_cost = *startup_cost + run_cost; } --- 1029,1039 ---- */ run_cost += seq_page_cost * pages; ! cost_qual_eval(&join_cost, join_conds, root); ! *startup_cost = ! (baserel->baserestrictcost.startup + join_cost.startup); ! cpu_per_tuple = cpu_tuple_cost * 10 + ! (baserel->baserestrictcost.per_tuple + join_cost.per_tuple); run_cost += cpu_per_tuple * ntuples; *total_cost = *startup_cost + run_cost; } *** a/contrib/postgres_fdw/postgres_fdw.c --- b/contrib/postgres_fdw/postgres_fdw.c *************** *** 241,246 **** static void postgresGetForeignRelSize(PlannerInfo *root, --- 241,251 ---- static void postgresGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid); + static ForeignPath *postgresReparameterizeForeignPath(PlannerInfo *root, + RelOptInfo *baserel, + Oid foreigntableid, + ForeignPath *path, + Relids required_outer); static ForeignScan *postgresGetForeignPlan(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid, *************** *** 342,347 **** postgres_fdw_handler(PG_FUNCTION_ARGS) --- 347,353 ---- /* Functions for scanning foreign tables */ routine->GetForeignRelSize = postgresGetForeignRelSize; routine->GetForeignPaths = postgresGetForeignPaths; + routine->ReparameterizeForeignPath = postgresReparameterizeForeignPath; routine->GetForeignPlan = postgresGetForeignPlan; routine->BeginForeignScan = postgresBeginForeignScan; routine->IterateForeignScan = postgresIterateForeignScan; *************** *** 729,734 **** postgresGetForeignPaths(PlannerInfo *root, --- 735,782 ---- } /* + * postgresReparameterizeForeignPath + * Attempt to modify a given path to have greater parameterization + */ + static ForeignPath * + postgresReparameterizeForeignPath(PlannerInfo *root, + RelOptInfo *baserel, + Oid foreigntableid, + ForeignPath *path, + Relids required_outer) + { + ParamPathInfo *param_info; + double rows; + int width; + Cost startup_cost; + Cost total_cost; + + /* Get the ParamPathInfo */ + param_info = get_baserel_parampathinfo(root, baserel, required_outer); + + /* Redo the cost estimates */ + estimate_path_cost_size(root, baserel, + param_info->ppi_clauses, + &rows, &width, + &startup_cost, &total_cost); + + /* + * ppi_rows currently won't get looked at by anything, but still we + * may as well ensure that it matches our idea of the rowcount. + */ + param_info->ppi_rows = rows; + + /* Make and return the new path */ + return create_foreignscan_path(root, baserel, + rows, + startup_cost, + total_cost, + NIL, /* no pathkeys */ + required_outer, + NIL); /* no fdw_private list */ + } + + /* * postgresGetForeignPlan * Create ForeignScan plan node which implements selected best path */ *************** *** 1775,1785 **** estimate_path_cost_size(PlannerInfo *root, } else { ! /* ! * We don't support join conditions in this mode (hence, no ! * parameterized paths can be made). ! */ ! Assert(join_conds == NIL); /* Use rows/width estimates made by set_baserel_size_estimates. */ rows = baserel->rows; --- 1823,1830 ---- } else { ! Selectivity join_sel; ! QualCost join_cost; /* Use rows/width estimates made by set_baserel_size_estimates. */ rows = baserel->rows; *************** *** 1792,1808 **** estimate_path_cost_size(PlannerInfo *root, retrieved_rows = clamp_row_est(rows / fpinfo->local_conds_sel); retrieved_rows = Min(retrieved_rows, baserel->tuples); /* * Cost as though this were a seqscan, which is pessimistic. We ! * effectively imagine the local_conds are being evaluated remotely, ! * too. */ startup_cost = 0; run_cost = 0; run_cost += seq_page_cost * baserel->pages; ! startup_cost += baserel->baserestrictcost.startup; ! cpu_per_tuple = cpu_tuple_cost + baserel->baserestrictcost.per_tuple; run_cost += cpu_per_tuple * baserel->tuples; total_cost = startup_cost + run_cost; --- 1837,1865 ---- retrieved_rows = clamp_row_est(rows / fpinfo->local_conds_sel); retrieved_rows = Min(retrieved_rows, baserel->tuples); + /* Factor in the selectivity of the join_conds */ + join_sel = clauselist_selectivity(root, + join_conds, + baserel->relid, + JOIN_INNER, + NULL); + + rows = clamp_row_est(rows * join_sel); + /* * Cost as though this were a seqscan, which is pessimistic. We ! * effectively imagine the local_conds and join_conds are being ! * evaluated remotely, too. */ startup_cost = 0; run_cost = 0; run_cost += seq_page_cost * baserel->pages; ! cost_qual_eval(&join_cost, join_conds, root); ! startup_cost += ! (baserel->baserestrictcost.startup + join_cost.startup); ! cpu_per_tuple = cpu_tuple_cost + ! (baserel->baserestrictcost.per_tuple + join_cost.per_tuple); run_cost += cpu_per_tuple * baserel->tuples; total_cost = startup_cost + run_cost; *** a/doc/src/sgml/ddl.sgml --- b/doc/src/sgml/ddl.sgml *************** *** 258,263 **** CREATE TABLE products ( --- 258,274 ---- even if the value came from the default value definition. + + + Note that constraints can be defined on foreign tables too, but such + constraints are not enforced on insert or update. Those constraints are + "assertive", and work only to tell planner that some kind of optimization + such as constraint exclusion can be considerd. This seems useless, but + allows us to use foriegn table as child table (see + ) to off-load to multiple servers. + + + Check Constraints *************** *** 2017,2024 **** CREATE TABLE capitals ( ! In PostgreSQL, a table can inherit from ! zero or more other tables, and a query can reference either all rows of a table or all rows of a table plus all of its descendant tables. The latter behavior is the default. For example, the following query finds the names of all cities, --- 2028,2035 ---- ! In PostgreSQL, a table or foreign table can ! inherit from zero or more other tables, and a query can reference either all rows of a table or all rows of a table plus all of its descendant tables. The latter behavior is the default. For example, the following query finds the names of all cities, *** a/doc/src/sgml/fdwhandler.sgml --- b/doc/src/sgml/fdwhandler.sgml *************** *** 162,167 **** GetForeignPaths (PlannerInfo *root, --- 162,198 ---- + ForeignPath * + ReparameterizeForeignPath (PlannerInfo *root, + RelOptInfo *baserel, + Oid foreigntableid, + ForeignPath *path, + Relids required_outer); + + + Create an access path for a scan on a foreign table using join + clauses, which is called a parameterized path. + This is called during query planning. + The parameters are as for GetForeignRelSize, plus + the ForeignPath (previously produced by + GetForeignPaths), and the IDs of all other tables + that provide the join clauses. + + + + This function must generate a parameterized path for a scan on the + foreign table. The parameterized path must contain cost estimates, + and can contain any FDW-private information that is needed to identify + the specific scan method intended. Unlike the other scan-related + functions, this function is optional. + + + + See for additional information. + + + + ForeignScan * GetForeignPlan (PlannerInfo *root, RelOptInfo *baserel, *************** *** 804,813 **** GetForeignServerByName(const char *name, bool missing_ok); The FDW callback functions GetForeignRelSize, ! GetForeignPaths, GetForeignPlan, and ! PlanForeignModify must fit into the workings of the ! PostgreSQL planner. Here are some notes about what ! they must do. --- 835,844 ---- The FDW callback functions GetForeignRelSize, ! GetForeignPaths, ReparameterizeForeignPath, ! GetForeignPlan, and PlanForeignModify must fit ! into the workings of the PostgreSQL planner. Here are ! some notes about what they must do. *************** *** 836,850 **** GetForeignServerByName(const char *name, bool missing_ok); the particular foreign table. The core planner does not touch it except to initialize it to NULL when the baserel node is created. It is useful for passing information forward from ! GetForeignRelSize to GetForeignPaths and/or ! GetForeignPaths to GetForeignPlan, thereby ! avoiding recalculation. ! GetForeignPaths can identify the meaning of different ! access paths by storing private information in the ! fdw_private field of ForeignPath nodes. fdw_private is declared as a List pointer, but could actually contain anything since the core planner does not touch it. However, best practice is to use a representation that's dumpable --- 867,883 ---- the particular foreign table. The core planner does not touch it except to initialize it to NULL when the baserel node is created. It is useful for passing information forward from ! GetForeignRelSize to GetForeignPaths, ! GetForeignPaths to ReparameterizeForeignPath ! and/or GetForeignPaths to GetForeignPlan, ! thereby avoiding recalculation. ! GetForeignPaths or ReparameterizeForeignPath ! can identify the meaning of different access paths by storing private ! information in the fdw_private field of ! ForeignPath nodes. fdw_private is declared as a List pointer, but could actually contain anything since the core planner does not touch it. However, best practice is to use a representation that's dumpable *************** *** 887,896 **** GetForeignServerByName(const char *name, bool missing_ok); sub_expression, which it determines can be executed on the remote server given the locally-evaluated value of the sub_expression. The actual identification of such a ! clause should happen during GetForeignPaths, since it would ! affect the cost estimate for the path. The path's ! fdw_private field would probably include a pointer to ! the identified clause's RestrictInfo node. Then GetForeignPlan would remove that clause from scan_clauses, but add the sub_expression to fdw_exprs to ensure that it gets massaged into executable form. It would probably --- 920,930 ---- sub_expression, which it determines can be executed on the remote server given the locally-evaluated value of the sub_expression. The actual identification of such a ! clause should happen during GetForeignPaths or ! ReparameterizeForeignPath, since it would affect the cost ! estimate for the path. The path's fdw_private field ! would probably include a pointer to the identified clause's ! RestrictInfo node. Then GetForeignPlan would remove that clause from scan_clauses, but add the sub_expression to fdw_exprs to ensure that it gets massaged into executable form. It would probably *** a/doc/src/sgml/ref/alter_foreign_table.sgml --- b/doc/src/sgml/ref/alter_foreign_table.sgml *************** *** 42,47 **** ALTER FOREIGN TABLE [ IF EXISTS ] namecolumn_name SET ( attribute_option = value [, ... ] ) ALTER [ COLUMN ] column_name RESET ( attribute_option [, ... ] ) ALTER [ COLUMN ] column_name OPTIONS ( [ ADD | SET | DROP ] option ['value'] [, ... ]) + ADD table_constraint + DROP CONSTRAINT [ IF EXISTS ] constraint_name [ RESTRICT | CASCADE ] + INHERIT parent_table + NO INHERIT parent_table OWNER TO new_owner OPTIONS ( [ ADD | SET | DROP ] option ['value'] [, ... ]) *************** *** 149,154 **** ALTER FOREIGN TABLE [ IF EXISTS ] name + ADD table_constraint [ NOT VALID ] + + + This form adds a new constraint to a table using the same syntax as + . + Unlike the case when adding a constraint to a regular table, nothing happens + to the underlying storage: this action simply declares that + some new constraint holds for all rows in the foreign table. + + + + + + DROP CONSTRAINT [ IF EXISTS ] + + + This form drops the specified constraint on a table. + If IF EXISTS is specified and the constraint + does not exist, no error is thrown. In this case a notice is issued instead. + + + + + + INHERIT parent_table + + + This form adds the target foreign table as a new child of the specified + parent table. The parent table must be an ordinary table. + + + + + + NO INHERIT parent_table + + + This form removes the target foreign table from the list of children of + the specified parent table. + + + + + OWNER *************** *** 270,275 **** ALTER FOREIGN TABLE [ IF EXISTS ] name + table_constraint + + + New table constraint for the table. + + + + + + constraint_name + + + Name of an existing constraint to drop. + + + + + CASCADE *************** *** 306,311 **** ALTER FOREIGN TABLE [ IF EXISTS ] name + + + parent_name + + + A parent table to associate or de-associate with this foreign table. + The parent table must be an ordinary table. + + + *************** *** 319,328 **** ALTER FOREIGN TABLE [ IF EXISTS ] name Consistency with the foreign server is not checked when a column is added or removed with ADD COLUMN or ! DROP COLUMN, a NOT NULL constraint is ! added, or a column type is changed with SET DATA TYPE. It is ! the user's responsibility to ensure that the table definition matches the ! remote side. --- 395,404 ---- Consistency with the foreign server is not checked when a column is added or removed with ADD COLUMN or ! DROP COLUMN, a NOT NULL constraint or ! CHECK constraint is added, or a column type is changed with ! SET DATA TYPE. It is the user's responsibility to ensure that ! the table definition matches the remote side. *** a/doc/src/sgml/ref/alter_table.sgml --- b/doc/src/sgml/ref/alter_table.sgml *************** *** 937,942 **** ALTER TABLE [ IF EXISTS ] name --- 937,948 ---- + Since it is not permitted to add an oid system + column to foreign tables, a recursive SET STORAGE + operation will be rejected if any descendant tables are foreign. + + + The TRIGGER, CLUSTER, OWNER, and TABLESPACE actions never recurse to descendant tables; that is, they always act as though ONLY were specified. *************** *** 945,950 **** ALTER TABLE [ IF EXISTS ] name --- 951,969 ---- + When adding a CHECK constraint with the NOT VALID + option recursively, an inherited constraint on a descendant + table that is foreign will be marked valid without checking + consistency with that foreign server. + + + + A recursive SET STORAGE operation will make the + storage mode for a descendant table's column unchanged if the table is + foreign. + + + Changing any part of a system catalog table is not permitted. *** a/doc/src/sgml/ref/analyze.sgml --- b/doc/src/sgml/ref/analyze.sgml *************** *** 200,205 **** ANALYZE [ VERBOSE ] [ table_name [ --- 200,212 ---- + The inheritance statistics for a parent table that contains one or more + children that are foreign tables are collected only when explicitly + selected. If the foreign table's wrapper does not support + ANALYZE, the command prints a warning and does nothing. + + + If the table being analyzed is completely empty, ANALYZE will not record new statistics for that table. Any existing statistics will be retained. *** a/doc/src/sgml/ref/create_foreign_table.sgml --- b/doc/src/sgml/ref/create_foreign_table.sgml *************** *** 19,27 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] table_name ( [ ! column_name data_type [ OPTIONS ( option 'value' [, ... ] ) ] [ COLLATE collation ] [ column_constraint [ ... ] ] [, ... ] ] ) SERVER server_name [ OPTIONS ( option 'value' [, ... ] ) ] --- 19,29 ---- CREATE FOREIGN TABLE [ IF NOT EXISTS ] table_name ( [ ! { column_name data_type [ OPTIONS ( option 'value' [, ... ] ) ] [ COLLATE collation ] [ column_constraint [ ... ] ] ! | table_constraint } [, ... ] ] ) + [ INHERITS ( parent_table [, ... ] ) ] SERVER server_name [ OPTIONS ( option 'value' [, ... ] ) ] *************** *** 30,36 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] table_name --- 32,44 ---- [ CONSTRAINT constraint_name ] { NOT NULL | NULL | + CHECK ( expression ) [ NO INHERIT ] | DEFAULT default_expr } + + and table_constraint is: + + [ CONSTRAINT constraint_name ] + { CHECK ( expression ) [ NO INHERIT ] } *************** *** 138,143 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] table_name --- 146,177 ---- + CHECK ( expression ) [ NO INHERIT ] + + + The CHECK clause specifies an expression producing a + Boolean result which each row must satisfy. + Expressions evaluating to TRUE or UNKNOWN succeed. + A check constraint specified as a column constraint should + reference that column's value only, while an expression + appearing in a table constraint can reference multiple columns. + + + + Currently, CHECK expressions cannot contain + subqueries nor refer to variables other than columns of the + current row. The system column tableoid + may be referenced, but not any other system column. + + + + A constraint marked with NO INHERIT will not propagate to + child tables. + + + + + DEFAULT default_expr *************** *** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] table_name --- 193,210 ---- + parent_table + + + The name of an existing table from which the new foreign table + automatically inherits all columns. The specified parent table + must be an ordinary table. See for the + details of table inheritance. + + + + + server_name *** a/src/backend/commands/analyze.c --- b/src/backend/commands/analyze.c *************** *** 82,87 **** int default_statistics_target = 100; --- 82,88 ---- /* A few variables that don't seem worth passing around as parameters */ static MemoryContext anl_context = NULL; + static VacuumMode vac_mode; static BufferAccessStrategy vac_strategy; *************** *** 115,121 **** static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull); * analyze_rel() -- analyze one relation */ void ! analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy) { Relation onerel; int elevel; --- 116,124 ---- * analyze_rel() -- analyze one relation */ void ! analyze_rel(Oid relid, VacuumStmt *vacstmt, ! VacuumMode vacmode, ! BufferAccessStrategy bstrategy) { Relation onerel; int elevel; *************** *** 129,134 **** analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy) --- 132,138 ---- elevel = DEBUG2; /* Set up static variables */ + vac_mode = vacmode; vac_strategy = bstrategy; /* *************** *** 1452,1457 **** acquire_inherited_sample_rows(Relation onerel, int elevel, --- 1456,1462 ---- { List *tableOIDs; Relation *rels; + AcquireSampleRowsFunc *acquirefunc; double *relblocks; double totalblocks; int numrows, *************** *** 1486,1491 **** acquire_inherited_sample_rows(Relation onerel, int elevel, --- 1491,1498 ---- * BlockNumber, so we use double arithmetic. */ rels = (Relation *) palloc(list_length(tableOIDs) * sizeof(Relation)); + acquirefunc = (AcquireSampleRowsFunc *) palloc(list_length(tableOIDs) + * sizeof(AcquireSampleRowsFunc)); relblocks = (double *) palloc(list_length(tableOIDs) * sizeof(double)); totalblocks = 0; nrels = 0; *************** *** 1507,1513 **** acquire_inherited_sample_rows(Relation onerel, int elevel, } rels[nrels] = childrel; ! relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel); totalblocks += relblocks[nrels]; nrels++; } --- 1514,1553 ---- } rels[nrels] = childrel; ! ! if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) ! { ! FdwRoutine *fdwroutine; ! BlockNumber relpages = 0; ! bool ok = false; ! ! /* Ignore unless analyzing a specific inheritance tree */ ! if (vac_mode != VAC_MODE_SINGLE) ! return 0; ! ! /* Check whether the FDW supports analysis */ ! fdwroutine = GetFdwRoutineForRelation(childrel, false); ! if (fdwroutine->AnalyzeForeignTable != NULL) ! ok = fdwroutine->AnalyzeForeignTable(childrel, ! &acquirefunc[nrels], ! &relpages); ! if (!ok) ! { ! /* Give up if the FDW doesn't support analysis */ ! ereport(WARNING, ! (errmsg("skipping analyze of inheritance tree \"%s\" --- cannot analyze foreign table \"%s\"", ! RelationGetRelationName(onerel), ! RelationGetRelationName(childrel)))); ! return 0; ! } ! relblocks[nrels] = (double) relpages; ! } ! else ! { ! acquirefunc[nrels] = acquire_sample_rows; ! relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel); ! } ! totalblocks += relblocks[nrels]; nrels++; } *************** *** 1525,1530 **** acquire_inherited_sample_rows(Relation onerel, int elevel, --- 1565,1571 ---- { Relation childrel = rels[i]; double childblocks = relblocks[i]; + AcquireSampleRowsFunc childacquirefunc = acquirefunc[i]; if (childblocks > 0) { *************** *** 1540,1551 **** acquire_inherited_sample_rows(Relation onerel, int elevel, tdrows; /* Fetch a random sample of the child's rows */ ! childrows = acquire_sample_rows(childrel, ! elevel, ! rows + numrows, ! childtargrows, ! &trows, ! &tdrows); /* We may need to convert from child's rowtype to parent's */ if (childrows > 0 && --- 1581,1592 ---- tdrows; /* Fetch a random sample of the child's rows */ ! childrows = childacquirefunc(childrel, ! elevel, ! rows + numrows, ! childtargrows, ! &trows, ! &tdrows); /* 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,317 **** static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel); static void ATSimplePermissions(Relation rel, int allowed_targets); static void ATWrongRelkindError(Relation rel, int allowed_targets); static void ATSimpleRecursion(List **wqueue, Relation rel, ! AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode); static void ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd, LOCKMODE lockmode); static List *find_typed_table_dependencies(Oid typeOid, const char *typeName, --- 311,318 ---- static void ATSimplePermissions(Relation rel, int allowed_targets); static void ATWrongRelkindError(Relation rel, int allowed_targets); static void ATSimpleRecursion(List **wqueue, Relation rel, ! AlterTableCmd *cmd, bool recurse, ! bool include_foreign, LOCKMODE lockmode); static void ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd, LOCKMODE lockmode); static List *find_typed_table_dependencies(Oid typeOid, const char *typeName, *************** *** 467,476 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId) ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("ON COMMIT can only be used on temporary tables"))); - if (stmt->constraints != NIL && relkind == RELKIND_FOREIGN_TABLE) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("constraints are not supported on foreign tables"))); /* * Look up the namespace in which we are supposed to create the relation, --- 468,473 ---- *************** *** 3019,3042 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, * rules. */ ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE); ! ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); /* No command-specific prep needed */ pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP; break; case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); ! ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); /* No command-specific prep needed */ pass = AT_PASS_DROP; break; case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); ! ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); /* No command-specific prep needed */ pass = AT_PASS_ADD_CONSTR; break; case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */ ! ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); /* Performs own permission checks */ ATPrepSetStatistics(rel, cmd->name, cmd->def, lockmode); pass = AT_PASS_MISC; --- 3016,3043 ---- * rules. */ ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE); ! /* Recurse to child tables that are foreign, too */ ! ATSimpleRecursion(wqueue, rel, cmd, recurse, true, lockmode); /* No command-specific prep needed */ pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP; break; case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); ! /* Recurse to child tables that are foreign, too */ ! ATSimpleRecursion(wqueue, rel, cmd, recurse, true, lockmode); /* No command-specific prep needed */ pass = AT_PASS_DROP; break; case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); ! /* Recurse to child tables that are foreign, too */ ! ATSimpleRecursion(wqueue, rel, cmd, recurse, true, lockmode); /* No command-specific prep needed */ pass = AT_PASS_ADD_CONSTR; break; case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */ ! /* Recurse to child tables that are foreign, too */ ! ATSimpleRecursion(wqueue, rel, cmd, recurse, true, lockmode); /* Performs own permission checks */ ATPrepSetStatistics(rel, cmd->name, cmd->def, lockmode); pass = AT_PASS_MISC; *************** *** 3049,3055 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, break; case AT_SetStorage: /* ALTER COLUMN SET STORAGE */ ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW); ! ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); /* No command-specific prep needed */ pass = AT_PASS_MISC; break; --- 3050,3057 ---- break; case AT_SetStorage: /* ALTER COLUMN SET STORAGE */ ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW); ! /* Don't recurse to child tables that are foreign */ ! ATSimpleRecursion(wqueue, rel, cmd, recurse, false, lockmode); /* No command-specific prep needed */ pass = AT_PASS_MISC; break; *************** *** 3067,3073 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, pass = AT_PASS_ADD_INDEX; break; case AT_AddConstraint: /* ADD CONSTRAINT */ ! ATSimplePermissions(rel, ATT_TABLE); /* Recursion occurs during execution phase */ /* No command-specific prep needed except saving recurse flag */ if (recurse) --- 3069,3075 ---- pass = AT_PASS_ADD_INDEX; break; case AT_AddConstraint: /* ADD CONSTRAINT */ ! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); /* Recursion occurs during execution phase */ /* No command-specific prep needed except saving recurse flag */ if (recurse) *************** *** 3081,3087 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, pass = AT_PASS_ADD_CONSTR; break; case AT_DropConstraint: /* DROP CONSTRAINT */ ! ATSimplePermissions(rel, ATT_TABLE); /* Recursion occurs during execution phase */ /* No command-specific prep needed except saving recurse flag */ if (recurse) --- 3083,3089 ---- pass = AT_PASS_ADD_CONSTR; break; case AT_DropConstraint: /* DROP CONSTRAINT */ ! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); /* Recursion occurs during execution phase */ /* No command-specific prep needed except saving recurse flag */ if (recurse) *************** *** 3149,3161 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, pass = AT_PASS_MISC; break; case AT_AddInherit: /* INHERIT */ ! ATSimplePermissions(rel, ATT_TABLE); /* This command never recurses */ ATPrepAddInherit(rel); pass = AT_PASS_MISC; break; case AT_AlterConstraint: /* ALTER CONSTRAINT */ ! ATSimplePermissions(rel, ATT_TABLE); pass = AT_PASS_MISC; break; case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */ --- 3151,3169 ---- pass = AT_PASS_MISC; break; case AT_AddInherit: /* INHERIT */ ! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); /* This command never recurses */ ATPrepAddInherit(rel); pass = AT_PASS_MISC; break; + case AT_DropInherit: /* NO INHERIT */ + ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); + /* This command never recurses */ + /* No command-specific prep needed */ + pass = AT_PASS_MISC; + break; case AT_AlterConstraint: /* ALTER CONSTRAINT */ ! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); pass = AT_PASS_MISC; break; case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */ *************** *** 3184,3190 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, case AT_EnableAlwaysRule: case AT_EnableReplicaRule: case AT_DisableRule: - case AT_DropInherit: /* NO INHERIT */ case AT_AddOf: /* OF */ case AT_DropOf: /* NOT OF */ ATSimplePermissions(rel, ATT_TABLE); --- 3192,3197 ---- *************** *** 4125,4131 **** ATWrongRelkindError(Relation rel, int allowed_targets) */ static void ATSimpleRecursion(List **wqueue, Relation rel, ! AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode) { /* * Propagate to children if desired. Non-table relations never have --- 4132,4139 ---- */ static void ATSimpleRecursion(List **wqueue, Relation rel, ! AlterTableCmd *cmd, bool recurse, ! bool include_foreign, LOCKMODE lockmode) { /* * Propagate to children if desired. Non-table relations never have *************** *** 4153,4160 **** ATSimpleRecursion(List **wqueue, Relation rel, continue; /* find_all_inheritors already got lock */ childrel = relation_open(childrelid, NoLock); ! CheckTableNotInUse(childrel, "ALTER TABLE"); ! ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode); relation_close(childrel, NoLock); } } --- 4161,4172 ---- continue; /* find_all_inheritors already got lock */ childrel = relation_open(childrelid, NoLock); ! if (childrel->rd_rel->relkind != RELKIND_FOREIGN_TABLE ! || include_foreign) ! { ! CheckTableNotInUse(childrel, "ALTER TABLE"); ! ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode); ! } relation_close(childrel, NoLock); } } *************** *** 4444,4450 **** ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, /* At top level, permission check was done in ATPrepCmd, else do it */ if (recursing) ! ATSimplePermissions(rel, ATT_TABLE); attrdesc = heap_open(AttributeRelationId, RowExclusiveLock); --- 4456,4462 ---- /* At top level, permission check was done in ATPrepCmd, else do it */ if (recursing) ! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); attrdesc = heap_open(AttributeRelationId, RowExclusiveLock); *************** *** 4740,4745 **** ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, --- 4752,4762 ---- /* find_inheritance_children already got lock */ childrel = heap_open(childrelid, NoLock); + if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE && isOid) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot add OID column to foreign table \"%s\"", + RelationGetRelationName(childrel)))); CheckTableNotInUse(childrel, "ALTER TABLE"); /* Find or create work queue entry for this table */ *************** *** 5340,5346 **** ATExecDropColumn(List **wqueue, Relation rel, const char *colName, /* At top level, permission check was done in ATPrepCmd, else do it */ if (recursing) ! ATSimplePermissions(rel, ATT_TABLE); /* * get the number of the attribute --- 5357,5363 ---- /* At top level, permission check was done in ATPrepCmd, else do it */ if (recursing) ! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); /* * get the number of the attribute *************** *** 5732,5738 **** ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, /* At top level, permission check was done in ATPrepCmd, else do it */ if (recursing) ! ATSimplePermissions(rel, ATT_TABLE); /* * Call AddRelationNewConstraints to do the work, making sure it works on --- 5749,5762 ---- /* At top level, permission check was done in ATPrepCmd, else do it */ if (recursing) ! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); ! ! /* Don't allow ADD CONSTRAINT NOT VALID to be applied to foreign tables */ ! if (tab->relkind == RELKIND_FOREIGN_TABLE && ! constr->skip_validation && !recursing) ! ereport(ERROR, ! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("NOT VALID is not supported on foreign tables"))); /* * Call AddRelationNewConstraints to do the work, making sure it works on *************** *** 5743,5751 **** ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, * omitted from the returned list, which is what we want: we do not need * to do any validation work. That can only happen at child tables, * though, since we disallow merging at the top level. */ newcons = AddRelationNewConstraints(rel, NIL, ! list_make1(copyObject(constr)), recursing, /* allow_merge */ !recursing, /* is_local */ is_readd); /* is_internal */ --- 5767,5783 ---- * omitted from the returned list, which is what we want: we do not need * to do any validation work. That can only happen at child tables, * though, since we disallow merging at the top level. + * + * When propagating a NOT VALID option to children that are foreign tables, + * we quietly ignore the option. Note that this is safe because foreign + * tables don't have any children. */ + constr = (Constraint *) copyObject(constr); + if (tab->relkind == RELKIND_FOREIGN_TABLE && + constr->skip_validation && recursing) + constr->skip_validation = false; newcons = AddRelationNewConstraints(rel, NIL, ! list_make1(constr), recursing, /* allow_merge */ !recursing, /* is_local */ is_readd); /* is_internal */ *************** *** 7225,7231 **** ATExecDropConstraint(Relation rel, const char *constrName, /* At top level, permission check was done in ATPrepCmd, else do it */ if (recursing) ! ATSimplePermissions(rel, ATT_TABLE); conrel = heap_open(ConstraintRelationId, RowExclusiveLock); --- 7257,7263 ---- /* At top level, permission check was done in ATPrepCmd, else do it */ if (recursing) ! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); conrel = heap_open(ConstraintRelationId, RowExclusiveLock); *************** *** 7560,7566 **** ATPrepAlterColumnType(List **wqueue, * alter would put them out of step. */ if (recurse) ! ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); else if (!recursing && find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL) ereport(ERROR, --- 7592,7601 ---- * alter would put them out of step. */ if (recurse) ! { ! /* Recurse to child tables that are foreign, too */ ! ATSimpleRecursion(wqueue, rel, cmd, recurse, true, lockmode); ! } else if (!recursing && find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL) ereport(ERROR, *** a/src/backend/commands/vacuum.c --- b/src/backend/commands/vacuum.c *************** *** 61,66 **** int vacuum_multixact_freeze_table_age; --- 61,67 ---- /* A few variables that don't seem worth passing around as parameters */ static MemoryContext vac_context = NULL; + static VacuumMode vac_mode; static BufferAccessStrategy vac_strategy; *************** *** 146,151 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast, --- 147,166 ---- ALLOCSET_DEFAULT_MAXSIZE); /* + * Identify vacuum mode. If relid is not InvalidOid, the caller should be + * an autovacuum worker. See the above comments. + */ + if (relid != InvalidOid) + vac_mode = VAC_MODE_AUTOVACUUM; + else + { + if (!vacstmt->relation) + vac_mode = VAC_MODE_ALL; + else + vac_mode = VAC_MODE_SINGLE; + } + + /* * If caller didn't give us a buffer strategy object, make one in the * cross-transaction memory context. */ *************** *** 248,254 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast, PushActiveSnapshot(GetTransactionSnapshot()); } ! analyze_rel(relid, vacstmt, vac_strategy); if (use_own_xacts) { --- 263,269 ---- PushActiveSnapshot(GetTransactionSnapshot()); } ! analyze_rel(relid, vacstmt, vac_mode, vac_strategy); if (use_own_xacts) { *** a/src/backend/optimizer/prep/prepunion.c --- b/src/backend/optimizer/prep/prepunion.c *************** *** 1337,1347 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) /* * Build an RTE for the child, and attach to query's rangetable list. * We copy most fields of the parent's RTE, but replace relation OID, ! * and set inh = false. Also, set requiredPerms to zero since all ! * required permissions checks are done on the original RTE. */ childrte = copyObject(rte); childrte->relid = childOID; childrte->inh = false; childrte->requiredPerms = 0; parse->rtable = lappend(parse->rtable, childrte); --- 1337,1348 ---- /* * Build an RTE for the child, and attach to query's rangetable list. * We copy most fields of the parent's RTE, but replace relation OID, ! * relkind and set inh = false. Also, set requiredPerms to zero since ! * all required permissions checks are done on the original RTE. */ childrte = copyObject(rte); childrte->relid = childOID; + childrte->relkind = newrelation->rd_rel->relkind; childrte->inh = false; childrte->requiredPerms = 0; parse->rtable = lappend(parse->rtable, childrte); *** a/src/backend/optimizer/util/pathnode.c --- b/src/backend/optimizer/util/pathnode.c *************** *** 16,21 **** --- 16,22 ---- #include + #include "foreign/fdwapi.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" *************** *** 2062,2067 **** reparameterize_path(PlannerInfo *root, Path *path, --- 2063,2091 ---- case T_SubqueryScan: return create_subqueryscan_path(root, rel, path->pathkeys, required_outer); + case T_ForeignScan: + { + ForeignPath *fpath = (ForeignPath *) path; + ForeignPath *newpath = NULL; + Index scan_relid = rel->relid; + RangeTblEntry *rte; + + /* it should be a base rel... */ + Assert(scan_relid > 0); + Assert(rel->rtekind == RTE_RELATION); + rte = planner_rt_fetch(scan_relid, root); + Assert(rte->rtekind == RTE_RELATION); + + /* Let the FDW reparameterize the path node if possible */ + if (rel->fdwroutine->ReparameterizeForeignPath != NULL) + newpath = + rel->fdwroutine->ReparameterizeForeignPath(root, + rel, + rte->relid, + fpath, + required_outer); + return (Path *) newpath; + } default: break; } *** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** *** 4209,4240 **** AlterForeignServerStmt: ALTER SERVER name foreign_server_version alter_generic_o CreateForeignTableStmt: CREATE FOREIGN TABLE qualified_name '(' OptTableElementList ')' ! SERVER name create_generic_options { CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt); $4->relpersistence = RELPERSISTENCE_PERMANENT; n->base.relation = $4; n->base.tableElts = $6; ! n->base.inhRelations = NIL; n->base.if_not_exists = false; /* FDW-specific data */ ! n->servername = $9; ! n->options = $10; $$ = (Node *) n; } | CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name '(' OptTableElementList ')' ! SERVER name create_generic_options { CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt); $7->relpersistence = RELPERSISTENCE_PERMANENT; n->base.relation = $7; n->base.tableElts = $9; ! n->base.inhRelations = NIL; n->base.if_not_exists = true; /* FDW-specific data */ ! n->servername = $12; ! n->options = $13; $$ = (Node *) n; } ; --- 4209,4240 ---- CreateForeignTableStmt: CREATE FOREIGN TABLE qualified_name '(' OptTableElementList ')' ! OptInherit SERVER name create_generic_options { CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt); $4->relpersistence = RELPERSISTENCE_PERMANENT; n->base.relation = $4; n->base.tableElts = $6; ! n->base.inhRelations = $8; n->base.if_not_exists = false; /* FDW-specific data */ ! n->servername = $10; ! n->options = $11; $$ = (Node *) n; } | CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name '(' OptTableElementList ')' ! OptInherit SERVER name create_generic_options { CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt); $7->relpersistence = RELPERSISTENCE_PERMANENT; n->base.relation = $7; n->base.tableElts = $9; ! n->base.inhRelations = $11; n->base.if_not_exists = true; /* FDW-specific data */ ! n->servername = $13; ! n->options = $14; $$ = (Node *) n; } ; *** a/src/backend/parser/parse_utilcmd.c --- b/src/backend/parser/parse_utilcmd.c *************** *** 515,526 **** transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) break; case CONSTR_CHECK: - if (cxt->isforeign) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("constraints are not supported on foreign tables"), - parser_errposition(cxt->pstate, - constraint->location))); cxt->ckconstraints = lappend(cxt->ckconstraints, constraint); break; --- 515,520 ---- *************** *** 529,535 **** transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) if (cxt->isforeign) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("constraints are not supported on foreign tables"), parser_errposition(cxt->pstate, constraint->location))); if (constraint->keys == NIL) --- 523,529 ---- if (cxt->isforeign) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("primary key or unique constraints are not supported on foreign tables"), parser_errposition(cxt->pstate, constraint->location))); if (constraint->keys == NIL) *************** *** 546,552 **** transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) if (cxt->isforeign) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("constraints are not supported on foreign tables"), parser_errposition(cxt->pstate, constraint->location))); --- 540,546 ---- if (cxt->isforeign) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("foreign key constraints are not supported on foreign tables"), parser_errposition(cxt->pstate, constraint->location))); *************** *** 605,614 **** transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) static void transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint) { ! if (cxt->isforeign) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("constraints are not supported on foreign tables"), parser_errposition(cxt->pstate, constraint->location))); --- 599,612 ---- static void transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint) { ! if (cxt->isforeign && ! (constraint->contype == CONSTR_PRIMARY || ! constraint->contype == CONSTR_UNIQUE || ! constraint->contype == CONSTR_EXCLUSION || ! constraint->contype == CONSTR_FOREIGN)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("primary key, unique, exclusion, or foreign key constraints are not supported on foreign tables"), parser_errposition(cxt->pstate, constraint->location))); *** a/src/include/commands/vacuum.h --- b/src/include/commands/vacuum.h *************** *** 140,145 **** extern int vacuum_multixact_freeze_min_age; --- 140,154 ---- extern int vacuum_multixact_freeze_table_age; + /* Possible modes for vacuum() */ + typedef enum + { + VAC_MODE_ALL, /* Vacuum/analyze all relations */ + VAC_MODE_SINGLE, /* Vacuum/analyze a specific relation */ + VAC_MODE_AUTOVACUUM /* Autovacuum worker */ + } VacuumMode; + + /* in commands/vacuum.c */ extern void vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast, BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel); *************** *** 174,180 **** extern void lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy); /* in commands/analyze.c */ ! extern void analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy); extern bool std_typanalyze(VacAttrStats *stats); extern double anl_random_fract(void); --- 183,189 ---- BufferAccessStrategy bstrategy); /* in commands/analyze.c */ ! extern void analyze_rel(Oid relid, VacuumStmt *vacstmt, VacuumMode vacmode, BufferAccessStrategy bstrategy); extern bool std_typanalyze(VacAttrStats *stats); extern double anl_random_fract(void); *** a/src/include/foreign/fdwapi.h --- b/src/include/foreign/fdwapi.h *************** *** 31,36 **** typedef void (*GetForeignPaths_function) (PlannerInfo *root, --- 31,42 ---- RelOptInfo *baserel, Oid foreigntableid); + typedef ForeignPath *(*ReparameterizeForeignPath_function) (PlannerInfo *root, + RelOptInfo *baserel, + Oid foreigntableid, + ForeignPath *path, + Relids required_outer); + typedef ForeignScan *(*GetForeignPlan_function) (PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid, *************** *** 117,122 **** typedef struct FdwRoutine --- 123,129 ---- /* Functions for scanning foreign tables */ GetForeignRelSize_function GetForeignRelSize; GetForeignPaths_function GetForeignPaths; + ReparameterizeForeignPath_function ReparameterizeForeignPath; GetForeignPlan_function GetForeignPlan; BeginForeignScan_function BeginForeignScan; IterateForeignScan_function IterateForeignScan; *** a/src/test/regress/expected/foreign_data.out --- b/src/test/regress/expected/foreign_data.out *************** *** 750,765 **** CREATE TABLE use_ft1_column_type (x ft1); ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET DATA TYPE integer; -- ERROR ERROR: cannot alter foreign table "ft1" because column "use_ft1_column_type.x" uses its row type DROP TABLE use_ft1_column_type; ! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0); -- ERROR ! ERROR: constraints are not supported on foreign tables ! LINE 1: ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c... ! ^ ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const; -- ERROR ! ERROR: "ft1" is not a table ALTER FOREIGN TABLE ft1 DROP CONSTRAINT IF EXISTS no_const; ! ERROR: "ft1" is not a table ! ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c1_check; ! ERROR: "ft1" is not a table ALTER FOREIGN TABLE ft1 SET WITH OIDS; -- ERROR ERROR: "ft1" is not a table ALTER FOREIGN TABLE ft1 OWNER TO regress_test_role; --- 750,761 ---- ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET DATA TYPE integer; -- ERROR ERROR: cannot alter foreign table "ft1" because column "use_ft1_column_type.x" uses its row type DROP TABLE use_ft1_column_type; ! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0); ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const; -- ERROR ! ERROR: constraint "no_const" of relation "ft1" does not exist ALTER FOREIGN TABLE ft1 DROP CONSTRAINT IF EXISTS no_const; ! NOTICE: constraint "no_const" of relation "ft1" does not exist, skipping ! ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c9_check; ALTER FOREIGN TABLE ft1 SET WITH OIDS; -- ERROR ERROR: "ft1" is not a table ALTER FOREIGN TABLE ft1 OWNER TO regress_test_role; *** a/src/test/regress/sql/foreign_data.sql --- b/src/test/regress/sql/foreign_data.sql *************** *** 314,323 **** ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET STATISTICS -1; CREATE TABLE use_ft1_column_type (x ft1); ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET DATA TYPE integer; -- ERROR DROP TABLE use_ft1_column_type; ! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0); -- ERROR ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const; -- ERROR ALTER FOREIGN TABLE ft1 DROP CONSTRAINT IF EXISTS no_const; ! ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c1_check; ALTER FOREIGN TABLE ft1 SET WITH OIDS; -- ERROR ALTER FOREIGN TABLE ft1 OWNER TO regress_test_role; ALTER FOREIGN TABLE ft1 OPTIONS (DROP delimiter, SET quote '~', ADD escape '@'); --- 314,323 ---- CREATE TABLE use_ft1_column_type (x ft1); ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET DATA TYPE integer; -- ERROR DROP TABLE use_ft1_column_type; ! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0); ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const; -- ERROR ALTER FOREIGN TABLE ft1 DROP CONSTRAINT IF EXISTS no_const; ! ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c9_check; ALTER FOREIGN TABLE ft1 SET WITH OIDS; -- ERROR ALTER FOREIGN TABLE ft1 OWNER TO regress_test_role; ALTER FOREIGN TABLE ft1 OPTIONS (DROP delimiter, SET quote '~', ADD escape '@');