diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 8ace8bd..b4e53c1 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -258,6 +258,17 @@ CREATE TABLE products ( 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,8 +2028,8 @@ CREATE TABLE capitals ( - In PostgreSQL, a table can inherit from - zero or more other tables, and a query can reference either all + 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, diff --git a/doc/src/sgml/ref/alter_foreign_table.sgml b/doc/src/sgml/ref/alter_foreign_table.sgml index 4d8cfc5..f7a382e 100644 --- a/doc/src/sgml/ref/alter_foreign_table.sgml +++ b/doc/src/sgml/ref/alter_foreign_table.sgml @@ -42,6 +42,8 @@ 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'] [, ... ]) + INHERIT parent_table + NO INHERIT parent_table OWNER TO new_owner OPTIONS ( [ ADD | SET | DROP ] option ['value'] [, ... ]) @@ -178,6 +180,26 @@ ALTER FOREIGN TABLE [ IF EXISTS ] name + INHERIT parent_table + + + This form adds the target foreign table as a new child of the specified + parent table. The parent table must be a plain table. + + + + + + NO INHERIT parent_table + + + This form removes the target foreign table from the list of children of + the specified parent table. + + + + + OPTIONS ( [ ADD | SET | DROP ] option ['value'] [, ... ] ) @@ -306,6 +328,16 @@ 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 a plain table. + + + diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml index 06a7087..cc11dee 100644 --- a/doc/src/sgml/ref/create_foreign_table.sgml +++ b/doc/src/sgml/ref/create_foreign_table.sgml @@ -22,6 +22,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] table_name column_name data_type [ OPTIONS ( option 'value' [, ... ] ) ] [ COLLATE collation ] [ column_constraint [ ... ] ] [, ... ] ] ) +[ INHERITS ( parent_table [, ... ] ) ] SERVER server_name [ OPTIONS ( option 'value' [, ... ] ) ] @@ -159,6 +160,18 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] table_name + parent_table + + + The name of an existing table from which the new foreign table + automatically inherits all columns. The specified parent table + must be a plain table. See for the + details of table inheritance. + + + + + server_name diff --git a/src/backend/catalog/pg_inherits.c b/src/backend/catalog/pg_inherits.c index f263b42..b4a084c 100644 --- a/src/backend/catalog/pg_inherits.c +++ b/src/backend/catalog/pg_inherits.c @@ -256,6 +256,48 @@ has_subclass(Oid relationId) /* + * Check wether the inheritance tree contains foreign table(s). + */ +bool +contains_foreign(Oid parentrelId, LOCKMODE lockmode) +{ + bool result = false; + List *tableOIDs; + ListCell *lc; + + /* Find all members of the inheritance tree */ + tableOIDs = find_all_inheritors(parentrelId, lockmode, NULL); + + /* There are no children */ + if (list_length(tableOIDs) < 2) + return result; + + foreach(lc, tableOIDs) + { + Oid childOID = lfirst_oid(lc); + Relation childrel; + + /* Parent should not be foreign */ + if (childOID == parentrelId) + continue; + + /* We already got the needed lock */ + childrel = heap_open(childOID, NoLock); + + if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) + { + /* Found it */ + result = true; + } + + heap_close(childrel, lockmode); + } + + return result; +} + + +/* * Given two type OIDs, determine whether the first is a complex type * (class type) that inherits from the second. */ diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index a04adea..bb7bdf4 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -115,7 +115,8 @@ 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) +analyze_rel(Oid relid, VacuumStmt *vacstmt, VacuumMode vacmode, + BufferAccessStrategy bstrategy) { Relation onerel; int elevel; @@ -270,7 +271,9 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy) /* * If there are child tables, do recursive ANALYZE. */ - if (onerel->rd_rel->relhassubclass) + if (onerel->rd_rel->relhassubclass && + (vacmode == VAC_MODE_SINGLE || + !contains_foreign(RelationGetRelid(onerel), AccessShareLock))) do_analyze_rel(onerel, vacstmt, acquirefunc, relpages, true, elevel); /* @@ -1452,12 +1455,15 @@ acquire_inherited_sample_rows(Relation onerel, int elevel, { List *tableOIDs; Relation *rels; + AcquireSampleRowsFunc *acquirefunc; double *relblocks; double totalblocks; + Relation saved_rel; int numrows, nrels, i; ListCell *lc; + bool isAnalyzable = true; /* * Find all members of inheritance set. We only need AccessShareLock on @@ -1486,6 +1492,8 @@ acquire_inherited_sample_rows(Relation onerel, int elevel, * 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,12 +1515,59 @@ acquire_inherited_sample_rows(Relation onerel, int elevel, } rels[nrels] = childrel; - relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel); + + if (childrel->rd_rel->relkind != RELKIND_FOREIGN_TABLE) + { + acquirefunc[nrels] = acquire_sample_rows; + relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel); + } + else + { + /* + * For a foreign table, call the FDW's hook function to see whether + * it supports analysis. + */ + FdwRoutine *fdwroutine; + BlockNumber relpages = 0; + bool ok = false; + + fdwroutine = GetFdwRoutineForRelation(childrel, false); + if (fdwroutine->AnalyzeForeignTable != NULL) + ok = fdwroutine->AnalyzeForeignTable(childrel, + &acquirefunc[nrels], + &relpages); + if (!ok) + { + isAnalyzable = false; + break; + } + + relblocks[nrels] = (double) relpages; + } + totalblocks += relblocks[nrels]; nrels++; } /* + * If there is at least one foreign table that cannot be analyzed, give up. + */ + if (!isAnalyzable) + { + ereport(WARNING, + (errmsg("skipping \"%s\" inheritance tree --- cannot analyze foreign table \"%s\"", + RelationGetRelationName(onerel), + RelationGetRelationName(rels[nrels])))); + for (i = 0; i < nrels; i++) + { + Relation childrel = rels[i]; + + heap_close(childrel, NoLock); + } + return 0; + } + + /* * Now sample rows from each relation, proportionally to its fraction of * the total block count. (This might be less than desirable if the child * rels have radically different free-space percentages, but it's not @@ -1525,6 +1580,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel, { Relation childrel = rels[i]; double childblocks = relblocks[i]; + AcquireSampleRowsFunc childacquirefunc = acquirefunc[i]; if (childblocks > 0) { @@ -1540,12 +1596,12 @@ 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); + childrows = childacquirefunc(childrel, + elevel, + rows + numrows, + childtargrows, + &trows, + &tdrows); /* 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 25f01e5..61ea18b 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -311,7 +311,8 @@ 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); + 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,10 +468,6 @@ 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, @@ -3019,24 +3016,28 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, * rules. */ ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE); - ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); + /* Recurses 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); - ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); + /* Recurses 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); - ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); + /* Recurses 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 */ - ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); + /* Recurses 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,7 +3050,8 @@ 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); + /* 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,7 +3069,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, pass = AT_PASS_ADD_INDEX; break; case AT_AddConstraint: /* ADD CONSTRAINT */ - ATSimplePermissions(rel, ATT_TABLE); + ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); /* Recursion occurs during execution phase */ /* No command-specific prep needed except saving recurse flag */ if (recurse) @@ -3081,7 +3083,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, pass = AT_PASS_ADD_CONSTR; break; case AT_DropConstraint: /* DROP CONSTRAINT */ - ATSimplePermissions(rel, ATT_TABLE); + ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); /* Recursion occurs during execution phase */ /* No command-specific prep needed except saving recurse flag */ if (recurse) @@ -3149,13 +3151,19 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, pass = AT_PASS_MISC; break; case AT_AddInherit: /* INHERIT */ - ATSimplePermissions(rel, ATT_TABLE); + 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); + ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); pass = AT_PASS_MISC; break; case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */ @@ -3184,7 +3192,6 @@ 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); @@ -4037,8 +4044,12 @@ ATSimplePermissions(Relation rel, int allowed_targets) case RELKIND_COMPOSITE_TYPE: actual_target = ATT_COMPOSITE_TYPE; break; + case RELKIND_FOREIGN_TABLE: actual_target = ATT_FOREIGN_TABLE; + ereport(NOTICE, + (errmsg("This command affects foreign relation \"%s\"", + RelationGetRelationName(rel)))); break; default: actual_target = 0; @@ -4125,7 +4136,8 @@ ATWrongRelkindError(Relation rel, int allowed_targets) */ static void ATSimpleRecursion(List **wqueue, Relation rel, - AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode) + AlterTableCmd *cmd, bool recurse, + bool include_foreign, LOCKMODE lockmode) { /* * Propagate to children if desired. Non-table relations never have @@ -4153,8 +4165,12 @@ 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); + 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,7 +4460,7 @@ 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); + ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); attrdesc = heap_open(AttributeRelationId, RowExclusiveLock); @@ -4859,7 +4875,19 @@ ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC ATPrepAddColumn(wqueue, rel, recurse, false, cmd, lockmode); if (recurse) + { + /* + * Don't allow to add an OID column to inheritance tree that contains + * foreign table(s) + */ + if (contains_foreign(RelationGetRelid(rel), AccessShareLock)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot add OID column to inheritance tree \"%s\" because it contains foreign table(s)", + RelationGetRelationName(rel)))); + cmd->subtype = AT_AddOidsRecurse; + } } /* @@ -5340,7 +5368,7 @@ 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); + ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); /* * get the number of the attribute @@ -5732,7 +5760,14 @@ 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); + ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); + + /* Don't allow ADD constraint NOT VALID on 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,9 +5778,17 @@ 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. - */ + * + * 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 = copyObject(constr); + if (tab->relkind == RELKIND_FOREIGN_TABLE && + constr->skip_validation && recursing) + constr->skip_validation = false; newcons = AddRelationNewConstraints(rel, NIL, - list_make1(copyObject(constr)), + list_make1(constr), recursing, /* allow_merge */ !recursing, /* is_local */ is_readd); /* is_internal */ @@ -7225,7 +7268,7 @@ ATExecDropConstraint(Relation rel, const char *constrName, /* At top level, permission check was done in ATPrepCmd, else do it */ if (recursing) - ATSimplePermissions(rel, ATT_TABLE); + ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); conrel = heap_open(ConstraintRelationId, RowExclusiveLock); @@ -7560,7 +7603,10 @@ ATPrepAlterColumnType(List **wqueue, * alter would put them out of step. */ if (recurse) - ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); + { + /* Recurses 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, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index ded1841..35b5dd2 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -98,6 +98,7 @@ vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast, BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel) { const char *stmttype; + VacuumMode vacmode; volatile bool in_outer_xact, use_own_xacts; List *relations; @@ -146,6 +147,20 @@ vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast, 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) + vacmode = VAC_MODE_AUTOVACUUM; + else + { + if (!vacstmt->relation) + vacmode = VAC_MODE_ALL; + else + vacmode = VAC_MODE_SINGLE; + } + + /* * If caller didn't give us a buffer strategy object, make one in the * cross-transaction memory context. */ @@ -248,7 +263,7 @@ vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast, PushActiveSnapshot(GetTransactionSnapshot()); } - analyze_rel(relid, vacstmt, vac_strategy); + analyze_rel(relid, vacstmt, vacmode, vac_strategy); if (use_own_xacts) { diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 52dcc72..238bba2 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -1337,11 +1337,12 @@ 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. + * 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); diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index b79af7a..18ced04 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -2062,6 +2062,16 @@ reparameterize_path(PlannerInfo *root, Path *path, case T_SubqueryScan: return create_subqueryscan_path(root, rel, path->pathkeys, required_outer); + case T_ForeignScan: + { + ForeignPath *fpath = (ForeignPath*) path; + ForeignPath *newpath = makeNode(ForeignPath); + memcpy(newpath, fpath, sizeof(ForeignPath)); + newpath->path.param_info = + get_baserel_parampathinfo(root, rel, required_outer); + /* cost recalc omitted */ + return (Path *)newpath; + } default: break; } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index e3060a4..b5e47f4 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -4209,32 +4209,32 @@ AlterForeignServerStmt: ALTER SERVER name foreign_server_version alter_generic_o CreateForeignTableStmt: CREATE FOREIGN TABLE qualified_name '(' OptTableElementList ')' - SERVER name create_generic_options + 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 = NIL; + n->base.inhRelations = $8; n->base.if_not_exists = false; /* FDW-specific data */ - n->servername = $9; - n->options = $10; + n->servername = $10; + n->options = $11; $$ = (Node *) n; } | CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name '(' OptTableElementList ')' - SERVER name create_generic_options + 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 = NIL; + n->base.inhRelations = $11; n->base.if_not_exists = true; /* FDW-specific data */ - n->servername = $12; - n->options = $13; + n->servername = $13; + n->options = $14; $$ = (Node *) n; } ; diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 1e071d7..955f27c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -515,12 +515,6 @@ 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; @@ -529,7 +523,7 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) if (cxt->isforeign) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("constraints are not supported on foreign tables"), + errmsg("primary key or unique constraints are not supported on foreign tables"), parser_errposition(cxt->pstate, constraint->location))); if (constraint->keys == NIL) @@ -546,7 +540,7 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) if (cxt->isforeign) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("constraints are not supported on foreign tables"), + errmsg("foreign key constraints are not supported on foreign tables"), parser_errposition(cxt->pstate, constraint->location))); @@ -605,10 +599,14 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) static void transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint) { - if (cxt->isforeign) + 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("constraints are not supported on foreign tables"), + errmsg("primary key, unique, exclusion, or foreign key constraints are not supported on foreign tables"), parser_errposition(cxt->pstate, constraint->location))); diff --git a/src/include/catalog/pg_inherits_fn.h b/src/include/catalog/pg_inherits_fn.h index 757d849..228573a 100644 --- a/src/include/catalog/pg_inherits_fn.h +++ b/src/include/catalog/pg_inherits_fn.h @@ -21,6 +21,7 @@ extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode); extern List *find_all_inheritors(Oid parentrelId, LOCKMODE lockmode, List **parents); extern bool has_subclass(Oid relationId); +extern bool contains_foreign(Oid parentrelId, LOCKMODE lockmode); extern bool typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId); #endif /* PG_INHERITS_FN_H */ diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h index 058dc5f..8253a07 100644 --- a/src/include/commands/vacuum.h +++ b/src/include/commands/vacuum.h @@ -140,6 +140,15 @@ extern int vacuum_multixact_freeze_min_age; 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,7 +183,7 @@ extern void lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy); /* in commands/analyze.c */ -extern void analyze_rel(Oid relid, VacuumStmt *vacstmt, +extern void analyze_rel(Oid relid, VacuumStmt *vacstmt, VacuumMode vacmode, BufferAccessStrategy bstrategy); extern bool std_typanalyze(VacAttrStats *stats); extern double anl_random_fract(void); diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out index 60506e0..f15d2e1 100644 --- a/src/test/regress/expected/foreign_data.out +++ b/src/test/regress/expected/foreign_data.out @@ -750,16 +750,12 @@ 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 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0); ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const; -- ERROR -ERROR: "ft1" is not a table +ERROR: constraint "no_const" of relation "ft1" does not exist 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 +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; diff --git a/src/test/regress/sql/foreign_data.sql b/src/test/regress/sql/foreign_data.sql index f819eb1..2aa5ffe 100644 --- a/src/test/regress/sql/foreign_data.sql +++ b/src/test/regress/sql/foreign_data.sql @@ -314,10 +314,10 @@ 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 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_c1_check; +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 '@');