pg.ddx.io pgsql-hackers@postgresql.org mailing list archive
help / color / mirror / Atom feedinherit support for foreign tables
179+ messages / 19 participants
[nested] [flat]
* inherit support for foreign tables
@ 2013-11-14 04:51 Shigeru Hanada <shigeru.hanada@gmail.com>
0 siblings, 1 reply; 179+ messages in thread
From: Shigeru Hanada @ 2013-11-14 04:51 UTC (permalink / raw)
To: pgsql-hackers
Hi hackers,
I'd like to propose adding inheritance support for foriegn tables.
David Fetter mentioned this feature last July, but it seems stalled.
http://www.postgresql.org/message-id/20130719005601.GA5760@fetter.org
Supporting inheritance by foreign tables allows us to distribute query
to remote servers by using foreign tables as partition table of a
(perhaps ordinary) table. For this purpose, I think that constraint
exclusion is necessary.
As result of extending Devid's patch for PoC, and AFAIS we need these changes:
1) Add INHERITS(rel, ...) clause to CREATE/ALTER FOREIGN TABLE
Apperantly we need to add new syntax to define parent table(s) of a
foreign table. We have options about the position of INHERIT clause,
but I'd prefer before SERVER clause because having options specific to
foreign tables at the tail would be most extensible.
a) CREATE FOREIGN TABLE child (...) INHERITS(p1, p2) SERVER server;
b) CREATE FOREIGN TABLE child (...) SERVER server INHERITS(p1, p2);
2) Allow foreign tables to have CHECK constraints
Like NOT NULL, I think we don't need to enforce the check duroing
INSERT/UPDATE against foreign table.
3) Allow foreign table as a child node of Append
Currently prepunion.c assumes that children of Append have
RELKIND_RELATION as relkind always, so we need to set relkind of child
RTE explicitly.
Please see attached PoC patch. I'll enhance implementation, tests and
document and submit the patch for the next CF.
Regards,
--
Shigeru HANADA
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0b31f55..2f2dc88 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -465,10 +465,25 @@ 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")));
+/*
+ * Shouldn't this have been checked in parser?
+ */
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ ListCell *lc;
+ foreach(lc, stmt->constraints)
+ {
+ NewConstraint *nc = lfirst(lc);
+
+ if (nc->contype != CONSTR_CHECK &&
+ nc->contype != CONSTR_DEFAULT &&
+ nc->contype != CONSTR_NULL &&
+ nc->contype != CONSTR_NOTNULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("only check constraints are supported on foreign tables")));
+ }
+ }
/*
* Look up the namespace in which we are supposed to create the relation,
@@ -1463,10 +1478,11 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("inherited relation \"%s\" is not a table",
+ errmsg("inherited relation \"%s\" is not a table or foreign table",
parent->relname)));
/* Permanent rels cannot inherit from temporary ones */
if (relpersistence != RELPERSISTENCE_TEMP &&
@@ -3043,7 +3059,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)
@@ -3057,7 +3073,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)
@@ -3125,13 +3141,13 @@ 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_AlterConstraint: /* ALTER CONSTRAINT */
- ATSimplePermissions(rel, ATT_TABLE);
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
pass = AT_PASS_MISC;
break;
case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index e249628..0d76221 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/parser/gram.y b/src/backend/parser/gram.y
index 11f6291..613a722 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -4191,32 +4191,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 19d19e5f..8cf2998 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -515,12 +515,14 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
break;
case CONSTR_CHECK:
+#ifdef NOT_USED
if (cxt->isforeign)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+#endif
cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
break;
@@ -605,10 +607,10 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
static void
transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
{
- if (cxt->isforeign)
+ if (cxt->isforeign && constraint->contype != CONSTR_CHECK)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("constraints are not supported on foreign tables"),
+ errmsg("only check constraints are supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/plain] foreign_inherit.diff (6.8K, ../../CAEZqfEfVJNptoTSEY3QrQ90K9WD2nxfM+6fsk3YTc1ZXZ1Y5-g@mail.gmail.com/2-foreign_inherit.diff)
download | inline diff:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0b31f55..2f2dc88 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -465,10 +465,25 @@ 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")));
+/*
+ * Shouldn't this have been checked in parser?
+ */
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ ListCell *lc;
+ foreach(lc, stmt->constraints)
+ {
+ NewConstraint *nc = lfirst(lc);
+
+ if (nc->contype != CONSTR_CHECK &&
+ nc->contype != CONSTR_DEFAULT &&
+ nc->contype != CONSTR_NULL &&
+ nc->contype != CONSTR_NOTNULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("only check constraints are supported on foreign tables")));
+ }
+ }
/*
* Look up the namespace in which we are supposed to create the relation,
@@ -1463,10 +1478,11 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("inherited relation \"%s\" is not a table",
+ errmsg("inherited relation \"%s\" is not a table or foreign table",
parent->relname)));
/* Permanent rels cannot inherit from temporary ones */
if (relpersistence != RELPERSISTENCE_TEMP &&
@@ -3043,7 +3059,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)
@@ -3057,7 +3073,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)
@@ -3125,13 +3141,13 @@ 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_AlterConstraint: /* ALTER CONSTRAINT */
- ATSimplePermissions(rel, ATT_TABLE);
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
pass = AT_PASS_MISC;
break;
case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index e249628..0d76221 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/parser/gram.y b/src/backend/parser/gram.y
index 11f6291..613a722 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -4191,32 +4191,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 19d19e5f..8cf2998 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -515,12 +515,14 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
break;
case CONSTR_CHECK:
+#ifdef NOT_USED
if (cxt->isforeign)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+#endif
cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
break;
@@ -605,10 +607,10 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
static void
transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
{
- if (cxt->isforeign)
+ if (cxt->isforeign && constraint->contype != CONSTR_CHECK)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("constraints are not supported on foreign tables"),
+ errmsg("only check constraints are supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2013-11-14 05:28 Tom Lane <tgl@sss.pgh.pa.us>
parent: Shigeru Hanada <shigeru.hanada@gmail.com>
0 siblings, 1 reply; 179+ messages in thread
From: Tom Lane @ 2013-11-14 05:28 UTC (permalink / raw)
To: Shigeru Hanada <shigeru.hanada@gmail.com>; +Cc: pgsql-hackers
Shigeru Hanada <shigeru.hanada@gmail.com> writes:
> I'd like to propose adding inheritance support for foriegn tables.
> David Fetter mentioned this feature last July, but it seems stalled.
> http://www.postgresql.org/message-id/20130719005601.GA5760@fetter.org
The discussion there pointed out that not enough consideration had been
given to interactions with other commands. I'm not really satisfied
with your analysis here. In particular:
> 2) Allow foreign tables to have CHECK constraints
> Like NOT NULL, I think we don't need to enforce the check duroing
> INSERT/UPDATE against foreign table.
Really? It's one thing to say that somebody who adds a CHECK constraint
to a foreign table is responsible to make sure that the foreign data will
satisfy the constraint. It feels like a different thing to say that ALTER
TABLE ADD CONSTRAINT applied to a parent table will silently assume that
some child table that happens to be foreign doesn't need any enforcement.
Perhaps more to the point, inheritance trees are the main place where the
planner depends on the assumption that CHECK constraints represent
reality. Are we really prepared to say that it's the user's fault if the
planner generates an incorrect plan on the strength of a CHECK constraint
that's not actually satisfied by the foreign data? If so, that had better
be documented by this patch. But for a project that refuses to let people
create a local CHECK or FOREIGN KEY constraint without mechanically
checking it, it seems pretty darn weird to be so laissez-faire about
constraints on foreign data.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2013-11-18 14:36 Robert Haas <robertmhaas@gmail.com>
parent: Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 2 replies; 179+ messages in thread
From: Robert Haas @ 2013-11-18 14:36 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On Thu, Nov 14, 2013 at 12:28 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> 2) Allow foreign tables to have CHECK constraints
>> Like NOT NULL, I think we don't need to enforce the check duroing
>> INSERT/UPDATE against foreign table.
>
> Really? It's one thing to say that somebody who adds a CHECK constraint
> to a foreign table is responsible to make sure that the foreign data will
> satisfy the constraint. It feels like a different thing to say that ALTER
> TABLE ADD CONSTRAINT applied to a parent table will silently assume that
> some child table that happens to be foreign doesn't need any enforcement.
>
> Perhaps more to the point, inheritance trees are the main place where the
> planner depends on the assumption that CHECK constraints represent
> reality. Are we really prepared to say that it's the user's fault if the
> planner generates an incorrect plan on the strength of a CHECK constraint
> that's not actually satisfied by the foreign data? If so, that had better
> be documented by this patch. But for a project that refuses to let people
> create a local CHECK or FOREIGN KEY constraint without mechanically
> checking it, it seems pretty darn weird to be so laissez-faire about
> constraints on foreign data.
I can see both sides of this issue. We certainly have no way to force
the remote side to enforce CHECK constraints defined on the local
side, because the remote side could also be accepting writes from
other sources that don't have any matching constraint. But having said
that, I can't see any particularly principled reason why we shouldn't
at least check the new rows we insert ourselves. After all, we could
be in the situation proposed by KaiGai Kohei, where the foreign data
wrapper API is being used as a surrogate storage engine API - i.e.
there are no writers to the foreign side except ourselves. In that
situation, it would seem odd to randomly fail to enforce the
constraints.
On the other hand, the performance costs of checking every row bound
for the remote table could be quite steep. Consider an update on an
inheritance hierarchy that sets a = a + 1 for every row. If we don't
worry about verifying that the resulting rows satisfy all local-side
constraints, we can potentially ship a single update statement to the
remote server and let it do all the work there. But if we DO have to
worry about that, then we're going to have to ship every updated row
over the wire in at least one direction, if not both. If the purpose
of adding CHECK constraints was to enable constraint exclusion, that's
a mighty steep price to pay for it.
I think it's been previously proposed that we have some version of a
CHECK constraint that effectively acts as an assertion for query
optimization purposes, but isn't actually enforced by the system. I
can see that being useful in a variety of situations, including this
one.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2013-11-18 14:55 Tom Lane <tgl@sss.pgh.pa.us>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 1 reply; 179+ messages in thread
From: Tom Lane @ 2013-11-18 14:55 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
> On Thu, Nov 14, 2013 at 12:28 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>>> 2) Allow foreign tables to have CHECK constraints
>>> Like NOT NULL, I think we don't need to enforce the check duroing
>>> INSERT/UPDATE against foreign table.
>> Really?
> I think it's been previously proposed that we have some version of a
> CHECK constraint that effectively acts as an assertion for query
> optimization purposes, but isn't actually enforced by the system. I
> can see that being useful in a variety of situations, including this
> one.
Yeah, I think it would be much smarter to provide a different syntax
to explicitly represent the notion that we're only assuming the condition
is true, and not trying to enforce it.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-14 09:24 Shigeru Hanada <shigeru.hanada@gmail.com>
parent: Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 2 replies; 179+ messages in thread
From: Shigeru Hanada @ 2014-01-14 09:24 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
2013/11/18 Tom Lane <tgl@sss.pgh.pa.us>:
> Robert Haas <robertmhaas@gmail.com> writes:
>> I think it's been previously proposed that we have some version of a
>> CHECK constraint that effectively acts as an assertion for query
>> optimization purposes, but isn't actually enforced by the system. I
>> can see that being useful in a variety of situations, including this
>> one.
>
> Yeah, I think it would be much smarter to provide a different syntax
> to explicitly represent the notion that we're only assuming the condition
> is true, and not trying to enforce it.
I'd like to revisit this feature.
Explicit notation to represent not-enforcing (assertive?) is an
interesting idea. I'm not sure which word is appropriate, but for
example, let's use the word ASSERTIVE as a new constraint attribute.
CREATE TABLE parent (
id int NOT NULL,
group int NOT NULL,
name text
);
CREATE FOREIGN TABLE child_grp1 (
/* no additional column */
) INHERITS (parent) SERVER server1;
ALTER TABLE child_grp1 ADD CONSTRAINT chk_group1 CHECK (group = 1) ASSERTIVE;
If ASSERTIVE is specified, it's not guaranteed that the constraint is
enforced completely, so it should be treated as a hint for planner.
As Robert mentioned, enforcing as much as we can during INSERT/UPDATE
is one option about this issue.
In addition, an idea which I can't throw away is to assume that all
constraints defined on foreign tables as ASSERTIVE. Foreign tables
potentially have dangers to have "wrong" data by updating source data
not through foreign tables. This is not specific to an FDW, so IMO
constraints defined on foreign tables are basically ASSERTIVE. Of
course PG can try to maintain data correct, but always somebody might
break it.
Besides CHECK constraints, currently NOT NULL constraints are
virtually ASSERTIVE (not enforcing). Should it also be noted
explicitly?
Thoughts?
--
Shigeru HANADA
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-14 20:40 Shigeru Hanada <shigeru.hanada@gmail.com>
parent: Shigeru Hanada <shigeru.hanada@gmail.com>
1 sibling, 0 replies; 179+ messages in thread
From: Shigeru Hanada @ 2014-01-14 20:40 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
Hi all,
2014/1/14 Shigeru Hanada <shigeru.hanada@gmail.com>:
> I'd like to revisit this feature.
Attached patch allows a foreign table to be a child of a table. It
also allows foreign tables to have CHECK constraints. These changes
provide us a chance to propagate query load to multiple servers via
constraint exclusion. If FDW supports async operation against remote
server, parallel processing (not stable but read-only case would be
find) can be achieved, though overhead of FDW mechanism is still
there.
Though this would be debatable, in current implementation, constraints
defined on a foreign table (now only NOT NULL and CHECK are supported)
are not enforced during INSERT or UPDATE executed against foreign
tables. This means that retrieved data might violates the constraints
defined on local side. This is debatable issue because integrity of
data is important for DBMS, but in the first cut, it is just
documented as a note.
Because I don't see practical case to have a foreign table as a
parent, and it avoid a problem about recursive ALTER TABLE operation,
foreign tables can't be a parent. An example of such problem is
adding constraint which is not unsupported for foreign tables to the
parent of foreign table. Propagated operation can be applied to
ordinary tables in the inheritance tree, but can't be to foreign
tables. If we allow foreign tables to be parent, it's difficult to
process ordinary tables below foreign tables in current traffic cop
mechanism.
For other commands recursively processed such as ANALYZE, foreign
tables in the leaf of inheritance tree are ignored.
Any comments or questions are welcome.
--
Shigeru HANADA
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[application/octet-stream] foreign_inherit.patch (16.9K, ../../CAEZqfEdWgKft9d-ursc+cwjP9y3T4N5WFNkt+B_FQm32BWdqvA@mail.gmail.com/2-foreign_inherit.patch)
download | inline diff:
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index bae2e97..b9edeae 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.
</para>
+ <note>
+ <para>
+ 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
+ <xref linkend="ddl-inherit">) to off-load to multiple servers.
+ </para>
+ </note>
+
<sect2 id="ddl-constraints-check-constraints">
<title>Check Constraints</title>
@@ -2021,8 +2032,8 @@ CREATE TABLE capitals (
</para>
<para>
- In <productname>PostgreSQL</productname>, a table can inherit from
- zero or more other tables, and a query can reference either all
+ In <productname>PostgreSQL</productname>, 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 723aa07..0ec16aa 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 ] <replaceable class="PARAMETER">name</replaceab
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
@@ -178,6 +180,26 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
</varlistentry>
<varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
<listitem>
<para>
@@ -306,6 +328,15 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">parent_name</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml
index 1ef4b5e..a87d18b 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 ] <replaceable class="PARAMETER">table_name
<replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
+[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
@@ -159,6 +160,17 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ The name of an existing table or foreign table from which the new foreign
+ table automatically inherits all columns, see
+ <xref linkend="ddl-inherit"> for details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index e7fcb55..8814387 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1496,6 +1496,13 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
/* We already got the needed lock */
childrel = heap_open(childOID, NoLock);
+ /* Ignore foreignt tables */
+ if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ heap_close(childrel, AccessShareLock);
+ continue;
+ }
+
/* Ignore if temp table of another backend */
if (RELATION_IS_OTHER_TEMP(childrel))
{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 466d757..9cad0ea 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -465,10 +465,25 @@ 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")));
+/*
+ * Shouldn't this have been checked in parser?
+ */
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ ListCell *lc;
+ foreach(lc, stmt->constraints)
+ {
+ NewConstraint *nc = lfirst(lc);
+
+ if (nc->contype != CONSTR_CHECK &&
+ nc->contype != CONSTR_DEFAULT &&
+ nc->contype != CONSTR_NULL &&
+ nc->contype != CONSTR_NOTNULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("only check constraints are supported on foreign tables")));
+ }
+ }
/*
* Look up the namespace in which we are supposed to create the relation,
@@ -3044,7 +3059,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)
@@ -3058,7 +3073,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)
@@ -3126,13 +3141,13 @@ 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_AlterConstraint: /* ALTER CONSTRAINT */
- ATSimplePermissions(rel, ATT_TABLE);
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
pass = AT_PASS_MISC;
break;
case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
@@ -3161,7 +3176,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);
@@ -3169,6 +3183,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_DropInherit: /* NO INHERIT */
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ /* These commands never recurse */
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
case AT_GenericOptions:
ATSimplePermissions(rel, ATT_FOREIGN_TABLE);
/* No command-specific prep needed */
@@ -4421,7 +4441,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);
@@ -5317,7 +5337,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
@@ -5708,7 +5728,7 @@ 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);
/*
* Call AddRelationNewConstraints to do the work, making sure it works on
@@ -7197,7 +7217,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);
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/parser/gram.y b/src/backend/parser/gram.y
index f0b9507..e856aac 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -4206,32 +4206,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 eb07ca3..7a01e18 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;
@@ -605,10 +599,10 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
static void
transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
{
- if (cxt->isforeign)
+ if (cxt->isforeign && constraint->contype != CONSTR_CHECK)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("constraints are not supported on foreign tables"),
+ errmsg("only check constraints are supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
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 '@');
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-14 22:35 Jim Nasby <jim@nasby.net>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 0 replies; 179+ messages in thread
From: Jim Nasby @ 2014-01-14 22:35 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On 11/18/13, 8:36 AM, Robert Haas wrote:
> On the other hand, the performance costs of checking every row bound
> for the remote table could be quite steep. Consider an update on an
> inheritance hierarchy that sets a = a + 1 for every row. If we don't
> worry about verifying that the resulting rows satisfy all local-side
> constraints, we can potentially ship a single update statement to the
> remote server and let it do all the work there. But if we DO have to
> worry about that, then we're going to have to ship every updated row
> over the wire in at least one direction, if not both. If the purpose
> of adding CHECK constraints was to enable constraint exclusion, that's
> a mighty steep price to pay for it.
A sophisticated enough FDW could verify that the appropriate check already existed in tho foreign side, or it could do something like:
BEGIN;
UPDATE SET ... WHERE <where>
SELECT EXISTS( SELECT 1 WHERE <where> AND NOT (<check condition>) );
And then rollback if the SELECT returns true.
But obviously you can't always do that, so I think there's a place for both true constraints and "suggested constraints".
--
Jim C. Nasby, Data Architect jim@nasby.net
512.569.9461 (cell) http://jim.nasby.net
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-21 01:42 KaiGai Kohei <kaigai@ak.jp.nec.com>
parent: Shigeru Hanada <shigeru.hanada@gmail.com>
1 sibling, 1 reply; 179+ messages in thread
From: KaiGai Kohei @ 2014-01-21 01:42 UTC (permalink / raw)
To: Shigeru Hanada <shigeru.hanada@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
(2014/01/14 18:24), Shigeru Hanada wrote:
> 2013/11/18 Tom Lane <tgl@sss.pgh.pa.us>:
>> Robert Haas <robertmhaas@gmail.com> writes:
>>> I think it's been previously proposed that we have some version of a
>>> CHECK constraint that effectively acts as an assertion for query
>>> optimization purposes, but isn't actually enforced by the system. I
>>> can see that being useful in a variety of situations, including this
>>> one.
>>
>> Yeah, I think it would be much smarter to provide a different syntax
>> to explicitly represent the notion that we're only assuming the condition
>> is true, and not trying to enforce it.
>
> I'd like to revisit this feature.
>
> Explicit notation to represent not-enforcing (assertive?) is an
> interesting idea. I'm not sure which word is appropriate, but for
> example, let's use the word ASSERTIVE as a new constraint attribute.
>
> CREATE TABLE parent (
> id int NOT NULL,
> group int NOT NULL,
> name text
> );
>
> CREATE FOREIGN TABLE child_grp1 (
> /* no additional column */
> ) INHERITS (parent) SERVER server1;
> ALTER TABLE child_grp1 ADD CONSTRAINT chk_group1 CHECK (group = 1) ASSERTIVE;
>
> If ASSERTIVE is specified, it's not guaranteed that the constraint is
> enforced completely, so it should be treated as a hint for planner.
> As Robert mentioned, enforcing as much as we can during INSERT/UPDATE
> is one option about this issue.
>
> In addition, an idea which I can't throw away is to assume that all
> constraints defined on foreign tables as ASSERTIVE. Foreign tables
> potentially have dangers to have "wrong" data by updating source data
> not through foreign tables. This is not specific to an FDW, so IMO
> constraints defined on foreign tables are basically ASSERTIVE. Of
> course PG can try to maintain data correct, but always somebody might
> break it.
> qu
>
Does it make sense to apply "assertive" CHECK constraint on the qual
of ForeignScan to filter out tuples with violated values at the local
side, as if row-level security feature doing.
It enables to handle a situation that planner expects only "clean"
tuples are returned but FDW driver is unavailable to anomalies.
Probably, this additional check can be turned on/off on the fly,
if FDW driver has a way to inform the core system its capability,
like FDW_CAN_ENFORCE_CHECK_CONSTRAINT that informs planner to skip
local checks.
> Besides CHECK constraints, currently NOT NULL constraints are
> virtually ASSERTIVE (not enforcing). Should it also be noted
> explicitly?
>
Backward compatibility....
NOT NULL [ASSERTIVE] might be an option.
Thanks,
--
OSS Promotion Center / The PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-21 02:44 Shigeru Hanada <shigeru.hanada@gmail.com>
parent: KaiGai Kohei <kaigai@ak.jp.nec.com>
0 siblings, 2 replies; 179+ messages in thread
From: Shigeru Hanada @ 2014-01-21 02:44 UTC (permalink / raw)
To: KaiGai Kohei <kaigai@ak.jp.nec.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
Thanks for the comments.
2014/1/21 KaiGai Kohei <kaigai@ak.jp.nec.com>:
>> In addition, an idea which I can't throw away is to assume that all
>> constraints defined on foreign tables as ASSERTIVE. Foreign tables
>> potentially have dangers to have "wrong" data by updating source data
>> not through foreign tables. This is not specific to an FDW, so IMO
>> constraints defined on foreign tables are basically ASSERTIVE. Of
>> course PG can try to maintain data correct, but always somebody might
>> break it.
>> qu
>>
> Does it make sense to apply "assertive" CHECK constraint on the qual
> of ForeignScan to filter out tuples with violated values at the local
> side, as if row-level security feature doing.
> It enables to handle a situation that planner expects only "clean"
> tuples are returned but FDW driver is unavailable to anomalies.
>
> Probably, this additional check can be turned on/off on the fly,
> if FDW driver has a way to inform the core system its capability,
> like FDW_CAN_ENFORCE_CHECK_CONSTRAINT that informs planner to skip
> local checks.
Hmm, IIUC you mean that local users can't (or don't need to) know that
data which violates the local constraints exist on remote side.
Applying constraints to the data which is modified through FDW would
be necessary as well. In that design, FDW is a bidirectional filter
which provides these features:
1) Don't push wrong data into remote data source, by applying local
constraints to the result of the modifying query executed on local PG.
This is not perfect filter, because remote constraints don't mapped
automatically or perfectly (imagine constraints which is available on
remote but is not supported in PG).
2) Don't retrieve wrong data from remote to local PG, by applying
local constraints
I have a concern about consistency. It has not been supported, but
let's think of Aggregate push-down invoked by a query below.
SELECT count(*) FROM remote_table;
If this query was fully pushed down, the result is the # of records
exist on remote side, but the result would be # of valid records when
we don't push down the aggregate. This would confuse users.
>> Besides CHECK constraints, currently NOT NULL constraints are
>> virtually ASSERTIVE (not enforcing). Should it also be noted
>> explicitly?
>>
> Backward compatibility….
Yep, backward compatibility (especially visible ones to users) should
be minimal, ideally zero.
> NOT NULL [ASSERTIVE] might be an option.
Treating [ASSERTIVE | NOT ASSERTIVE] like DEFERRABLE, and allow
ingASSERTIVE for only foreign tables? It makes sense, though we need
consider exclusiveness . But It needs to default to ASSERTIVE on
foreign tables, and NOT ASSERTIVE (means "forced") on others. Isn't
is too complicated?
CREATE FOREIGN TABLE foo (
id int NOT NULL ASSERTIVE CHECK (id > 1) ASSERTIVE,
…
CONSTRAINT chk_foo_name_upper CHECK (upper(name) = name) ASSERTIVE
) SERVER server;
BTW, I noticed that this is like push-down-able expressions in
JOIN/WHERE. We need to check a CHECK constraint defined on a foreign
tables contains only expressions which have same semantics as remote
side (in practice, built-in and immutable)?
--
Shigeru HANADA
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-21 05:24 KaiGai Kohei <kaigai@ak.jp.nec.com>
parent: Shigeru Hanada <shigeru.hanada@gmail.com>
1 sibling, 0 replies; 179+ messages in thread
From: KaiGai Kohei @ 2014-01-21 05:24 UTC (permalink / raw)
To: Shigeru Hanada <shigeru.hanada@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
(2014/01/21 11:44), Shigeru Hanada wrote:
> Thanks for the comments.
>
> 2014/1/21 KaiGai Kohei <kaigai@ak.jp.nec.com>:
>>> In addition, an idea which I can't throw away is to assume that all
>>> constraints defined on foreign tables as ASSERTIVE. Foreign tables
>>> potentially have dangers to have "wrong" data by updating source data
>>> not through foreign tables. This is not specific to an FDW, so IMO
>>> constraints defined on foreign tables are basically ASSERTIVE. Of
>>> course PG can try to maintain data correct, but always somebody might
>>> break it.
>>> qu
>>>
>> Does it make sense to apply "assertive" CHECK constraint on the qual
>> of ForeignScan to filter out tuples with violated values at the local
>> side, as if row-level security feature doing.
>> It enables to handle a situation that planner expects only "clean"
>> tuples are returned but FDW driver is unavailable to anomalies.
>>
>> Probably, this additional check can be turned on/off on the fly,
>> if FDW driver has a way to inform the core system its capability,
>> like FDW_CAN_ENFORCE_CHECK_CONSTRAINT that informs planner to skip
>> local checks.
>
> Hmm, IIUC you mean that local users can't (or don't need to) know that
> data which violates the local constraints exist on remote side.
> Applying constraints to the data which is modified through FDW would
> be necessary as well. In that design, FDW is a bidirectional filter
> which provides these features:
>
> 1) Don't push wrong data into remote data source, by applying local
> constraints to the result of the modifying query executed on local PG.
> This is not perfect filter, because remote constraints don't mapped
> automatically or perfectly (imagine constraints which is available on
> remote but is not supported in PG).
> 2) Don't retrieve wrong data from remote to local PG, by applying
> local constraints
>
Yes. (1) can be done with ExecConstraints prior to FDW callback on
UPDATE or INSERT, even not a perfect solution because of side-channel
on the remote data source. For (2), my proposition tries to drop
retrieved violated tuples, however, the result is same.
> I have a concern about consistency. It has not been supported, but
> let's think of Aggregate push-down invoked by a query below.
>
> SELECT count(*) FROM remote_table;
>
> If this query was fully pushed down, the result is the # of records
> exist on remote side, but the result would be # of valid records when
> we don't push down the aggregate. This would confuse users.
>
Hmm. In this case, FDW driver needs to be responsible to push-down
the additional check quals into remote side, so it does not work
transparently towards the ForeignScan.
It might be a little bit complicated suggestion for the beginning
of the efforts.
>>> Besides CHECK constraints, currently NOT NULL constraints are
>>> virtually ASSERTIVE (not enforcing). Should it also be noted
>>> explicitly?
>>>
>> Backward compatibility….
>
> Yep, backward compatibility (especially visible ones to users) should
> be minimal, ideally zero.
>
>> NOT NULL [ASSERTIVE] might be an option.
>
> Treating [ASSERTIVE | NOT ASSERTIVE] like DEFERRABLE, and allow
> ingASSERTIVE for only foreign tables? It makes sense, though we need
> consider exclusiveness . But It needs to default to ASSERTIVE on
> foreign tables, and NOT ASSERTIVE (means "forced") on others. Isn't
> is too complicated?
>
I think it is not easy to implement assertive checks, except for
foreign tables, because all the write stuff to regular tables are
managed by PostgreSQL itself.
So, it is a good first step to add support "ASSERTIVE" CHECK on
foreign table only, and to enforce FDW drivers nothing special
from my personal sense.
How about committer's opinion?
Thanks,
--
OSS Promotion Center / The PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-21 19:09 Robert Haas <robertmhaas@gmail.com>
parent: Shigeru Hanada <shigeru.hanada@gmail.com>
1 sibling, 2 replies; 179+ messages in thread
From: Robert Haas @ 2014-01-21 19:09 UTC (permalink / raw)
To: Shigeru Hanada <shigeru.hanada@gmail.com>; +Cc: KaiGai Kohei <kaigai@ak.jp.nec.com>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers
On Mon, Jan 20, 2014 at 9:44 PM, Shigeru Hanada
<shigeru.hanada@gmail.com> wrote:
> Thanks for the comments.
>
> 2014/1/21 KaiGai Kohei <kaigai@ak.jp.nec.com>:
>>> In addition, an idea which I can't throw away is to assume that all
>>> constraints defined on foreign tables as ASSERTIVE. Foreign tables
>>> potentially have dangers to have "wrong" data by updating source data
>>> not through foreign tables. This is not specific to an FDW, so IMO
>>> constraints defined on foreign tables are basically ASSERTIVE. Of
>>> course PG can try to maintain data correct, but always somebody might
>>> break it.
>>> qu
>>>
>> Does it make sense to apply "assertive" CHECK constraint on the qual
>> of ForeignScan to filter out tuples with violated values at the local
>> side, as if row-level security feature doing.
>> It enables to handle a situation that planner expects only "clean"
>> tuples are returned but FDW driver is unavailable to anomalies.
>>
>> Probably, this additional check can be turned on/off on the fly,
>> if FDW driver has a way to inform the core system its capability,
>> like FDW_CAN_ENFORCE_CHECK_CONSTRAINT that informs planner to skip
>> local checks.
>
> Hmm, IIUC you mean that local users can't (or don't need to) know that
> data which violates the local constraints exist on remote side.
> Applying constraints to the data which is modified through FDW would
> be necessary as well. In that design, FDW is a bidirectional filter
> which provides these features:
>
> 1) Don't push wrong data into remote data source, by applying local
> constraints to the result of the modifying query executed on local PG.
> This is not perfect filter, because remote constraints don't mapped
> automatically or perfectly (imagine constraints which is available on
> remote but is not supported in PG).
> 2) Don't retrieve wrong data from remote to local PG, by applying
> local constraints
>
> I have a concern about consistency. It has not been supported, but
> let's think of Aggregate push-down invoked by a query below.
>
> SELECT count(*) FROM remote_table;
>
> If this query was fully pushed down, the result is the # of records
> exist on remote side, but the result would be # of valid records when
> we don't push down the aggregate. This would confuse users.
>
>>> Besides CHECK constraints, currently NOT NULL constraints are
>>> virtually ASSERTIVE (not enforcing). Should it also be noted
>>> explicitly?
>>>
>> Backward compatibility….
>
> Yep, backward compatibility (especially visible ones to users) should
> be minimal, ideally zero.
>
>> NOT NULL [ASSERTIVE] might be an option.
>
> Treating [ASSERTIVE | NOT ASSERTIVE] like DEFERRABLE, and allow
> ingASSERTIVE for only foreign tables? It makes sense, though we need
> consider exclusiveness . But It needs to default to ASSERTIVE on
> foreign tables, and NOT ASSERTIVE (means "forced") on others. Isn't
> is too complicated?
>
> CREATE FOREIGN TABLE foo (
> id int NOT NULL ASSERTIVE CHECK (id > 1) ASSERTIVE,
> …
> CONSTRAINT chk_foo_name_upper CHECK (upper(name) = name) ASSERTIVE
> ) SERVER server;
>
> BTW, I noticed that this is like push-down-able expressions in
> JOIN/WHERE. We need to check a CHECK constraint defined on a foreign
> tables contains only expressions which have same semantics as remote
> side (in practice, built-in and immutable)?
I don't think that that ASSERTIVE is going to fly, because "assertive"
means (sayeth the Google) "having or showing a confident and forceful
personality", which is not what we mean here. It's tempting to do
something like try to replace the keyword "check" with "assume" or
"assert" or (stretching) "assertion", but that would require whichever
one we picked to be a fully-reserved keyword, which I can't think is
going to get much support here, for entirely understandable reasons.
So I think we should look for another option.
Currently, constraints can be marked NO INHERIT (though this seems to
have not been fully documented, as the ALTER TABLE page doesn't
mention it anywhere) or NOT VALID, so I'm thinking maybe we should go
with something along those lines. Some ideas:
- NO CHECK. The idea of writing CHECK (id > 1) NO CHECK is pretty
hilarious, though.
- NO VALIDATE. But then people need to understand that NOT VALID
means "we didn't validate it yet" while "no validate" means "we don't
ever intend to validate it", which could be confusing.
- NO ENFORCE. Requires a new (probably unreserved) keyword.
- NOT VALIDATED or NOT CHECKED. Same problems as NO CHECK and NO
VALIDATE, respectively, plus now we have to create a new keyword.
Another idea is to apply an extensible-options syntax to constraints,
like we do for EXPLAIN, VACUUM, etc. Like maybe:
CHECK (id > 1) OPTIONS (enforced false, valid true)
Yet another idea is to consider validity a three-state property:
either the constraint is valid (because we have checked it and are
enforcing it), or it is not valid (because we are enforcing it but
have not checked the pre-existing data), or it is assumed true
(because we are not checking or enforcing it but are believing it
anyway). So then we could have a syntax like this:
CHECK (id > 1) VALIDATE { ON | OFF | ASSERTION }
Other ideas?
One thing that's bugging me a bit about this whole line of attack is
that, in the first instance, the whole goal here is to support
inheritance hierarchies that mix ordinary tables with foreign tables.
If you have a table with children some of which are inherited and
others of which are not inherited, you're very likely going to want
your constraints enforced for real on the children that are tables and
assumed true on the children that are foreign tables, and none of what
we're talking about here gets us to that, because we normally want the
constraints to be identical throughout the inheritance hierarchy.
Maybe there's some way around that, but I'm back to wondering if it
wouldn't be better to simply silently force any constraints on a
foreign-table into assertion mode. That could be done without any new
syntax at all, and frankly I think it's what people are going to want
more often than not.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-21 20:00 Tom Lane <tgl@sss.pgh.pa.us>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 1 reply; 179+ messages in thread
From: Tom Lane @ 2014-01-21 20:00 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Shigeru Hanada <shigeru.hanada@gmail.com>; KaiGai Kohei <kaigai@ak.jp.nec.com>; pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
> One thing that's bugging me a bit about this whole line of attack is
> that, in the first instance, the whole goal here is to support
> inheritance hierarchies that mix ordinary tables with foreign tables.
> If you have a table with children some of which are inherited and
> others of which are not inherited, you're very likely going to want
> your constraints enforced for real on the children that are tables and
> assumed true on the children that are foreign tables, and none of what
> we're talking about here gets us to that, because we normally want the
> constraints to be identical throughout the inheritance hierarchy.
There's a nearby thread that's addressing this same question, in which
I make the case (again) that the right thing for postgres_fdw constraints
is that they're just assumed true. So I'm not sure why this conversation
is proposing to implement a lot of mechanism to do something different
from that.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-21 20:13 Robert Haas <robertmhaas@gmail.com>
parent: Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 0 replies; 179+ messages in thread
From: Robert Haas @ 2014-01-21 20:13 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Shigeru Hanada <shigeru.hanada@gmail.com>; KaiGai Kohei <kaigai@ak.jp.nec.com>; pgsql-hackers
On Tue, Jan 21, 2014 at 3:00 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
>> One thing that's bugging me a bit about this whole line of attack is
>> that, in the first instance, the whole goal here is to support
>> inheritance hierarchies that mix ordinary tables with foreign tables.
>> If you have a table with children some of which are inherited and
>> others of which are not inherited, you're very likely going to want
>> your constraints enforced for real on the children that are tables and
>> assumed true on the children that are foreign tables, and none of what
>> we're talking about here gets us to that, because we normally want the
>> constraints to be identical throughout the inheritance hierarchy.
>
> There's a nearby thread that's addressing this same question, in which
> I make the case (again) that the right thing for postgres_fdw constraints
> is that they're just assumed true. So I'm not sure why this conversation
> is proposing to implement a lot of mechanism to do something different
> from that.
/me scratches head.
Because the other guy named Tom Lane took the opposite position on the
second message on this thread, dated 11/14/13?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* [Review] inherit support for foreign tables
@ 2014-01-23 12:59 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-01-23 12:59 UTC (permalink / raw)
To: shigeru.hanada@gmail.com; +Cc: pgsql-hackers
Shigeru Hanada wrote:
> Attached patch allows a foreign table to be a child of a table. It
> also allows foreign tables to have CHECK constraints.
Sorry for the delay. I started to look at this patch.
> Though this would be debatable, in current implementation, constraints
> defined on a foreign table (now only NOT NULL and CHECK are supported)
> are not enforced during INSERT or UPDATE executed against foreign
> tables. This means that retrieved data might violates the constraints
> defined on local side. This is debatable issue because integrity of
> data is important for DBMS, but in the first cut, it is just
> documented as a note.
I agree with you, but we should introduce a new keyword such as
ASSERTIVE or something in 9.4, in preparation for the enforcement of
constraints on the local side in a future release? What I'm concerned
about is, otherwise, users will have to rewrite those DDL queries at
that point. No?
> Because I don't see practical case to have a foreign table as a
> parent, and it avoid a problem about recursive ALTER TABLE operation,
> foreign tables can't be a parent.
I agree with you on that point.
> For other commands recursively processed such as ANALYZE, foreign
> tables in the leaf of inheritance tree are ignored.
I'm not sure that in the processing of the ANALYZE command, we should
ignore child foreign tables. It seems to me that the recursive
processing is not that hard. Rather what I'm concerned about is that if
the recursive processing is allowed, then autovacuum will probably have
to access to forign tables on the far side in order to acquire those
sample rows. It might be undesirable from the viewpoint of security or
from the viewpoint of efficiency.
--- 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 ] <replaceable
class="PARAMETER">table_name
<replaceable class="PARAMETER">column_name</replaceable> <replaceable
class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable
class="PA\
RAMETER">option</replaceable> '<replaceable
class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE
<replaceable>collation</replaceable> ] [ <rep\
laceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
+[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable>
'<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
@@ -159,6 +160,17 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable
class="PARAMETER">table_name
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ The name of an existing table or foreign table from which the new foreign
+ table automatically inherits all columns, see
+ <xref linkend="ddl-inherit"> for details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
Correct? I think we should not allow a foreign table to be a parent.
I'll look at this patch more closely.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: [Review] inherit support for foreign tables
@ 2014-01-25 02:27 Shigeru Hanada <shigeru.hanada@gmail.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Shigeru Hanada @ 2014-01-25 02:27 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: pgsql-hackers
Hi Fujita-san,
Thanks for the review.
2014/1/23 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>:
> Shigeru Hanada wrote:
>> Though this would be debatable, in current implementation, constraints
>> defined on a foreign table (now only NOT NULL and CHECK are supported)
>> are not enforced during INSERT or UPDATE executed against foreign
>> tables. This means that retrieved data might violates the constraints
>> defined on local side. This is debatable issue because integrity of
>> data is important for DBMS, but in the first cut, it is just
>> documented as a note.
>
> I agree with you, but we should introduce a new keyword such as
> ASSERTIVE or something in 9.4, in preparation for the enforcement of
> constraints on the local side in a future release? What I'm concerned
> about is, otherwise, users will have to rewrite those DDL queries at
> that point. No?
In the original thread, whether the new syntax is necessary (maybe
ASSERTIVE will not be used though) is under discussion. Anyway, your
point, decrease user's DDL rewrite less as possible is important.
Could you post review result and/or your opinion as the reply to the
original thread, then we include other people interested in this
feature can share discussion.
>> Because I don't see practical case to have a foreign table as a
>> parent, and it avoid a problem about recursive ALTER TABLE operation,
>> foreign tables can't be a parent.
>
> I agree with you on that point.
>
>> For other commands recursively processed such as ANALYZE, foreign
>> tables in the leaf of inheritance tree are ignored.
>
> I'm not sure that in the processing of the ANALYZE command, we should
> ignore child foreign tables. It seems to me that the recursive
> processing is not that hard. Rather what I'm concerned about is that if
> the recursive processing is allowed, then autovacuum will probably have
> to access to forign tables on the far side in order to acquire those
> sample rows. It might be undesirable from the viewpoint of security or
> from the viewpoint of efficiency.
As you say, it's not difficult to do recursive ANALYZE including
foreign tables. The reason why ANALYZE ignores descendant foreign
tables is consistent behavior. At the moment, ANALYZE ignores foreign
tables in top-level (non-child) when no table name was given, and if
we want to ANALYZE foreign tables we need to specify explicitly.
I think it's not so bad to show WARNING or INFO message about foreign
table ignorance, including which is not a child.
> --- 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 ] <replaceable
> class="PARAMETER">table_name
> <replaceable class="PARAMETER">column_name</replaceable> <replaceable
> class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable
> class="PA\
> RAMETER">option</replaceable> '<replaceable
> class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE
> <replaceable>collation</replaceable> ] [ <rep\
> laceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
> [, ... ]
> ] )
> +[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
> SERVER <replaceable class="parameter">server_name</replaceable>
> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable>
> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
>
> @@ -159,6 +160,17 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable
> class="PARAMETER">table_name
> </varlistentry>
>
> <varlistentry>
> + <term><replaceable class="PARAMETER">parent_table</replaceable></term>
> + <listitem>
> + <para>
> + The name of an existing table or foreign table from which the new foreign
> + table automatically inherits all columns, see
> + <xref linkend="ddl-inherit"> for details of table inheritance.
> + </para>
> + </listitem>
> + </varlistentry>
>
> Correct? I think we should not allow a foreign table to be a parent.
Oops, good catch.
--
Shigeru HANADA
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: [Review] inherit support for foreign tables
@ 2014-01-27 03:59 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Shigeru Hanada <shigeru.hanada@gmail.com>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-01-27 03:59 UTC (permalink / raw)
To: Shigeru Hanada <shigeru.hanada@gmail.com>; +Cc: pgsql-hackers
(2014/01/25 11:27), Shigeru Hanada wrote:
> 2014/1/23 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>:
>> Shigeru Hanada wrote:
>>> Though this would be debatable, in current implementation, constraints
>>> defined on a foreign table (now only NOT NULL and CHECK are supported)
>>> are not enforced during INSERT or UPDATE executed against foreign
>>> tables. This means that retrieved data might violates the constraints
>>> defined on local side. This is debatable issue because integrity of
>>> data is important for DBMS, but in the first cut, it is just
>>> documented as a note.
>>
>> I agree with you, but we should introduce a new keyword such as
>> ASSERTIVE or something in 9.4, in preparation for the enforcement of
>> constraints on the local side in a future release? What I'm concerned
>> about is, otherwise, users will have to rewrite those DDL queries at
>> that point. No?
>
> In the original thread, whether the new syntax is necessary (maybe
> ASSERTIVE will not be used though) is under discussion. Anyway, your
> point, decrease user's DDL rewrite less as possible is important.
> Could you post review result and/or your opinion as the reply to the
> original thread, then we include other people interested in this
> feature can share discussion.
OK I'll give my opinion in that thread.
>>> For other commands recursively processed such as ANALYZE, foreign
>>> tables in the leaf of inheritance tree are ignored.
>>
>> I'm not sure that in the processing of the ANALYZE command, we should
>> ignore child foreign tables. It seems to me that the recursive
>> processing is not that hard. Rather what I'm concerned about is that if
>> the recursive processing is allowed, then autovacuum will probably have
>> to access to forign tables on the far side in order to acquire those
>> sample rows. It might be undesirable from the viewpoint of security or
>> from the viewpoint of efficiency.
>
> As you say, it's not difficult to do recursive ANALYZE including
> foreign tables. The reason why ANALYZE ignores descendant foreign
> tables is consistent behavior. At the moment, ANALYZE ignores foreign
> tables in top-level (non-child) when no table name was given, and if
> we want to ANALYZE foreign tables we need to specify explicitly.
>
> I think it's not so bad to show WARNING or INFO message about foreign
> table ignorance, including which is not a child.
Yeah, the consistency is essential for its ease of use. But I'm not
sure that inherited stats ignoring foreign tables is actually useful for
query optimization. What I think about the consistency is a) the
ANALYZE command with no table names skips ANALYZEing inheritance trees
that include at least one foreign table as a child, but b) the ANALYZE
command with a table name indicating an inheritance tree that includes
any foreign tables does compute the inherited stats in the same way as
an inheritance tree consiting of only ordinary tables by acquiring the
sample rows from each foreign table on the far side.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-27 05:51 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-01-27 05:51 UTC (permalink / raw)
To: pgsql-hackers
(2014/01/22 4:09), Robert Haas wrote:
> On Mon, Jan 20, 2014 at 9:44 PM, Shigeru Hanada
> <shigeru.hanada@gmail.com> wrote:
>> Thanks for the comments.
>>
>> 2014/1/21 KaiGai Kohei <kaigai@ak.jp.nec.com>:
>>>> In addition, an idea which I can't throw away is to assume that all
>>>> constraints defined on foreign tables as ASSERTIVE. Foreign tables
>>>> potentially have dangers to have "wrong" data by updating source data
>>>> not through foreign tables. This is not specific to an FDW, so IMO
>>>> constraints defined on foreign tables are basically ASSERTIVE. Of
>>>> course PG can try to maintain data correct, but always somebody might
>>>> break it.
>>>> qu
>>>>
>>> Does it make sense to apply "assertive" CHECK constraint on the qual
>>> of ForeignScan to filter out tuples with violated values at the local
>>> side, as if row-level security feature doing.
>>> It enables to handle a situation that planner expects only "clean"
>>> tuples are returned but FDW driver is unavailable to anomalies.
>>>
>>> Probably, this additional check can be turned on/off on the fly,
>>> if FDW driver has a way to inform the core system its capability,
>>> like FDW_CAN_ENFORCE_CHECK_CONSTRAINT that informs planner to skip
>>> local checks.
>>
>> Hmm, IIUC you mean that local users can't (or don't need to) know that
>> data which violates the local constraints exist on remote side.
>> Applying constraints to the data which is modified through FDW would
>> be necessary as well. In that design, FDW is a bidirectional filter
>> which provides these features:
>>
>> 1) Don't push wrong data into remote data source, by applying local
>> constraints to the result of the modifying query executed on local PG.
>> This is not perfect filter, because remote constraints don't mapped
>> automatically or perfectly (imagine constraints which is available on
>> remote but is not supported in PG).
>> 2) Don't retrieve wrong data from remote to local PG, by applying
>> local constraints
>>
>> I have a concern about consistency. It has not been supported, but
>> let's think of Aggregate push-down invoked by a query below.
>>
>> SELECT count(*) FROM remote_table;
>>
>> If this query was fully pushed down, the result is the # of records
>> exist on remote side, but the result would be # of valid records when
>> we don't push down the aggregate. This would confuse users.
>>
>>>> Besides CHECK constraints, currently NOT NULL constraints are
>>>> virtually ASSERTIVE (not enforcing). Should it also be noted
>>>> explicitly?
>>>>
>>> Backward compatibility….
>>
>> Yep, backward compatibility (especially visible ones to users) should
>> be minimal, ideally zero.
>>
>>> NOT NULL [ASSERTIVE] might be an option.
>>
>> Treating [ASSERTIVE | NOT ASSERTIVE] like DEFERRABLE, and allow
>> ingASSERTIVE for only foreign tables? It makes sense, though we need
>> consider exclusiveness . But It needs to default to ASSERTIVE on
>> foreign tables, and NOT ASSERTIVE (means "forced") on others. Isn't
>> is too complicated?
>>
>> CREATE FOREIGN TABLE foo (
>> id int NOT NULL ASSERTIVE CHECK (id > 1) ASSERTIVE,
>> …
>> CONSTRAINT chk_foo_name_upper CHECK (upper(name) = name) ASSERTIVE
>> ) SERVER server;
>>
>> BTW, I noticed that this is like push-down-able expressions in
>> JOIN/WHERE. We need to check a CHECK constraint defined on a foreign
>> tables contains only expressions which have same semantics as remote
>> side (in practice, built-in and immutable)?
>
> I don't think that that ASSERTIVE is going to fly, because "assertive"
> means (sayeth the Google) "having or showing a confident and forceful
> personality", which is not what we mean here. It's tempting to do
> something like try to replace the keyword "check" with "assume" or
> "assert" or (stretching) "assertion", but that would require whichever
> one we picked to be a fully-reserved keyword, which I can't think is
> going to get much support here, for entirely understandable reasons.
> So I think we should look for another option.
>
> Currently, constraints can be marked NO INHERIT (though this seems to
> have not been fully documented, as the ALTER TABLE page doesn't
> mention it anywhere) or NOT VALID, so I'm thinking maybe we should go
> with something along those lines. Some ideas:
>
> - NO CHECK. The idea of writing CHECK (id > 1) NO CHECK is pretty
> hilarious, though.
> - NO VALIDATE. But then people need to understand that NOT VALID
> means "we didn't validate it yet" while "no validate" means "we don't
> ever intend to validate it", which could be confusing.
> - NO ENFORCE. Requires a new (probably unreserved) keyword.
> - NOT VALIDATED or NOT CHECKED. Same problems as NO CHECK and NO
> VALIDATE, respectively, plus now we have to create a new keyword.
>
> Another idea is to apply an extensible-options syntax to constraints,
> like we do for EXPLAIN, VACUUM, etc. Like maybe:
>
> CHECK (id > 1) OPTIONS (enforced false, valid true)
>
> Yet another idea is to consider validity a three-state property:
> either the constraint is valid (because we have checked it and are
> enforcing it), or it is not valid (because we are enforcing it but
> have not checked the pre-existing data), or it is assumed true
> (because we are not checking or enforcing it but are believing it
> anyway). So then we could have a syntax like this:
>
> CHECK (id > 1) VALIDATE { ON | OFF | ASSERTION }
>
> Other ideas?
>
> One thing that's bugging me a bit about this whole line of attack is
> that, in the first instance, the whole goal here is to support
> inheritance hierarchies that mix ordinary tables with foreign tables.
> If you have a table with children some of which are inherited and
> others of which are not inherited, you're very likely going to want
> your constraints enforced for real on the children that are tables and
> assumed true on the children that are foreign tables, and none of what
> we're talking about here gets us to that, because we normally want the
> constraints to be identical throughout the inheritance hierarchy.
> Maybe there's some way around that, but I'm back to wondering if it
> wouldn't be better to simply silently force any constraints on a
> foreign-table into assertion mode. That could be done without any new
> syntax at all, and frankly I think it's what people are going to want
> more often than not.
I'd like to vote for the idea of silently forcing any constraints on a
foreign-table into assertion mode. No new syntax and better documentation.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-27 08:06 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 2 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-01-27 08:06 UTC (permalink / raw)
To: pgsql-hackers
Hi Hanada-san,
While still reviwing this patch, I feel this patch has given enough
consideration to interactions with other commands, but found the
following incorrect? behabior:
postgres=# CREATE TABLE product (id INTEGER, description TEXT);
CREATE TABLE
postgres=# CREATE FOREIGN TABLE product1 () INHERITS (product) SERVER fs
OPTIONS (filename '/home/foo/product1.csv', format 'csv');
CREATE FOREIGN TABLE
postgres=# ALTER TABLE product ALTER COLUMN description SET STORAGE
EXTERNAL;
ERROR: "product1" is not a table or materialized view
ISTN the ALTER TABLE simple recursion mechanism (ie ATSimpleRecursion())
should be modified for the ALTER COLUMN SET STORAGE case.
I just wanted to quickly tell you this for you to take time to consider.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: [Review] inherit support for foreign tables
@ 2014-01-27 12:49 Shigeru Hanada <shigeru.hanada@gmail.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Shigeru Hanada @ 2014-01-27 12:49 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: pgsql-hackers
2014-01-27 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>:
> (2014/01/25 11:27), Shigeru Hanada wrote:
> Yeah, the consistency is essential for its ease of use. But I'm not sure
> that inherited stats ignoring foreign tables is actually useful for query
> optimization. What I think about the consistency is a) the ANALYZE command
> with no table names skips ANALYZEing inheritance trees that include at least
> one foreign table as a child, but b) the ANALYZE command with a table name
> indicating an inheritance tree that includes any foreign tables does compute
> the inherited stats in the same way as an inheritance tree consiting of only
> ordinary tables by acquiring the sample rows from each foreign table on the
> far side.
b) sounds little complex to understand or explain.
Is it too big change that making ANALYZE command to handle foreign
tables too even if no table name was specified? IIRC, performance
issue was the reason to exclude foreign tables from auto-analyze
processing. ANALYZEing large database contains local huge data also
takes long time. One idea to avoid unexpected long processing is to
add option to foreign tables to mark it as "not-auto-analyzable".
Thoughts?
--
Shigeru HANADA
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-27 12:52 Shigeru Hanada <shigeru.hanada@gmail.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: Shigeru Hanada @ 2014-01-27 12:52 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: pgsql-hackers
2014-01-27 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>:
> While still reviwing this patch, I feel this patch has given enough
> consideration to interactions with other commands, but found the following
> incorrect? behabior:
>
> postgres=# CREATE TABLE product (id INTEGER, description TEXT);
> CREATE TABLE
> postgres=# CREATE FOREIGN TABLE product1 () INHERITS (product) SERVER fs
> OPTIONS (filename '/home/foo/product1.csv', format 'csv');
> CREATE FOREIGN TABLE
> postgres=# ALTER TABLE product ALTER COLUMN description SET STORAGE
> EXTERNAL;
> ERROR: "product1" is not a table or materialized view
>
> ISTN the ALTER TABLE simple recursion mechanism (ie ATSimpleRecursion())
> should be modified for the ALTER COLUMN SET STORAGE case.
>
> I just wanted to quickly tell you this for you to take time to consider.
Thanks for the review. It must be an oversight, so I'll fix it up soon.
--
Shigeru HANADA
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-27 15:33 David Fetter <david@fetter.org>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: David Fetter @ 2014-01-27 15:33 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: pgsql-hackers
On Mon, Jan 27, 2014 at 05:06:19PM +0900, Etsuro Fujita wrote:
> Hi Hanada-san,
>
> While still reviwing this patch, I feel this patch has given enough
> consideration to interactions with other commands, but found the
> following incorrect? behabior:
>
> postgres=# CREATE TABLE product (id INTEGER, description TEXT);
> CREATE TABLE
> postgres=# CREATE FOREIGN TABLE product1 () INHERITS (product)
> SERVER fs OPTIONS (filename '/home/foo/product1.csv', format 'csv');
> CREATE FOREIGN TABLE
> postgres=# ALTER TABLE product ALTER COLUMN description SET STORAGE
> EXTERNAL;
> ERROR: "product1" is not a table or materialized view
>
> ISTN the ALTER TABLE simple recursion mechanism (ie
> ATSimpleRecursion()) should be modified for the ALTER COLUMN SET
> STORAGE case.
This points to a larger discussion about what precisely foreign tables
can and cannot inherit from local ones. I don't think that a generic
solution will be satisfactory, as the PostgreSQL FDW could, at least
in principle, support many more than the CSV FDW, as shown above.
In my estimation, the outcome of discussion above is not a blocker for
this patch.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-27 15:55 Atri Sharma <atri.jiit@gmail.com>
parent: David Fetter <david@fetter.org>
0 siblings, 1 reply; 179+ messages in thread
From: Atri Sharma @ 2014-01-27 15:55 UTC (permalink / raw)
To: David Fetter <david@fetter.org>; +Cc: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; pgsql-hackers
Sent from my iPad
> On 27-Jan-2014, at 21:03, David Fetter <david@fetter.org> wrote:
>
>> On Mon, Jan 27, 2014 at 05:06:19PM +0900, Etsuro Fujita wrote:
>> Hi Hanada-san,
>>
>> While still reviwing this patch, I feel this patch has given enough
>> consideration to interactions with other commands, but found the
>> following incorrect? behabior:
>>
>> postgres=# CREATE TABLE product (id INTEGER, description TEXT);
>> CREATE TABLE
>> postgres=# CREATE FOREIGN TABLE product1 () INHERITS (product)
>> SERVER fs OPTIONS (filename '/home/foo/product1.csv', format 'csv');
>> CREATE FOREIGN TABLE
>> postgres=# ALTER TABLE product ALTER COLUMN description SET STORAGE
>> EXTERNAL;
>> ERROR: "product1" is not a table or materialized view
>>
>> ISTN the ALTER TABLE simple recursion mechanism (ie
>> ATSimpleRecursion()) should be modified for the ALTER COLUMN SET
>> STORAGE case.
>
> This points to a larger discussion about what precisely foreign tables
> can and cannot inherit from local ones. I don't think that a generic
> solution will be satisfactory, as the PostgreSQL FDW could, at least
> in principle, support many more than the CSV FDW, as shown above.
>
> In my estimation, the outcome of discussion above is not a blocker for
> this
I wonder what shall be the cases when foreign table is on a server which does not support *all* SQL features.
Does a FDW need to have the possible inherit options mentioned in its documentation for this patch?
Regards,
Atri
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-28 04:07 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Atri Sharma <atri.jiit@gmail.com>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-01-28 04:07 UTC (permalink / raw)
To: Atri Sharma <atri.jiit@gmail.com>; David Fetter <david@fetter.org>; +Cc: pgsql-hackers
(2014/01/28 0:55), Atri Sharma wrote:
>> On 27-Jan-2014, at 21:03, David Fetter <david@fetter.org> wrote:
>>> On Mon, Jan 27, 2014 at 05:06:19PM +0900, Etsuro Fujita wrote:
>>> Hi Hanada-san,
>>>
>>> While still reviwing this patch, I feel this patch has given enough
>>> consideration to interactions with other commands, but found the
>>> following incorrect? behabior:
>>>
>>> postgres=# CREATE TABLE product (id INTEGER, description TEXT);
>>> CREATE TABLE
>>> postgres=# CREATE FOREIGN TABLE product1 () INHERITS (product)
>>> SERVER fs OPTIONS (filename '/home/foo/product1.csv', format 'csv');
>>> CREATE FOREIGN TABLE
>>> postgres=# ALTER TABLE product ALTER COLUMN description SET STORAGE
>>> EXTERNAL;
>>> ERROR: "product1" is not a table or materialized view
>>>
>>> ISTN the ALTER TABLE simple recursion mechanism (ie
>>> ATSimpleRecursion()) should be modified for the ALTER COLUMN SET
>>> STORAGE case.
>>
>> This points to a larger discussion about what precisely foreign tables
>> can and cannot inherit from local ones. I don't think that a generic
>> solution will be satisfactory, as the PostgreSQL FDW could, at least
>> in principle, support many more than the CSV FDW, as shown above.
>>
>> In my estimation, the outcome of discussion above is not a blocker for
>> this
I just thought that among the structures that local tables can alter,
the ones that foreign tables also can by ALTER FOREIGN TABLE are
inherited, and the others are not inherited. So for the case as shown
above, I thought that we silently ignore executing the ALTER COLUMN SET
STORAGE command for the foreign table. I wonder that would be the first
step.
> I wonder what shall be the cases when foreign table is on a server which does not support *all* SQL features.
>
> Does a FDW need to have the possible inherit options mentioned in its documentation for this patch?
The answer is no, in my understanding. The altering operation simply
declares some chages for foreign tables in the inheritance tree and does
nothing to the underlying storages.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-28 04:34 Kouhei Kaigai <kaigai@ak.jp.nec.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Kouhei Kaigai @ 2014-01-28 04:34 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; Atri Sharma <atri.jiit@gmail.com>; David Fetter <david@fetter.org>; +Cc: pgsql-hackers
> > I wonder what shall be the cases when foreign table is on a server which
> does not support *all* SQL features.
> >
> > Does a FDW need to have the possible inherit options mentioned in its
> documentation for this patch?
>
> The answer is no, in my understanding. The altering operation simply
> declares some chages for foreign tables in the inheritance tree and does
> nothing to the underlying storages.
>
It seems to me Atri mention about the idea to enforce constraint when
partitioned foreign table was referenced...
BTW, isn't it feasible to consign FDW drivers its decision?
If a foreign table has a check constraint (X BETWEEN 101 AND 200),
postgres_fdw will be capable to run this check on the remote server,
however, file_fdw will not be capable. It is usual job of them when
qualifiers are attached on Path node.
Probably, things to do is simple. If the backend appends the configured
check constraint as if a user-given qualifier, FDW driver will handle it
appropriately.
Thanks,
--
NEC OSS Promotion Center / PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>
> -----Original Message-----
> From: pgsql-hackers-owner@postgresql.org
> [mailto:pgsql-hackers-owner@postgresql.org] On Behalf Of Etsuro Fujita
> Sent: Tuesday, January 28, 2014 1:08 PM
> To: Atri Sharma; David Fetter
> Cc: pgsql-hackers@postgresql.org
> Subject: Re: [HACKERS] inherit support for foreign tables
>
> (2014/01/28 0:55), Atri Sharma wrote:
> >> On 27-Jan-2014, at 21:03, David Fetter <david@fetter.org> wrote:
> >>> On Mon, Jan 27, 2014 at 05:06:19PM +0900, Etsuro Fujita wrote:
> >>> Hi Hanada-san,
> >>>
> >>> While still reviwing this patch, I feel this patch has given enough
> >>> consideration to interactions with other commands, but found the
> >>> following incorrect? behabior:
> >>>
> >>> postgres=# CREATE TABLE product (id INTEGER, description TEXT);
> >>> CREATE TABLE postgres=# CREATE FOREIGN TABLE product1 () INHERITS
> >>> (product) SERVER fs OPTIONS (filename '/home/foo/product1.csv',
> >>> format 'csv'); CREATE FOREIGN TABLE postgres=# ALTER TABLE product
> >>> ALTER COLUMN description SET STORAGE EXTERNAL;
> >>> ERROR: "product1" is not a table or materialized view
> >>>
> >>> ISTN the ALTER TABLE simple recursion mechanism (ie
> >>> ATSimpleRecursion()) should be modified for the ALTER COLUMN SET
> >>> STORAGE case.
> >>
> >> This points to a larger discussion about what precisely foreign
> >> tables can and cannot inherit from local ones. I don't think that a
> >> generic solution will be satisfactory, as the PostgreSQL FDW could,
> >> at least in principle, support many more than the CSV FDW, as shown above.
> >>
> >> In my estimation, the outcome of discussion above is not a blocker
> >> for this
>
> I just thought that among the structures that local tables can alter, the
> ones that foreign tables also can by ALTER FOREIGN TABLE are inherited,
> and the others are not inherited. So for the case as shown above, I thought
> that we silently ignore executing the ALTER COLUMN SET STORAGE command for
> the foreign table. I wonder that would be the first step.
>
> > I wonder what shall be the cases when foreign table is on a server which
> does not support *all* SQL features.
> >
> > Does a FDW need to have the possible inherit options mentioned in its
> documentation for this patch?
>
> The answer is no, in my understanding. The altering operation simply
> declares some chages for foreign tables in the inheritance tree and does
> nothing to the underlying storages.
>
> Thanks,
>
> Best regards,
> Etsuro Fujita
>
>
> --
> Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make
> changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-hackers
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: [Review] inherit support for foreign tables
@ 2014-01-28 13:01 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Shigeru Hanada <shigeru.hanada@gmail.com>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-01-28 13:01 UTC (permalink / raw)
To: Shigeru Hanada <shigeru.hanada@gmail.com>; +Cc: pgsql-hackers
(2014/01/27 21:49), Shigeru Hanada wrote:
> 2014-01-27 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>:
>> (2014/01/25 11:27), Shigeru Hanada wrote:
>> Yeah, the consistency is essential for its ease of use. But I'm not sure
>> that inherited stats ignoring foreign tables is actually useful for query
>> optimization. What I think about the consistency is a) the ANALYZE command
>> with no table names skips ANALYZEing inheritance trees that include at least
>> one foreign table as a child, but b) the ANALYZE command with a table name
>> indicating an inheritance tree that includes any foreign tables does compute
>> the inherited stats in the same way as an inheritance tree consiting of only
>> ordinary tables by acquiring the sample rows from each foreign table on the
>> far side.
>
> b) sounds little complex to understand or explain.
>
> Is it too big change that making ANALYZE command to handle foreign
> tables too even if no table name was specified? IIRC, performance
> issue was the reason to exclude foreign tables from auto-analyze
> processing. ANALYZEing large database contains local huge data also
> takes long time. One idea to avoid unexpected long processing is to
> add option to foreign tables to mark it as "not-auto-analyzable".
Maybe I didn't express my idea clearly. Sorry for that.
I don't think that we now allow the ANALYZE command to handle foreign
tables when no table name is specified with the command. I think that
we allow the ANALYZE command to handle an inheritance tree that includes
foreign tables when the name of the parent table is specified, without
ignoring such foreign tables in the caluculation. ISTM it would be
possible to do so if we introduce a new parameter, say, vac_mode, which
indicates wether vacuum() is called with a specific table or not.
I'll try to modify the ANALYZE command to do so on top of you patch.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-30 05:01 Shigeru Hanada <shigeru.hanada@gmail.com>
parent: Shigeru Hanada <shigeru.hanada@gmail.com>
0 siblings, 2 replies; 179+ messages in thread
From: Shigeru Hanada @ 2014-01-30 05:01 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: pgsql-hackers
(2014/01/27 21:52), Shigeru Hanada wrote:
> 2014-01-27 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>:
>> While still reviwing this patch, I feel this patch has given enough
>> consideration to interactions with other commands, but found the following
>> incorrect? behabior:
>>
>> postgres=# CREATE TABLE product (id INTEGER, description TEXT);
>> CREATE TABLE
>> postgres=# CREATE FOREIGN TABLE product1 () INHERITS (product) SERVER fs
>> OPTIONS (filename '/home/foo/product1.csv', format 'csv');
>> CREATE FOREIGN TABLE
>> postgres=# ALTER TABLE product ALTER COLUMN description SET STORAGE
>> EXTERNAL;
>> ERROR: "product1" is not a table or materialized view
>>
>> ISTN the ALTER TABLE simple recursion mechanism (ie ATSimpleRecursion())
>> should be modified for the ALTER COLUMN SET STORAGE case.
>>
>> I just wanted to quickly tell you this for you to take time to consider.
>
> Thanks for the review. It must be an oversight, so I'll fix it up soon.
>
It seems little bit complex than I expected. Currently foreign tables
deny ALTER TABLE SET STORAGE with message like below, because foreign
tables don't have storage in the meaning of PG heap tables.
ERROR: "pgbench1_accounts_c1" is not a table or materialized view
At the moment we don't use attstorage for foreign tables, so allowing
SET STORAGE against foreign tables never introduce visible change
except \d+ output of foreign tables. But IMO such operation should
not allowed because users would be confused. So I changed
ATExecSetStorage() to skip on foreign tables. This allows us to emit
ALTER TABLE SET STORAGE against ordinary tables in upper level of
inheritance tree, but it have effect on only ordinary tables in the
tree.
This also allows direct ALTER FOREIGN TABLE SET STORAGE against
foreign table but the command is silently ignored. SET STORAGE
support for foreign tables is not documented because it may confuse
users.
With attached patch, SET STORAGE against wrong relations produces
message like this. Is it confusing to mention foreign table here?
ERROR: "pgbench1_accounts_pkey" is not a table, materialized view, or
foreign table
One other idea is to support SET STORAGE against foreign tables though
it has no effect. This makes all tables and foreign tables to have
same storage option in same column. It might be more understandable
behavior for users.
Thoughts?
FYI, here are lists of ALTER TABLE features which is allowed/denied
for foreign tables.
Allowed
- ADD|DROP COLUMN
- SET DATA TYPE
- SET|DROP NOT NULL
- SET STATISTICS
- SET (attribute_option = value)
- RESET (atrribute_option)
- SET|DROP DEFAULT
- ADD table_constraint
- ALTER CONSTRAINT
- DROP CONSTRAINT
- [NO] INHERIT parent_table
- OWNER
- RENAME
- SET SCHEMA
- SET STORAGE
- moved to here by attached patch
Denied
- ADD table_constraint_using_index
- VALIDATE CONSTRAINT
- DISABLE|ENABLE TRIGGER
- DISABLE|ENABLE RULE
- CLUSTER ON
- SET WITHOUT CLUSTER
- SET WITH|WITHOUT OIDS
- SET (storage_parameter = value)
- RESET (storage_parameter)
- OF type
- NOT OF
- SET TABLESPACE
- REPLICA IDENTITY
--
Shigeru HANADA
--
Shigeru HANADA
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[application/octet-stream] foreign_inherit-v2.patch (18.1K, ../../CAEZqfEfYauyxZQvsKODM5BQKt+KsnNakFQxrHHdnivq6mO83rg@mail.gmail.com/2-foreign_inherit-v2.patch)
download | inline diff:
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index bae2e97..b9edeae 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.
</para>
+ <note>
+ <para>
+ 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
+ <xref linkend="ddl-inherit">) to off-load to multiple servers.
+ </para>
+ </note>
+
<sect2 id="ddl-constraints-check-constraints">
<title>Check Constraints</title>
@@ -2021,8 +2032,8 @@ CREATE TABLE capitals (
</para>
<para>
- In <productname>PostgreSQL</productname>, a table can inherit from
- zero or more other tables, and a query can reference either all
+ In <productname>PostgreSQL</productname>, 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 723aa07..0ec16aa 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 ] <replaceable class="PARAMETER">name</replaceab
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
@@ -178,6 +180,26 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
</varlistentry>
<varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
<listitem>
<para>
@@ -306,6 +328,15 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">parent_name</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml
index 1ef4b5e..97e6b97 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 ] <replaceable class="PARAMETER">table_name
<replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
+[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
@@ -159,6 +160,17 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ The name of an existing table from which the new foreign table
+ automatically inherits all columns, see <xref linkend="ddl-inherit"> for
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index e7fcb55..8814387 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1496,6 +1496,13 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
/* We already got the needed lock */
childrel = heap_open(childOID, NoLock);
+ /* Ignore foreignt tables */
+ if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ heap_close(childrel, AccessShareLock);
+ continue;
+ }
+
/* Ignore if temp table of another backend */
if (RELATION_IS_OTHER_TEMP(childrel))
{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 08b037e..caf2a64 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -465,10 +465,25 @@ 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")));
+/*
+ * Shouldn't this have been checked in parser?
+ */
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ ListCell *lc;
+ foreach(lc, stmt->constraints)
+ {
+ NewConstraint *nc = lfirst(lc);
+
+ if (nc->contype != CONSTR_CHECK &&
+ nc->contype != CONSTR_DEFAULT &&
+ nc->contype != CONSTR_NULL &&
+ nc->contype != CONSTR_NOTNULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("only check constraints are supported on foreign tables")));
+ }
+ }
/*
* Look up the namespace in which we are supposed to create the relation,
@@ -3043,7 +3058,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
pass = AT_PASS_MISC;
break;
case AT_SetStorage: /* ALTER COLUMN SET STORAGE */
- ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
+ ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_FOREIGN_TABLE);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_MISC;
@@ -3062,7 +3077,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)
@@ -3076,7 +3091,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)
@@ -3144,13 +3159,13 @@ 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_AlterConstraint: /* ALTER CONSTRAINT */
- ATSimplePermissions(rel, ATT_TABLE);
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
pass = AT_PASS_MISC;
break;
case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
@@ -3179,7 +3194,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);
@@ -3187,6 +3201,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_DropInherit: /* NO INHERIT */
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ /* These commands never recurse */
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
case AT_GenericOptions:
ATSimplePermissions(rel, ATT_FOREIGN_TABLE);
/* No command-specific prep needed */
@@ -4084,6 +4104,9 @@ ATWrongRelkindError(Relation rel, int allowed_targets)
case ATT_TABLE | ATT_MATVIEW | ATT_INDEX:
msg = _("\"%s\" is not a table, materialized view, or index");
break;
+ case ATT_TABLE | ATT_MATVIEW | ATT_FOREIGN_TABLE:
+ msg = _("\"%s\" is not a table, materialized view, or foreign table");
+ break;
case ATT_TABLE | ATT_FOREIGN_TABLE:
msg = _("\"%s\" is not a table or foreign table");
break;
@@ -4439,7 +4462,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);
@@ -5232,6 +5255,10 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc
HeapTuple tuple;
Form_pg_attribute attrtuple;
+ /* Do nothing for foreign tables. */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ return;
+
Assert(IsA(newValue, String));
storagemode = strVal(newValue);
@@ -5335,7 +5362,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
@@ -5726,7 +5753,7 @@ 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);
/*
* Call AddRelationNewConstraints to do the work, making sure it works on
@@ -7215,7 +7242,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);
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/parser/gram.y b/src/backend/parser/gram.y
index 0787eb7..a296b4c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -4207,32 +4207,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 eb07ca3..7a01e18 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;
@@ -605,10 +599,10 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
static void
transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
{
- if (cxt->isforeign)
+ if (cxt->isforeign && constraint->contype != CONSTR_CHECK)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("constraints are not supported on foreign tables"),
+ errmsg("only check constraints are supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
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 '@');
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-30 05:06 Atri Sharma <atri.jiit@gmail.com>
parent: Kouhei Kaigai <kaigai@ak.jp.nec.com>
0 siblings, 0 replies; 179+ messages in thread
From: Atri Sharma @ 2014-01-30 05:06 UTC (permalink / raw)
To: Kouhei Kaigai <kaigai@ak.jp.nec.com>; +Cc: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; David Fetter <david@fetter.org>; pgsql-hackers
Sent from my iPad
On 28-Jan-2014, at 10:04, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:
>>> I wonder what shall be the cases when foreign table is on a server which
>> does not support *all* SQL features.
>>>
>>> Does a FDW need to have the possible inherit options mentioned in its
>> documentation for this patch?
>>
>> The answer is no, in my understanding. The altering operation simply
>> declares some chages for foreign tables in the inheritance tree and does
>> nothing to the underlying storages.
> It seems to me Atri mention about the idea to enforce constraint when
> partitioned foreign table was referenced...
>
> BTW, isn't it feasible to consign FDW drivers its decision?
> If a foreign table has a check constraint (X BETWEEN 101 AND 200),
> postgres_fdw will be capable to run this check on the remote server,
> however, file_fdw will not be capable. It is usual job of them when
> qualifiers are attached on Path node.
> Probably, things to do is simple. If the backend appends the configured
> check constraint as if a user-given qualifier, FDW driver will handle
Hi,
I think that pushing it to FDW driver is the best approach. The FDW driver knows whether or not the foreign server supports the said feature,hence,the user should be abstracted from that.
I agree that the foreign constraint can be added as a user defined qualifier and dealt with accordingly.
Regards,
Atri
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-30 16:04 Tom Lane <tgl@sss.pgh.pa.us>
parent: Shigeru Hanada <shigeru.hanada@gmail.com>
1 sibling, 1 reply; 179+ messages in thread
From: Tom Lane @ 2014-01-30 16:04 UTC (permalink / raw)
To: Shigeru Hanada <shigeru.hanada@gmail.com>; +Cc: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; pgsql-hackers
Shigeru Hanada <shigeru.hanada@gmail.com> writes:
> At the moment we don't use attstorage for foreign tables, so allowing
> SET STORAGE against foreign tables never introduce visible change
> except \d+ output of foreign tables. But IMO such operation should
> not allowed because users would be confused. So I changed
> ATExecSetStorage() to skip on foreign tables.
I think this is totally misguided. Who's to say that some weird FDW
might not pay attention to attstorage? I could imagine a file-based
FDW using that to decide whether to compress columns, for instance.
Admittedly, the chances of that aren't large, but it's pretty hard
to argue that going out of our way to prevent it is a useful activity.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-30 21:48 Robert Haas <robertmhaas@gmail.com>
parent: Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 1 reply; 179+ messages in thread
From: Robert Haas @ 2014-01-30 21:48 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Shigeru Hanada <shigeru.hanada@gmail.com>; Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; pgsql-hackers
On Thu, Jan 30, 2014 at 11:04 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Shigeru Hanada <shigeru.hanada@gmail.com> writes:
>> At the moment we don't use attstorage for foreign tables, so allowing
>> SET STORAGE against foreign tables never introduce visible change
>> except \d+ output of foreign tables. But IMO such operation should
>> not allowed because users would be confused. So I changed
>> ATExecSetStorage() to skip on foreign tables.
>
> I think this is totally misguided. Who's to say that some weird FDW
> might not pay attention to attstorage? I could imagine a file-based
> FDW using that to decide whether to compress columns, for instance.
> Admittedly, the chances of that aren't large, but it's pretty hard
> to argue that going out of our way to prevent it is a useful activity.
I think that's a pretty tenuous position. There are already
FDW-specific options sufficient to let a particular FDW store whatever
kinds of options it likes; letting the user set options that were only
ever intended to be applied to tables just because we can seems sort
of dubious. I'm tempted by the idea of continuing to disallow SET
STORAGE on an unvarnished foreign table, but allowing it on an
inheritance hierarchy that contains at least one real table, with the
semantics that we quietly ignore the foreign tables and apply the
operation to the plain tables.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-30 22:05 Tom Lane <tgl@sss.pgh.pa.us>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 179+ messages in thread
From: Tom Lane @ 2014-01-30 22:05 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Shigeru Hanada <shigeru.hanada@gmail.com>; Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
> On Thu, Jan 30, 2014 at 11:04 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> I think this is totally misguided. Who's to say that some weird FDW
>> might not pay attention to attstorage? I could imagine a file-based
>> FDW using that to decide whether to compress columns, for instance.
>> Admittedly, the chances of that aren't large, but it's pretty hard
>> to argue that going out of our way to prevent it is a useful activity.
> I think that's a pretty tenuous position. There are already
> FDW-specific options sufficient to let a particular FDW store whatever
> kinds of options it likes; letting the user set options that were only
> ever intended to be applied to tables just because we can seems sort
> of dubious. I'm tempted by the idea of continuing to disallow SET
> STORAGE on an unvarnished foreign table, but allowing it on an
> inheritance hierarchy that contains at least one real table, with the
> semantics that we quietly ignore the foreign tables and apply the
> operation to the plain tables.
[ shrug... ] By far the easiest implementation of that is just to apply
the catalog change to all of them. According to your assumptions, it'll
be a no-op on the foreign tables anyway.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-31 00:56 Robert Haas <robertmhaas@gmail.com>
parent: Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 2 replies; 179+ messages in thread
From: Robert Haas @ 2014-01-31 00:56 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Shigeru Hanada <shigeru.hanada@gmail.com>; Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; pgsql-hackers
On Thu, Jan 30, 2014 at 5:05 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
>> On Thu, Jan 30, 2014 at 11:04 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>>> I think this is totally misguided. Who's to say that some weird FDW
>>> might not pay attention to attstorage? I could imagine a file-based
>>> FDW using that to decide whether to compress columns, for instance.
>>> Admittedly, the chances of that aren't large, but it's pretty hard
>>> to argue that going out of our way to prevent it is a useful activity.
>
>> I think that's a pretty tenuous position. There are already
>> FDW-specific options sufficient to let a particular FDW store whatever
>> kinds of options it likes; letting the user set options that were only
>> ever intended to be applied to tables just because we can seems sort
>> of dubious. I'm tempted by the idea of continuing to disallow SET
>> STORAGE on an unvarnished foreign table, but allowing it on an
>> inheritance hierarchy that contains at least one real table, with the
>> semantics that we quietly ignore the foreign tables and apply the
>> operation to the plain tables.
>
> [ shrug... ] By far the easiest implementation of that is just to apply
> the catalog change to all of them. According to your assumptions, it'll
> be a no-op on the foreign tables anyway.
Well, there's some point to that, too, I suppose. What do others think?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: [Review] inherit support for foreign tables
@ 2014-01-31 02:55 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 0 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-01-31 02:55 UTC (permalink / raw)
To: Shigeru Hanada <shigeru.hanada@gmail.com>; +Cc: pgsql-hackers
(2014/01/28 22:01), Etsuro Fujita wrote:
> (2014/01/27 21:49), Shigeru Hanada wrote:
>> Is it too big change that making ANALYZE command to handle foreign
>> tables too even if no table name was specified? IIRC, performance
>> issue was the reason to exclude foreign tables from auto-analyze
>> processing. ANALYZEing large database contains local huge data also
>> takes long time. One idea to avoid unexpected long processing is to
>> add option to foreign tables to mark it as "not-auto-analyzable".
>
> Maybe I didn't express my idea clearly. Sorry for that.
>
> I don't think that we now allow the ANALYZE command to handle foreign
> tables when no table name is specified with the command. I think that
> we allow the ANALYZE command to handle an inheritance tree that includes
> foreign tables when the name of the parent table is specified, without
> ignoring such foreign tables in the caluculation. ISTM it would be
> possible to do so if we introduce a new parameter, say, vac_mode, which
> indicates wether vacuum() is called with a specific table or not.
>
> I'll try to modify the ANALYZE command to do so on top of you patch.
Done on top of your patch, foreign_inherit.patch, not the latest
version, foreign_inherit-v2.patch. As I proposed, an inheritance tree
that includes foreign tables is now ANALYZEd, without ignoring such
foreign tables in the inherited-stats computation, if the name of the
parent table is specified with the ANALYZE command. (That has been done
by a small modification of analyze.c, thanks to [1].) The ANALYZE
command with no tablename or the autovacuum worker skips the
inherited-stats computation itself for inheritance trees that includes
foreign tables, which is different from the original patch. To
distinguish the ANALYZE-with-a-tablename command from the others (ie,
the ANALYZE-with-no-tablename command or the autovacuum worker), I've
introduced a new parameter, vacmode, in vacuum(), and thus called
analyze_rel() with that parameter. Attached is the modified patch.
Could you review the patch?
Thanks,
[1]
http://www.postgresql.org/message-id/E1SGFOO-0006ZF-8n@gemulon.postgresql.org
Best regards,
Etsuro Fujita
*** 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.
</para>
+ <note>
+ <para>
+ 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
+ <xref linkend="ddl-inherit">) to off-load to multiple servers.
+ </para>
+ </note>
+
<sect2 id="ddl-constraints-check-constraints">
<title>Check Constraints</title>
***************
*** 2021,2028 **** CREATE TABLE capitals (
</para>
<para>
! In <productname>PostgreSQL</productname>, 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,
--- 2032,2039 ----
</para>
<para>
! In <productname>PostgreSQL</productname>, 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/ref/alter_foreign_table.sgml
--- b/doc/src/sgml/ref/alter_foreign_table.sgml
***************
*** 42,47 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 42,49 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 178,183 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 180,205 ----
</varlistentry>
<varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
<listitem>
<para>
***************
*** 306,311 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 328,342 ----
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">parent_name</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
*** a/doc/src/sgml/ref/create_foreign_table.sgml
--- b/doc/src/sgml/ref/create_foreign_table.sgml
***************
*** 22,27 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 22,28 ----
<replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 160,176 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ The name of an existing table or foreign table from which the new foreign
+ table automatically inherits all columns, see
+ <xref linkend="ddl-inherit"> for details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 91,96 **** static void BlockSampler_Init(BlockSampler bs, BlockNumber nblocks,
--- 91,98 ----
int samplesize);
static bool BlockSampler_HasMore(BlockSampler bs);
static BlockNumber BlockSampler_Next(BlockSampler bs);
+ static bool inheritance_tree_include_foreign(Relation onerel,
+ bool *isAnalyzable);
static void compute_index_stats(Relation onerel, double totalrows,
AnlIndexData *indexdata, int nindexes,
HeapTuple *rows, int numrows,
***************
*** 114,120 **** 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,123 ----
* analyze_rel() -- analyze one relation
*/
void
! analyze_rel(Oid relid, VacuumStmt *vacstmt, VacuumMode vacmode,
! BufferAccessStrategy bstrategy)
{
Relation onerel;
int elevel;
***************
*** 270,276 **** analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
* If there are child tables, do recursive ANALYZE.
*/
if (onerel->rd_rel->relhassubclass)
! do_analyze_rel(onerel, vacstmt, acquirefunc, relpages, true, elevel);
/*
* Close source relation now, but keep lock so that no one deletes it
--- 273,288 ----
* If there are child tables, do recursive ANALYZE.
*/
if (onerel->rd_rel->relhassubclass)
! {
! bool include_foreign;
! bool isAnalyzable;
!
! include_foreign = inheritance_tree_include_foreign(onerel,
! &isAnalyzable);
!
! if (!include_foreign || (isAnalyzable && vacmode == VAC_MODE_SINGLE))
! do_analyze_rel(onerel, vacstmt, acquirefunc, relpages, true, elevel);
! }
/*
* Close source relation now, but keep lock so that no one deletes it
***************
*** 668,673 **** do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
--- 680,772 ----
}
/*
+ * Check wether the inheritance tree includes foreign tables. And if so,
+ * check wether such foreign tables are all analyzable.
+ */
+ static bool
+ inheritance_tree_include_foreign(Relation onerel, bool *isAnalyzable)
+ {
+ bool result = false;
+ List *tableOIDs;
+ ListCell *lc;
+
+ *isAnalyzable = true;
+
+ /*
+ * Find all members of inheritance set. We only need AccessShareLock on
+ * the children.
+ */
+ tableOIDs =
+ find_all_inheritors(RelationGetRelid(onerel), AccessShareLock, NULL);
+
+ /*
+ * Check that there's at least one descendant, else fail. This could
+ * happen despite analyze_rel's relhassubclass check, if table once had a
+ * child but no longer does. In that case, we can clear the
+ * relhassubclass field so as not to make the same mistake again later.
+ * (This is safe because we hold ShareUpdateExclusiveLock.)
+ */
+ if (list_length(tableOIDs) < 2)
+ {
+ /* CCI because we already updated the pg_class row in this command */
+ CommandCounterIncrement();
+ SetRelationHasSubclass(RelationGetRelid(onerel), false);
+ return result;
+ }
+
+ /*
+ * Check wether the inheritance tree includes some foreign tables.
+ */
+ foreach(lc, tableOIDs)
+ {
+ Oid childOID = lfirst_oid(lc);
+ Relation childrel;
+
+ /* Parent table should not be foreign */
+ if (childOID == RelationGetRelid(onerel))
+ continue;
+
+ /* We already got the needed lock */
+ childrel = heap_open(childOID, NoLock);
+
+ /* Ignore if temp table of another backend */
+ if (RELATION_IS_OTHER_TEMP(childrel))
+ {
+ /* ... but release the lock on it */
+ Assert(childrel != onerel);
+ heap_close(childrel, AccessShareLock);
+ continue;
+ }
+
+ if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ FdwRoutine *fdwroutine;
+ AcquireSampleRowsFunc func = NULL;
+ BlockNumber relpages = 0;
+ bool ok = false;
+
+ /* Found it */
+ result = true;
+
+ /* Check whether the FDW supports analysis */
+ fdwroutine = GetFdwRoutineForRelation(childrel, false);
+
+ if (fdwroutine->AnalyzeForeignTable != NULL)
+ ok = fdwroutine->AnalyzeForeignTable(childrel,
+ &func,
+ &relpages);
+
+ if (!ok)
+ *isAnalyzable = false;
+ }
+
+ heap_close(childrel, AccessShareLock);
+ }
+
+ return result;
+ }
+
+ /*
* Compute statistics about indexes of a relation
*/
static void
***************
*** 1451,1456 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1550,1556 ----
{
List *tableOIDs;
Relation *rels;
+ AcquireSampleRowsFunc *acquirefunc;
double *relblocks;
double totalblocks;
int numrows,
***************
*** 1485,1490 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1585,1592 ----
* 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;
***************
*** 1506,1512 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
}
rels[nrels] = childrel;
! relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
totalblocks += relblocks[nrels];
nrels++;
}
--- 1608,1638 ----
}
rels[nrels] = childrel;
!
! if (childrel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
! {
! acquirefunc[nrels] = acquire_sample_rows;
! relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
! }
! else
! {
! FdwRoutine *fdwroutine;
! BlockNumber relpages = 0;
! bool ok = false;
!
! acquirefunc[nrels] = NULL;
!
! fdwroutine = GetFdwRoutineForRelation(childrel, false);
! Assert(fdwroutine->AnalyzeForeignTable != NULL)
!
! ok = fdwroutine->AnalyzeForeignTable(childrel,
! &acquirefunc[nrels],
! &relpages);
! Assert(ok);
!
! relblocks[nrels] = (double) relpages;
! }
!
totalblocks += relblocks[nrels];
nrels++;
}
***************
*** 1524,1529 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1650,1656 ----
{
Relation childrel = rels[i];
double childblocks = relblocks[i];
+ AcquireSampleRowsFunc childacquirefunc = acquirefunc[i];
if (childblocks > 0)
{
***************
*** 1539,1550 **** 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 &&
--- 1666,1677 ----
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
***************
*** 465,474 **** 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,
--- 465,489 ----
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
! /*
! * Shouldn't this have been checked in parser?
! */
! if (relkind == RELKIND_FOREIGN_TABLE)
! {
! ListCell *lc;
! foreach(lc, stmt->constraints)
! {
! NewConstraint *nc = lfirst(lc);
!
! if (nc->contype != CONSTR_CHECK &&
! nc->contype != CONSTR_DEFAULT &&
! nc->contype != CONSTR_NULL &&
! nc->contype != CONSTR_NOTNULL)
! ereport(ERROR,
! (errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("only check constraints are supported on foreign tables")));
! }
! }
/*
* Look up the namespace in which we are supposed to create the relation,
***************
*** 3044,3050 **** 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)
--- 3059,3065 ----
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)
***************
*** 3058,3064 **** 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)
--- 3073,3079 ----
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)
***************
*** 3126,3138 **** 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 */
--- 3141,3153 ----
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_AlterConstraint: /* ALTER CONSTRAINT */
! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
pass = AT_PASS_MISC;
break;
case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
***************
*** 3161,3167 **** 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);
--- 3176,3181 ----
***************
*** 3169,3174 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
--- 3183,3194 ----
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_DropInherit: /* NO INHERIT */
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ /* These commands never recurse */
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
case AT_GenericOptions:
ATSimplePermissions(rel, ATT_FOREIGN_TABLE);
/* No command-specific prep needed */
***************
*** 4421,4427 **** 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);
--- 4441,4447 ----
/* 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);
***************
*** 5317,5323 **** 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
--- 5337,5343 ----
/* 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
***************
*** 5708,5714 **** 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
--- 5728,5734 ----
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/*
* Call AddRelationNewConstraints to do the work, making sure it works on
***************
*** 7197,7203 **** 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);
--- 7217,7223 ----
/* 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);
*** a/src/backend/commands/vacuum.c
--- b/src/backend/commands/vacuum.c
***************
*** 96,101 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
--- 96,102 ----
BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel)
{
const char *stmttype;
+ VacuumMode vacmode;
volatile bool in_outer_xact,
use_own_xacts;
List *relations;
***************
*** 144,149 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
--- 145,164 ----
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.
*/
***************
*** 246,252 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
PushActiveSnapshot(GetTransactionSnapshot());
}
! analyze_rel(relid, vacstmt, vac_strategy);
if (use_own_xacts)
{
--- 261,267 ----
PushActiveSnapshot(GetTransactionSnapshot());
}
! analyze_rel(relid, vacstmt, vacmode, 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/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 4206,4237 **** 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;
}
;
--- 4206,4237 ----
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 ----
***************
*** 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,608 ----
static void
transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
{
! if (cxt->isforeign && constraint->contype != CONSTR_CHECK)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("only check constraints are supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
*** a/src/include/commands/vacuum.h
--- b/src/include/commands/vacuum.h
***************
*** 138,143 **** extern int vacuum_freeze_min_age;
--- 138,152 ----
extern int vacuum_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);
***************
*** 170,176 **** 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);
--- 179,185 ----
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/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 '@');
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/plain] foreign_inherit-v1.1.patch (30.2K, ../../52EB10AC.4070307@lab.ntt.co.jp/2-foreign_inherit-v1.1.patch)
download | inline diff:
*** 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.
</para>
+ <note>
+ <para>
+ 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
+ <xref linkend="ddl-inherit">) to off-load to multiple servers.
+ </para>
+ </note>
+
<sect2 id="ddl-constraints-check-constraints">
<title>Check Constraints</title>
***************
*** 2021,2028 **** CREATE TABLE capitals (
</para>
<para>
! In <productname>PostgreSQL</productname>, 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,
--- 2032,2039 ----
</para>
<para>
! In <productname>PostgreSQL</productname>, 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/ref/alter_foreign_table.sgml
--- b/doc/src/sgml/ref/alter_foreign_table.sgml
***************
*** 42,47 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 42,49 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 178,183 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 180,205 ----
</varlistentry>
<varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
<listitem>
<para>
***************
*** 306,311 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 328,342 ----
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">parent_name</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
*** a/doc/src/sgml/ref/create_foreign_table.sgml
--- b/doc/src/sgml/ref/create_foreign_table.sgml
***************
*** 22,27 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 22,28 ----
<replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 160,176 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ The name of an existing table or foreign table from which the new foreign
+ table automatically inherits all columns, see
+ <xref linkend="ddl-inherit"> for details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 91,96 **** static void BlockSampler_Init(BlockSampler bs, BlockNumber nblocks,
--- 91,98 ----
int samplesize);
static bool BlockSampler_HasMore(BlockSampler bs);
static BlockNumber BlockSampler_Next(BlockSampler bs);
+ static bool inheritance_tree_include_foreign(Relation onerel,
+ bool *isAnalyzable);
static void compute_index_stats(Relation onerel, double totalrows,
AnlIndexData *indexdata, int nindexes,
HeapTuple *rows, int numrows,
***************
*** 114,120 **** 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,123 ----
* analyze_rel() -- analyze one relation
*/
void
! analyze_rel(Oid relid, VacuumStmt *vacstmt, VacuumMode vacmode,
! BufferAccessStrategy bstrategy)
{
Relation onerel;
int elevel;
***************
*** 270,276 **** analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
* If there are child tables, do recursive ANALYZE.
*/
if (onerel->rd_rel->relhassubclass)
! do_analyze_rel(onerel, vacstmt, acquirefunc, relpages, true, elevel);
/*
* Close source relation now, but keep lock so that no one deletes it
--- 273,288 ----
* If there are child tables, do recursive ANALYZE.
*/
if (onerel->rd_rel->relhassubclass)
! {
! bool include_foreign;
! bool isAnalyzable;
!
! include_foreign = inheritance_tree_include_foreign(onerel,
! &isAnalyzable);
!
! if (!include_foreign || (isAnalyzable && vacmode == VAC_MODE_SINGLE))
! do_analyze_rel(onerel, vacstmt, acquirefunc, relpages, true, elevel);
! }
/*
* Close source relation now, but keep lock so that no one deletes it
***************
*** 668,673 **** do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
--- 680,772 ----
}
/*
+ * Check wether the inheritance tree includes foreign tables. And if so,
+ * check wether such foreign tables are all analyzable.
+ */
+ static bool
+ inheritance_tree_include_foreign(Relation onerel, bool *isAnalyzable)
+ {
+ bool result = false;
+ List *tableOIDs;
+ ListCell *lc;
+
+ *isAnalyzable = true;
+
+ /*
+ * Find all members of inheritance set. We only need AccessShareLock on
+ * the children.
+ */
+ tableOIDs =
+ find_all_inheritors(RelationGetRelid(onerel), AccessShareLock, NULL);
+
+ /*
+ * Check that there's at least one descendant, else fail. This could
+ * happen despite analyze_rel's relhassubclass check, if table once had a
+ * child but no longer does. In that case, we can clear the
+ * relhassubclass field so as not to make the same mistake again later.
+ * (This is safe because we hold ShareUpdateExclusiveLock.)
+ */
+ if (list_length(tableOIDs) < 2)
+ {
+ /* CCI because we already updated the pg_class row in this command */
+ CommandCounterIncrement();
+ SetRelationHasSubclass(RelationGetRelid(onerel), false);
+ return result;
+ }
+
+ /*
+ * Check wether the inheritance tree includes some foreign tables.
+ */
+ foreach(lc, tableOIDs)
+ {
+ Oid childOID = lfirst_oid(lc);
+ Relation childrel;
+
+ /* Parent table should not be foreign */
+ if (childOID == RelationGetRelid(onerel))
+ continue;
+
+ /* We already got the needed lock */
+ childrel = heap_open(childOID, NoLock);
+
+ /* Ignore if temp table of another backend */
+ if (RELATION_IS_OTHER_TEMP(childrel))
+ {
+ /* ... but release the lock on it */
+ Assert(childrel != onerel);
+ heap_close(childrel, AccessShareLock);
+ continue;
+ }
+
+ if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ FdwRoutine *fdwroutine;
+ AcquireSampleRowsFunc func = NULL;
+ BlockNumber relpages = 0;
+ bool ok = false;
+
+ /* Found it */
+ result = true;
+
+ /* Check whether the FDW supports analysis */
+ fdwroutine = GetFdwRoutineForRelation(childrel, false);
+
+ if (fdwroutine->AnalyzeForeignTable != NULL)
+ ok = fdwroutine->AnalyzeForeignTable(childrel,
+ &func,
+ &relpages);
+
+ if (!ok)
+ *isAnalyzable = false;
+ }
+
+ heap_close(childrel, AccessShareLock);
+ }
+
+ return result;
+ }
+
+ /*
* Compute statistics about indexes of a relation
*/
static void
***************
*** 1451,1456 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1550,1556 ----
{
List *tableOIDs;
Relation *rels;
+ AcquireSampleRowsFunc *acquirefunc;
double *relblocks;
double totalblocks;
int numrows,
***************
*** 1485,1490 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1585,1592 ----
* 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;
***************
*** 1506,1512 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
}
rels[nrels] = childrel;
! relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
totalblocks += relblocks[nrels];
nrels++;
}
--- 1608,1638 ----
}
rels[nrels] = childrel;
!
! if (childrel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
! {
! acquirefunc[nrels] = acquire_sample_rows;
! relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
! }
! else
! {
! FdwRoutine *fdwroutine;
! BlockNumber relpages = 0;
! bool ok = false;
!
! acquirefunc[nrels] = NULL;
!
! fdwroutine = GetFdwRoutineForRelation(childrel, false);
! Assert(fdwroutine->AnalyzeForeignTable != NULL)
!
! ok = fdwroutine->AnalyzeForeignTable(childrel,
! &acquirefunc[nrels],
! &relpages);
! Assert(ok);
!
! relblocks[nrels] = (double) relpages;
! }
!
totalblocks += relblocks[nrels];
nrels++;
}
***************
*** 1524,1529 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1650,1656 ----
{
Relation childrel = rels[i];
double childblocks = relblocks[i];
+ AcquireSampleRowsFunc childacquirefunc = acquirefunc[i];
if (childblocks > 0)
{
***************
*** 1539,1550 **** 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 &&
--- 1666,1677 ----
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
***************
*** 465,474 **** 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,
--- 465,489 ----
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
! /*
! * Shouldn't this have been checked in parser?
! */
! if (relkind == RELKIND_FOREIGN_TABLE)
! {
! ListCell *lc;
! foreach(lc, stmt->constraints)
! {
! NewConstraint *nc = lfirst(lc);
!
! if (nc->contype != CONSTR_CHECK &&
! nc->contype != CONSTR_DEFAULT &&
! nc->contype != CONSTR_NULL &&
! nc->contype != CONSTR_NOTNULL)
! ereport(ERROR,
! (errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("only check constraints are supported on foreign tables")));
! }
! }
/*
* Look up the namespace in which we are supposed to create the relation,
***************
*** 3044,3050 **** 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)
--- 3059,3065 ----
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)
***************
*** 3058,3064 **** 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)
--- 3073,3079 ----
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)
***************
*** 3126,3138 **** 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 */
--- 3141,3153 ----
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_AlterConstraint: /* ALTER CONSTRAINT */
! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
pass = AT_PASS_MISC;
break;
case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
***************
*** 3161,3167 **** 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);
--- 3176,3181 ----
***************
*** 3169,3174 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
--- 3183,3194 ----
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_DropInherit: /* NO INHERIT */
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ /* These commands never recurse */
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
case AT_GenericOptions:
ATSimplePermissions(rel, ATT_FOREIGN_TABLE);
/* No command-specific prep needed */
***************
*** 4421,4427 **** 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);
--- 4441,4447 ----
/* 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);
***************
*** 5317,5323 **** 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
--- 5337,5343 ----
/* 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
***************
*** 5708,5714 **** 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
--- 5728,5734 ----
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/*
* Call AddRelationNewConstraints to do the work, making sure it works on
***************
*** 7197,7203 **** 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);
--- 7217,7223 ----
/* 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);
*** a/src/backend/commands/vacuum.c
--- b/src/backend/commands/vacuum.c
***************
*** 96,101 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
--- 96,102 ----
BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel)
{
const char *stmttype;
+ VacuumMode vacmode;
volatile bool in_outer_xact,
use_own_xacts;
List *relations;
***************
*** 144,149 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
--- 145,164 ----
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.
*/
***************
*** 246,252 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
PushActiveSnapshot(GetTransactionSnapshot());
}
! analyze_rel(relid, vacstmt, vac_strategy);
if (use_own_xacts)
{
--- 261,267 ----
PushActiveSnapshot(GetTransactionSnapshot());
}
! analyze_rel(relid, vacstmt, vacmode, 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/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 4206,4237 **** 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;
}
;
--- 4206,4237 ----
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 ----
***************
*** 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,608 ----
static void
transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
{
! if (cxt->isforeign && constraint->contype != CONSTR_CHECK)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("only check constraints are supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
*** a/src/include/commands/vacuum.h
--- b/src/include/commands/vacuum.h
***************
*** 138,143 **** extern int vacuum_freeze_min_age;
--- 138,152 ----
extern int vacuum_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);
***************
*** 170,176 **** 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);
--- 179,185 ----
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/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 '@');
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-31 15:21 Stephen Frost <sfrost@snowman.net>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 1 reply; 179+ messages in thread
From: Stephen Frost @ 2014-01-31 15:21 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Shigeru Hanada <shigeru.hanada@gmail.com>; Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; pgsql-hackers
* Robert Haas (robertmhaas@gmail.com) wrote:
> On Thu, Jan 30, 2014 at 5:05 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> > Robert Haas <robertmhaas@gmail.com> writes:
> >> On Thu, Jan 30, 2014 at 11:04 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> >>> I think this is totally misguided. Who's to say that some weird FDW
> >>> might not pay attention to attstorage? I could imagine a file-based
> >>> FDW using that to decide whether to compress columns, for instance.
> >>> Admittedly, the chances of that aren't large, but it's pretty hard
> >>> to argue that going out of our way to prevent it is a useful activity.
> >
> >> I think that's a pretty tenuous position. There are already
> >> FDW-specific options sufficient to let a particular FDW store whatever
> >> kinds of options it likes; letting the user set options that were only
> >> ever intended to be applied to tables just because we can seems sort
> >> of dubious. I'm tempted by the idea of continuing to disallow SET
> >> STORAGE on an unvarnished foreign table, but allowing it on an
> >> inheritance hierarchy that contains at least one real table, with the
> >> semantics that we quietly ignore the foreign tables and apply the
> >> operation to the plain tables.
> >
> > [ shrug... ] By far the easiest implementation of that is just to apply
> > the catalog change to all of them. According to your assumptions, it'll
> > be a no-op on the foreign tables anyway.
>
> Well, there's some point to that, too, I suppose. What do others think?
I agree that using the FDW-specific options is the right approach and
disallowing those to be set on foreign tables makes sense. I don't
particularly like the idea of applying changes during inheiritance
which we wouldn't allow the user to do directly. While it might be a
no-op and no FDW might use it, it'd still be confusing.
Thanks,
Stephen
Attachments:
[application/pgp-signature] signature.asc (835B, ../../20140131152136.GJ2921@tamriel.snowman.net/2-signature.asc)
download
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-31 15:30 Tom Lane <tgl@sss.pgh.pa.us>
parent: Stephen Frost <sfrost@snowman.net>
0 siblings, 1 reply; 179+ messages in thread
From: Tom Lane @ 2014-01-31 15:30 UTC (permalink / raw)
To: Stephen Frost <sfrost@snowman.net>; +Cc: Robert Haas <robertmhaas@gmail.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; pgsql-hackers
Stephen Frost <sfrost@snowman.net> writes:
> I agree that using the FDW-specific options is the right approach and
> disallowing those to be set on foreign tables makes sense. I don't
> particularly like the idea of applying changes during inheiritance
> which we wouldn't allow the user to do directly. While it might be a
> no-op and no FDW might use it, it'd still be confusing.
If we don't allow it directly, why not? Seems rather arbitrary.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-01-31 18:05 Stephen Frost <sfrost@snowman.net>
parent: Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 0 replies; 179+ messages in thread
From: Stephen Frost @ 2014-01-31 18:05 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Robert Haas <robertmhaas@gmail.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; pgsql-hackers
* Tom Lane (tgl@sss.pgh.pa.us) wrote:
> Stephen Frost <sfrost@snowman.net> writes:
> > I agree that using the FDW-specific options is the right approach and
> > disallowing those to be set on foreign tables makes sense. I don't
> > particularly like the idea of applying changes during inheiritance
> > which we wouldn't allow the user to do directly. While it might be a
> > no-op and no FDW might use it, it'd still be confusing.
>
> If we don't allow it directly, why not? Seems rather arbitrary.
I'm apparently missing something here. From my perspective, they're
either allowed or they're not and if they aren't allowed then they
shouldn't be allowed to happen. I'd view this like a CHECK constraint-
if it's a foreign table then it can't have a value for SET STORAGE. I
don't see why it would be 'ok' to allow it to be set if we're setting it
as part of inheiritance but otherwise not allow it to be set.
Thanks,
Stephen
Attachments:
[application/pgp-signature] signature.asc (835B, ../../20140131180508.GM2921@tamriel.snowman.net/2-signature.asc)
download
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-02-03 03:15 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-02-03 03:15 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
(2014/01/31 9:56), Robert Haas wrote:
> On Thu, Jan 30, 2014 at 5:05 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> Robert Haas <robertmhaas@gmail.com> writes:
>>> On Thu, Jan 30, 2014 at 11:04 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>>>> I think this is totally misguided. Who's to say that some weird FDW
>>>> might not pay attention to attstorage? I could imagine a file-based
>>>> FDW using that to decide whether to compress columns, for instance.
>>>> Admittedly, the chances of that aren't large, but it's pretty hard
>>>> to argue that going out of our way to prevent it is a useful activity.
>>
>>> I think that's a pretty tenuous position. There are already
>>> FDW-specific options sufficient to let a particular FDW store whatever
>>> kinds of options it likes; letting the user set options that were only
>>> ever intended to be applied to tables just because we can seems sort
>>> of dubious. I'm tempted by the idea of continuing to disallow SET
>>> STORAGE on an unvarnished foreign table, but allowing it on an
>>> inheritance hierarchy that contains at least one real table, with the
>>> semantics that we quietly ignore the foreign tables and apply the
>>> operation to the plain tables.
>>
>> [ shrug... ] By far the easiest implementation of that is just to apply
>> the catalog change to all of them. According to your assumptions, it'll
>> be a no-op on the foreign tables anyway.
>
> Well, there's some point to that, too, I suppose. What do others think?
Allowing ALTER COLUMN SET STORAGE on foreign tables would make sense if
for example, "SELECT * INTO local_table FROM foreign_table" did create a
new local table of columns having the storage types associated with
those of a foreign table?
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-02-04 11:56 Robert Haas <robertmhaas@gmail.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Robert Haas @ 2014-02-04 11:56 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On Sun, Feb 2, 2014 at 10:15 PM, Etsuro Fujita
<fujita.etsuro@lab.ntt.co.jp> wrote:
> Allowing ALTER COLUMN SET STORAGE on foreign tables would make sense if for
> example, "SELECT * INTO local_table FROM foreign_table" did create a new
> local table of columns having the storage types associated with those of a
> foreign table?
Seems like a pretty weak argument. It's not that we can't find
strange corner cases where applying SET STORAGE to a foreign table
doesn't do something; it's that they *are* strange corner cases. The
options as we normally don't understand them just aren't sensible in
this context, and a good deal of work has been put into an alternative
options framework, which is what authors of FDWs ought to be using.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-02-05 02:20 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 0 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-02-05 02:20 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
(2014/02/04 20:56), Robert Haas wrote:
> On Sun, Feb 2, 2014 at 10:15 PM, Etsuro Fujita
> <fujita.etsuro@lab.ntt.co.jp> wrote:
>> Allowing ALTER COLUMN SET STORAGE on foreign tables would make sense if for
>> example, "SELECT * INTO local_table FROM foreign_table" did create a new
>> local table of columns having the storage types associated with those of a
>> foreign table?
>
> Seems like a pretty weak argument. It's not that we can't find
> strange corner cases where applying SET STORAGE to a foreign table
> doesn't do something; it's that they *are* strange corner cases. The
> options as we normally don't understand them just aren't sensible in
> this context, and a good deal of work has been put into an alternative
> options framework, which is what authors of FDWs ought to be using.
I just wanted to discuss the possiblity of allowing SET STORAGE on a
foreign table, but I've got the point. I'll resume the patch review.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-02-07 12:31 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Shigeru Hanada <shigeru.hanada@gmail.com>
1 sibling, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-02-07 12:31 UTC (permalink / raw)
To: Shigeru Hanada <shigeru.hanada@gmail.com>; +Cc: pgsql-hackers
Hi Hanada-san,
Sorry for the delay.
(2014/01/30 14:01), Shigeru Hanada wrote:
>> 2014-01-27 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>:
>>> While still reviwing this patch, I feel this patch has given enough
>>> consideration to interactions with other commands, but found the following
>>> incorrect? behabior:
>>>
>>> postgres=# CREATE TABLE product (id INTEGER, description TEXT);
>>> CREATE TABLE
>>> postgres=# CREATE FOREIGN TABLE product1 () INHERITS (product) SERVER fs
>>> OPTIONS (filename '/home/foo/product1.csv', format 'csv');
>>> CREATE FOREIGN TABLE
>>> postgres=# ALTER TABLE product ALTER COLUMN description SET STORAGE
>>> EXTERNAL;
>>> ERROR: "product1" is not a table or materialized view
>>>
>>> ISTN the ALTER TABLE simple recursion mechanism (ie ATSimpleRecursion())
>>> should be modified for the ALTER COLUMN SET STORAGE case.
> It seems little bit complex than I expected. Currently foreign tables
> deny ALTER TABLE SET STORAGE with message like below, because foreign
> tables don't have storage in the meaning of PG heap tables.
>
> ERROR: "pgbench1_accounts_c1" is not a table or materialized view
>
> At the moment we don't use attstorage for foreign tables, so allowing
> SET STORAGE against foreign tables never introduce visible change
> except \d+ output of foreign tables. But IMO such operation should
> not allowed because users would be confused. So I changed
> ATExecSetStorage() to skip on foreign tables. This allows us to emit
> ALTER TABLE SET STORAGE against ordinary tables in upper level of
> inheritance tree, but it have effect on only ordinary tables in the
> tree.
>
> This also allows direct ALTER FOREIGN TABLE SET STORAGE against
> foreign table but the command is silently ignored. SET STORAGE
> support for foreign tables is not documented because it may confuse
> users.
>
> With attached patch, SET STORAGE against wrong relations produces
> message like this. Is it confusing to mention foreign table here?
>
> ERROR: "pgbench1_accounts_pkey" is not a table, materialized view, or
> foreign table
ITSM that would be confusing to users. So, I've modified the patch so
that we continue to disallow SET STORAGE on a foreign table *in the same
manner as before*, but, as your patch does, allow it on an inheritance
hierarchy that contains foreign tables, with the semantics that we
quietly ignore the foreign tables and apply the operation to the plain
tables, by modifying the ALTER TABLE simple recursion mechanism.
Attached is the updated version of the patch.
I'll continue the patch review a bit more, though the patch looks good
generally to me except for the abobe issue and the way that the ANALYZE
command works. For the latter, if there are no objections, I'll merge
the ANALYZE patch [1] with your patch.
Thanks,
[1] http://www.postgresql.org/message-id/52EB10AC.4070307@lab.ntt.co.jp
Best regards,
Etsuro Fujita
*** 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.
</para>
+ <note>
+ <para>
+ 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
+ <xref linkend="ddl-inherit">) to off-load to multiple servers.
+ </para>
+ </note>
+
<sect2 id="ddl-constraints-check-constraints">
<title>Check Constraints</title>
***************
*** 2021,2028 **** CREATE TABLE capitals (
</para>
<para>
! In <productname>PostgreSQL</productname>, 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,
--- 2032,2039 ----
</para>
<para>
! In <productname>PostgreSQL</productname>, 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/ref/alter_foreign_table.sgml
--- b/doc/src/sgml/ref/alter_foreign_table.sgml
***************
*** 42,47 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 42,49 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 178,183 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 180,205 ----
</varlistentry>
<varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
<listitem>
<para>
***************
*** 306,311 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 328,342 ----
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">parent_name</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
*** a/doc/src/sgml/ref/create_foreign_table.sgml
--- b/doc/src/sgml/ref/create_foreign_table.sgml
***************
*** 22,27 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 22,28 ----
<replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 160,176 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ The name of an existing table or foreign table from which the new foreign
+ table automatically inherits all columns, see
+ <xref linkend="ddl-inherit"> for details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 1496,1501 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1496,1508 ----
/* We already got the needed lock */
childrel = heap_open(childOID, NoLock);
+ /* Ignore foreignt tables */
+ if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ heap_close(childrel, AccessShareLock);
+ continue;
+ }
+
/* Ignore if temp table of another backend */
if (RELATION_IS_OTHER_TEMP(childrel))
{
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 310,316 **** 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,
--- 310,317 ----
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,
***************
*** 465,474 **** 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,
--- 466,490 ----
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
! /*
! * Shouldn't this have been checked in parser?
! */
! if (relkind == RELKIND_FOREIGN_TABLE)
! {
! ListCell *lc;
! foreach(lc, stmt->constraints)
! {
! NewConstraint *nc = lfirst(lc);
!
! if (nc->contype != CONSTR_CHECK &&
! nc->contype != CONSTR_DEFAULT &&
! nc->contype != CONSTR_NULL &&
! nc->contype != CONSTR_NOTNULL)
! ereport(ERROR,
! (errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("only check constraints are supported on foreign tables")));
! }
! }
/*
* Look up the namespace in which we are supposed to create the relation,
***************
*** 3014,3037 **** 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;
--- 3030,3053 ----
* rules.
*/
ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
! 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, 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, 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, true, lockmode);
/* Performs own permission checks */
ATPrepSetStatistics(rel, cmd->name, cmd->def, lockmode);
pass = AT_PASS_MISC;
***************
*** 3044,3050 **** 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;
--- 3060,3066 ----
break;
case AT_SetStorage: /* ALTER COLUMN SET STORAGE */
ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
! ATSimpleRecursion(wqueue, rel, cmd, recurse, false, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
***************
*** 3062,3068 **** 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)
--- 3078,3084 ----
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)
***************
*** 3076,3082 **** 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)
--- 3092,3098 ----
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)
***************
*** 3144,3156 **** 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 */
--- 3160,3178 ----
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 recurse */
+ /* 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 */
***************
*** 3179,3185 **** 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);
--- 3201,3206 ----
***************
*** 4120,4126 **** 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
--- 4141,4148 ----
*/
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
***************
*** 4148,4155 **** 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);
}
}
--- 4170,4181 ----
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);
}
}
***************
*** 4439,4445 **** 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);
--- 4465,4471 ----
/* 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);
***************
*** 5335,5341 **** 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
--- 5361,5367 ----
/* 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
***************
*** 5726,5732 **** 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
--- 5752,5758 ----
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/*
* Call AddRelationNewConstraints to do the work, making sure it works on
***************
*** 7215,7221 **** 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);
--- 7241,7247 ----
/* 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);
***************
*** 7550,7556 **** 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,
--- 7576,7582 ----
* alter would put them out of step.
*/
if (recurse)
! ATSimpleRecursion(wqueue, rel, cmd, recurse, true, lockmode);
else if (!recursing &&
find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
ereport(ERROR,
*** 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/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 4207,4238 **** 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;
}
;
--- 4207,4238 ----
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 ----
***************
*** 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,608 ----
static void
transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
{
! if (cxt->isforeign && constraint->contype != CONSTR_CHECK)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("only check constraints are supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
*** 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 '@');
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/plain] foreign_inherit-v2.1.patch (26.4K, ../../52F4D206.7010704@lab.ntt.co.jp/2-foreign_inherit-v2.1.patch)
download | inline diff:
*** 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.
</para>
+ <note>
+ <para>
+ 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
+ <xref linkend="ddl-inherit">) to off-load to multiple servers.
+ </para>
+ </note>
+
<sect2 id="ddl-constraints-check-constraints">
<title>Check Constraints</title>
***************
*** 2021,2028 **** CREATE TABLE capitals (
</para>
<para>
! In <productname>PostgreSQL</productname>, 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,
--- 2032,2039 ----
</para>
<para>
! In <productname>PostgreSQL</productname>, 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/ref/alter_foreign_table.sgml
--- b/doc/src/sgml/ref/alter_foreign_table.sgml
***************
*** 42,47 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 42,49 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 178,183 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 180,205 ----
</varlistentry>
<varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
<listitem>
<para>
***************
*** 306,311 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 328,342 ----
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">parent_name</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
*** a/doc/src/sgml/ref/create_foreign_table.sgml
--- b/doc/src/sgml/ref/create_foreign_table.sgml
***************
*** 22,27 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 22,28 ----
<replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 160,176 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ The name of an existing table or foreign table from which the new foreign
+ table automatically inherits all columns, see
+ <xref linkend="ddl-inherit"> for details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 1496,1501 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1496,1508 ----
/* We already got the needed lock */
childrel = heap_open(childOID, NoLock);
+ /* Ignore foreignt tables */
+ if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ heap_close(childrel, AccessShareLock);
+ continue;
+ }
+
/* Ignore if temp table of another backend */
if (RELATION_IS_OTHER_TEMP(childrel))
{
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 310,316 **** 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,
--- 310,317 ----
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,
***************
*** 465,474 **** 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,
--- 466,490 ----
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
! /*
! * Shouldn't this have been checked in parser?
! */
! if (relkind == RELKIND_FOREIGN_TABLE)
! {
! ListCell *lc;
! foreach(lc, stmt->constraints)
! {
! NewConstraint *nc = lfirst(lc);
!
! if (nc->contype != CONSTR_CHECK &&
! nc->contype != CONSTR_DEFAULT &&
! nc->contype != CONSTR_NULL &&
! nc->contype != CONSTR_NOTNULL)
! ereport(ERROR,
! (errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("only check constraints are supported on foreign tables")));
! }
! }
/*
* Look up the namespace in which we are supposed to create the relation,
***************
*** 3014,3037 **** 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;
--- 3030,3053 ----
* rules.
*/
ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
! 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, 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, 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, true, lockmode);
/* Performs own permission checks */
ATPrepSetStatistics(rel, cmd->name, cmd->def, lockmode);
pass = AT_PASS_MISC;
***************
*** 3044,3050 **** 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;
--- 3060,3066 ----
break;
case AT_SetStorage: /* ALTER COLUMN SET STORAGE */
ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
! ATSimpleRecursion(wqueue, rel, cmd, recurse, false, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
***************
*** 3062,3068 **** 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)
--- 3078,3084 ----
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)
***************
*** 3076,3082 **** 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)
--- 3092,3098 ----
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)
***************
*** 3144,3156 **** 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 */
--- 3160,3178 ----
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 recurse */
+ /* 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 */
***************
*** 3179,3185 **** 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);
--- 3201,3206 ----
***************
*** 4120,4126 **** 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
--- 4141,4148 ----
*/
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
***************
*** 4148,4155 **** 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);
}
}
--- 4170,4181 ----
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);
}
}
***************
*** 4439,4445 **** 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);
--- 4465,4471 ----
/* 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);
***************
*** 5335,5341 **** 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
--- 5361,5367 ----
/* 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
***************
*** 5726,5732 **** 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
--- 5752,5758 ----
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/*
* Call AddRelationNewConstraints to do the work, making sure it works on
***************
*** 7215,7221 **** 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);
--- 7241,7247 ----
/* 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);
***************
*** 7550,7556 **** 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,
--- 7576,7582 ----
* alter would put them out of step.
*/
if (recurse)
! ATSimpleRecursion(wqueue, rel, cmd, recurse, true, lockmode);
else if (!recursing &&
find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
ereport(ERROR,
*** 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/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 4207,4238 **** 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;
}
;
--- 4207,4238 ----
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 ----
***************
*** 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,608 ----
static void
transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
{
! if (cxt->isforeign && constraint->contype != CONSTR_CHECK)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("only check constraints are supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
*** 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 '@');
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-02-10 12:00 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 2 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-02-10 12:00 UTC (permalink / raw)
To: Shigeru Hanada <shigeru.hanada@gmail.com>; +Cc: pgsql-hackers
(2014/02/07 21:31), Etsuro Fujita wrote:
> So, I've modified the patch so
> that we continue to disallow SET STORAGE on a foreign table *in the same
> manner as before*, but, as your patch does, allow it on an inheritance
> hierarchy that contains foreign tables, with the semantics that we
> quietly ignore the foreign tables and apply the operation to the plain
> tables, by modifying the ALTER TABLE simple recursion mechanism.
> Attached is the updated version of the patch.
>
> I'll continue the patch review a bit more, though the patch looks good
> generally to me except for the abobe issue and the way that the ANALYZE
> command works.
While reviwing the patch, I've found some issues on interactions with
other commands, other than the SET STORAGE command.
* ADD table_constraint NOT VALID: the patch allows ADD table_constraint
*NOT VALID* to be set on a foreign table as well as an inheritance
hierarchy that contains foreign tables. But I think it would be better
to disallow ADD table_constraint *NOT VALID* on a foreign table, but
allow it on an inheritance hierarchy that contains foreign tables, with
the semantics that we apply the operation to the plain tables and apply
the transformed operation *ADD table_constraint* to the foreign tables.
* VALIDATE CONSTRAINT constraint_name: though the patch disallow the
direct operation on foreign tables, it allows the operation on an
inheritance hierarchy that contains foreign tables, and the operation
fails if there is at least one foreign table that has at least one NOT
VALID constraint. I think it would be better to modify the patch so
that we allow the operation on an inheritance hierarchy that contains
foreign tables, with the semantics that we quietly ignore the foreign
tables and apply the operation on the plain tables just like the
semantics of SET STORAGE. (Note that the foreign tables wouldn't have
NOT VALID constraints by diallowing ADD table_constraint *NOT VALID* on
foreign tables as mentioned above.)
* SET WITH OIDS: though the patch disallow the direct operation on
foreign tables, it allows the operation on an inheritance hierarchy that
contains foreign tables, and that operation is successfully done on
foreign tables. I think it would be better to modify the patch so that
we allow it on an inheritance hierarchy that contains foreign tables,
with the semantics that we quietly ignore the foreign tables and apply
the operation to the plain tables just like the semantics of SET STORAGE.
Comments are wellcome!
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-02-14 11:00 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-02-14 11:00 UTC (permalink / raw)
To: Shigeru Hanada <shigeru.hanada@gmail.com>; +Cc: pgsql-hackers
(2014/02/10 21:00), Etsuro Fujita wrote:
> (2014/02/07 21:31), Etsuro Fujita wrote:
>> So, I've modified the patch so
>> that we continue to disallow SET STORAGE on a foreign table *in the same
>> manner as before*, but, as your patch does, allow it on an inheritance
>> hierarchy that contains foreign tables, with the semantics that we
>> quietly ignore the foreign tables and apply the operation to the plain
>> tables, by modifying the ALTER TABLE simple recursion mechanism.
> While reviwing the patch, I've found some issues on interactions with
> other commands, other than the SET STORAGE command.
> * ADD table_constraint NOT VALID: the patch allows ADD table_constraint
> *NOT VALID* to be set on a foreign table as well as an inheritance
> hierarchy that contains foreign tables. But I think it would be better
> to disallow ADD table_constraint *NOT VALID* on a foreign table, but
> allow it on an inheritance hierarchy that contains foreign tables, with
> the semantics that we apply the operation to the plain tables and apply
> the transformed operation *ADD table_constraint* to the foreign tables.
Done.
> * VALIDATE CONSTRAINT constraint_name:
I've modified the patch so that though we continue to disallow the
operation on a foreign table, we allow it on an inheritance hierarchy
that contains foreign tables. In that case, the to-be-validated
constraints on the plain tables are validated by the operation, as
before, and the constraints on the foreign tables are ignored. Note
that the constraints on the foreign tables are not NOT VALID due to the
spec mentioned above.
> * SET WITH OIDS: though the patch disallow the direct operation on
> foreign tables, it allows the operation on an inheritance hierarchy that
> contains foreign tables, and that operation is successfully done on
> foreign tables. I think it would be better to modify the patch so that
> we allow it on an inheritance hierarchy that contains foreign tables,
> with the semantics that we quietly ignore the foreign tables and apply
> the operation to the plain tables just like the semantics of SET STORAGE.
I noticed this breaks inheritance hierarchy. To avoid this, I've
modified the patch to disallow SET WITH OIDS on an inheritance hierarchy
that contains at least one foreign table.
The other changes:
- if (stmt->constraints != NIL && relkind == RELKIND_FOREIGN_TABLE)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("constraints are not supported
on foreign tables")));
+/*
+ * Shouldn't this have been checked in parser?
+ */
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ ListCell *lc;
+ foreach(lc, stmt->constraints)
+ {
+ NewConstraint *nc = lfirst(lc);
+
+ if (nc->contype != CONSTR_CHECK &&
+ nc->contype != CONSTR_DEFAULT &&
+ nc->contype != CONSTR_NULL &&
+ nc->contype != CONSTR_NOTNULL)
+ ereport(ERROR,
+
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("only check
constraints are supported on foreign tables")));
+ }
+ }
ISTM we wouldn't need the above check in DefineRelation(), so I've
removed the check from the patch. And I've modified the vague error
messages in parse_utilcmd.c.
There seems to be no objections, I've merged the ANALYZE patch [1] with
your patch. I noticed that the patch in [1] isn't efficient. (To
ANALYZE one inheritance tree that contains foreign tables, the patch in
[1] calls the AnalyzeForeignTable() routine two times for each such
foreign table.) So, the updated version has been merged that calls the
routine only once for each such foreign table.
Todo:
ISTM the documentation would need to be updated further.
Thanks,
[1] http://www.postgresql.org/message-id/52EB10AC.4070307@lab.ntt.co.jp
Best regards,
Etsuro Fujita
*** 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.
</para>
+ <note>
+ <para>
+ 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
+ <xref linkend="ddl-inherit">) to off-load to multiple servers.
+ </para>
+ </note>
+
<sect2 id="ddl-constraints-check-constraints">
<title>Check Constraints</title>
***************
*** 2021,2028 **** CREATE TABLE capitals (
</para>
<para>
! In <productname>PostgreSQL</productname>, 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,
--- 2032,2039 ----
</para>
<para>
! In <productname>PostgreSQL</productname>, 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/ref/alter_foreign_table.sgml
--- b/doc/src/sgml/ref/alter_foreign_table.sgml
***************
*** 42,47 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 42,49 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 178,183 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 180,205 ----
</varlistentry>
<varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be a plain table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
<listitem>
<para>
***************
*** 306,311 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 328,343 ----
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">parent_name</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be a plain table.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
*** a/doc/src/sgml/ref/create_foreign_table.sgml
--- b/doc/src/sgml/ref/create_foreign_table.sgml
***************
*** 22,27 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 22,28 ----
<replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 160,177 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for the
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
*** a/src/backend/catalog/pg_inherits.c
--- b/src/backend/catalog/pg_inherits.c
***************
*** 256,261 **** has_subclass(Oid relationId)
--- 256,303 ----
/*
+ * 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.
*/
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 114,120 **** 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;
--- 114,121 ----
* analyze_rel() -- analyze one relation
*/
void
! analyze_rel(Oid relid, VacuumStmt *vacstmt, VacuumMode vacmode,
! BufferAccessStrategy bstrategy)
{
Relation onerel;
int elevel;
***************
*** 269,275 **** analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
/*
* If there are child tables, do recursive ANALYZE.
*/
! if (onerel->rd_rel->relhassubclass)
do_analyze_rel(onerel, vacstmt, acquirefunc, relpages, true, elevel);
/*
--- 270,278 ----
/*
* If there are child tables, do recursive ANALYZE.
*/
! if (onerel->rd_rel->relhassubclass &&
! (vacmode == VAC_MODE_SINGLE ||
! !contains_foreign(RelationGetRelid(onerel), AccessShareLock)))
do_analyze_rel(onerel, vacstmt, acquirefunc, relpages, true, elevel);
/*
***************
*** 1451,1462 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1454,1468 ----
{
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
***************
*** 1485,1490 **** 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;
***************
*** 1506,1517 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
}
rels[nrels] = childrel;
! relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
totalblocks += relblocks[nrels];
nrels++;
}
/*
* 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
--- 1514,1572 ----
}
rels[nrels] = 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
***************
*** 1524,1529 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1579,1585 ----
{
Relation childrel = rels[i];
double childblocks = relblocks[i];
+ AcquireSampleRowsFunc childacquirefunc = acquirefunc[i];
if (childblocks > 0)
{
***************
*** 1539,1550 **** 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 &&
--- 1595,1606 ----
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
***************
*** 310,316 **** 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,
--- 310,317 ----
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,
***************
*** 465,474 **** 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,
--- 466,471 ----
***************
*** 3014,3037 **** 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;
--- 3011,3038 ----
* rules.
*/
ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
! /* 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);
! /* 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);
! /* 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 */
! /* 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;
***************
*** 3044,3050 **** 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;
--- 3045,3052 ----
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;
***************
*** 3062,3068 **** 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)
--- 3064,3070 ----
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)
***************
*** 3076,3082 **** 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)
--- 3078,3084 ----
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)
***************
*** 3144,3156 **** 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 */
--- 3146,3164 ----
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 */
***************
*** 3179,3185 **** 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);
--- 3187,3192 ----
***************
*** 4120,4126 **** 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
--- 4127,4134 ----
*/
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
***************
*** 4148,4155 **** 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);
}
}
--- 4156,4167 ----
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);
}
}
***************
*** 4439,4445 **** 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);
--- 4451,4457 ----
/* 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);
***************
*** 4854,4860 **** ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
--- 4866,4884 ----
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;
+ }
}
/*
***************
*** 5335,5341 **** 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
--- 5359,5365 ----
/* 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
***************
*** 5726,5732 **** 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
--- 5750,5763 ----
/* 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 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
***************
*** 5737,5745 **** 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 */
--- 5768,5784 ----
* 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(constr),
recursing, /* allow_merge */
!recursing, /* is_local */
is_readd); /* is_internal */
***************
*** 7215,7221 **** 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);
--- 7254,7260 ----
/* 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);
***************
*** 7550,7556 **** 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,
--- 7589,7598 ----
* alter would put them out of step.
*/
if (recurse)
! {
! /* 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,
*** a/src/backend/commands/vacuum.c
--- b/src/backend/commands/vacuum.c
***************
*** 96,101 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
--- 96,102 ----
BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel)
{
const char *stmttype;
+ VacuumMode vacmode;
volatile bool in_outer_xact,
use_own_xacts;
List *relations;
***************
*** 144,149 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
--- 145,164 ----
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.
*/
***************
*** 246,252 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
PushActiveSnapshot(GetTransactionSnapshot());
}
! analyze_rel(relid, vacstmt, vac_strategy);
if (use_own_xacts)
{
--- 261,267 ----
PushActiveSnapshot(GetTransactionSnapshot());
}
! analyze_rel(relid, vacstmt, vacmode, 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/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 4207,4238 **** 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;
}
;
--- 4207,4238 ----
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/catalog/pg_inherits_fn.h
--- b/src/include/catalog/pg_inherits_fn.h
***************
*** 21,26 **** extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode);
--- 21,27 ----
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 */
*** a/src/include/commands/vacuum.h
--- b/src/include/commands/vacuum.h
***************
*** 138,143 **** extern int vacuum_freeze_min_age;
--- 138,152 ----
extern int vacuum_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);
***************
*** 170,176 **** 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);
--- 179,185 ----
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/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 '@');
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/plain] foreign_inherit-v3.patch (38.7K, ../../52FDF74A.5070203@lab.ntt.co.jp/2-foreign_inherit-v3.patch)
download | inline diff:
*** 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.
</para>
+ <note>
+ <para>
+ 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
+ <xref linkend="ddl-inherit">) to off-load to multiple servers.
+ </para>
+ </note>
+
<sect2 id="ddl-constraints-check-constraints">
<title>Check Constraints</title>
***************
*** 2021,2028 **** CREATE TABLE capitals (
</para>
<para>
! In <productname>PostgreSQL</productname>, 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,
--- 2032,2039 ----
</para>
<para>
! In <productname>PostgreSQL</productname>, 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/ref/alter_foreign_table.sgml
--- b/doc/src/sgml/ref/alter_foreign_table.sgml
***************
*** 42,47 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 42,49 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 178,183 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 180,205 ----
</varlistentry>
<varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be a plain table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
<listitem>
<para>
***************
*** 306,311 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 328,343 ----
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">parent_name</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be a plain table.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
*** a/doc/src/sgml/ref/create_foreign_table.sgml
--- b/doc/src/sgml/ref/create_foreign_table.sgml
***************
*** 22,27 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 22,28 ----
<replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 160,177 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for the
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
*** a/src/backend/catalog/pg_inherits.c
--- b/src/backend/catalog/pg_inherits.c
***************
*** 256,261 **** has_subclass(Oid relationId)
--- 256,303 ----
/*
+ * 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.
*/
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 114,120 **** 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;
--- 114,121 ----
* analyze_rel() -- analyze one relation
*/
void
! analyze_rel(Oid relid, VacuumStmt *vacstmt, VacuumMode vacmode,
! BufferAccessStrategy bstrategy)
{
Relation onerel;
int elevel;
***************
*** 269,275 **** analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
/*
* If there are child tables, do recursive ANALYZE.
*/
! if (onerel->rd_rel->relhassubclass)
do_analyze_rel(onerel, vacstmt, acquirefunc, relpages, true, elevel);
/*
--- 270,278 ----
/*
* If there are child tables, do recursive ANALYZE.
*/
! if (onerel->rd_rel->relhassubclass &&
! (vacmode == VAC_MODE_SINGLE ||
! !contains_foreign(RelationGetRelid(onerel), AccessShareLock)))
do_analyze_rel(onerel, vacstmt, acquirefunc, relpages, true, elevel);
/*
***************
*** 1451,1462 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1454,1468 ----
{
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
***************
*** 1485,1490 **** 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;
***************
*** 1506,1517 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
}
rels[nrels] = childrel;
! relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
totalblocks += relblocks[nrels];
nrels++;
}
/*
* 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
--- 1514,1572 ----
}
rels[nrels] = 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
***************
*** 1524,1529 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1579,1585 ----
{
Relation childrel = rels[i];
double childblocks = relblocks[i];
+ AcquireSampleRowsFunc childacquirefunc = acquirefunc[i];
if (childblocks > 0)
{
***************
*** 1539,1550 **** 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 &&
--- 1595,1606 ----
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
***************
*** 310,316 **** 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,
--- 310,317 ----
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,
***************
*** 465,474 **** 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,
--- 466,471 ----
***************
*** 3014,3037 **** 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;
--- 3011,3038 ----
* rules.
*/
ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
! /* 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);
! /* 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);
! /* 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 */
! /* 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;
***************
*** 3044,3050 **** 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;
--- 3045,3052 ----
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;
***************
*** 3062,3068 **** 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)
--- 3064,3070 ----
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)
***************
*** 3076,3082 **** 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)
--- 3078,3084 ----
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)
***************
*** 3144,3156 **** 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 */
--- 3146,3164 ----
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 */
***************
*** 3179,3185 **** 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);
--- 3187,3192 ----
***************
*** 4120,4126 **** 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
--- 4127,4134 ----
*/
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
***************
*** 4148,4155 **** 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);
}
}
--- 4156,4167 ----
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);
}
}
***************
*** 4439,4445 **** 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);
--- 4451,4457 ----
/* 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);
***************
*** 4854,4860 **** ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
--- 4866,4884 ----
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;
+ }
}
/*
***************
*** 5335,5341 **** 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
--- 5359,5365 ----
/* 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
***************
*** 5726,5732 **** 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
--- 5750,5763 ----
/* 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 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
***************
*** 5737,5745 **** 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 */
--- 5768,5784 ----
* 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(constr),
recursing, /* allow_merge */
!recursing, /* is_local */
is_readd); /* is_internal */
***************
*** 7215,7221 **** 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);
--- 7254,7260 ----
/* 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);
***************
*** 7550,7556 **** 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,
--- 7589,7598 ----
* alter would put them out of step.
*/
if (recurse)
! {
! /* 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,
*** a/src/backend/commands/vacuum.c
--- b/src/backend/commands/vacuum.c
***************
*** 96,101 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
--- 96,102 ----
BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel)
{
const char *stmttype;
+ VacuumMode vacmode;
volatile bool in_outer_xact,
use_own_xacts;
List *relations;
***************
*** 144,149 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
--- 145,164 ----
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.
*/
***************
*** 246,252 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
PushActiveSnapshot(GetTransactionSnapshot());
}
! analyze_rel(relid, vacstmt, vac_strategy);
if (use_own_xacts)
{
--- 261,267 ----
PushActiveSnapshot(GetTransactionSnapshot());
}
! analyze_rel(relid, vacstmt, vacmode, 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/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 4207,4238 **** 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;
}
;
--- 4207,4238 ----
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/catalog/pg_inherits_fn.h
--- b/src/include/catalog/pg_inherits_fn.h
***************
*** 21,26 **** extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode);
--- 21,27 ----
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 */
*** a/src/include/commands/vacuum.h
--- b/src/include/commands/vacuum.h
***************
*** 138,143 **** extern int vacuum_freeze_min_age;
--- 138,152 ----
extern int vacuum_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);
***************
*** 170,176 **** 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);
--- 179,185 ----
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/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 '@');
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-02-18 05:47 Shigeru Hanada <shigeru.hanada@gmail.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 2 replies; 179+ messages in thread
From: Shigeru Hanada @ 2014-02-18 05:47 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: pgsql-hackers
Hi Fujita-san,
Thanks for the reviewing.
2014-02-10 21:00 GMT+09:00 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>:
> (2014/02/07 21:31), Etsuro Fujita wrote:
>> So, I've modified the patch so
>> that we continue to disallow SET STORAGE on a foreign table *in the same
>> manner as before*, but, as your patch does, allow it on an inheritance
>> hierarchy that contains foreign tables, with the semantics that we
>> quietly ignore the foreign tables and apply the operation to the plain
>> tables, by modifying the ALTER TABLE simple recursion mechanism.
>> Attached is the updated version of the patch.
I'm not sure that allowing ALTER TABLE against parent table affects
descendants even some of them are foreign table. I think the rule
should be simple enough to understand for users, of course it should
be also consistent and have backward compatibility.
If foreign table can be modified through inheritance tree, this kind
of change can be done.
1) create foreign table as a child of a ordinary table
2) run ALTER TABLE parent, the foreign table is also changed
3) remove foreign table from the inheritance tree by ALTER TABLE child
NO INHERIT parent
4) here we can't do same thing as 2), because it is not a child anymore.
So IMO we should determine which ALTER TABLE features are allowed to
foreign tables, and allow them regardless of the recursivity.
Comments?
--
Shigeru HANADA
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-02-18 10:24 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Shigeru Hanada <shigeru.hanada@gmail.com>
1 sibling, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-02-18 10:24 UTC (permalink / raw)
To: 'Shigeru Hanada' <shigeru.hanada@gmail.com>; +Cc: pgsql-hackers
> From: Shigeru Hanada [mailto:shigeru.hanada@gmail.com]
> 2014-02-10 21:00 GMT+09:00 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>:
> > (2014/02/07 21:31), Etsuro Fujita wrote:
> >> So, I've modified the patch so
> >> that we continue to disallow SET STORAGE on a foreign table *in the
> >> same manner as before*, but, as your patch does, allow it on an
> >> inheritance hierarchy that contains foreign tables, with the
> >> semantics that we quietly ignore the foreign tables and apply the
> >> operation to the plain tables, by modifying the ALTER TABLE simple
> recursion mechanism.
> >> Attached is the updated version of the patch.
>
> I'm not sure that allowing ALTER TABLE against parent table affects
> descendants even some of them are foreign table. I think the rule should
> be simple enough to understand for users, of course it should be also
> consistent and have backward compatibility.
Yeah, the understandability is important. But I think the flexibility is also important. In other words, I think it is a bit too inflexible that we disallow e.g., SET STORAGE to be set on an inheritance tree that contains foreign table(s) because we disallow SET STORAGE to be set on foreign tables directly.
> If foreign table can be modified through inheritance tree, this kind of
> change can be done.
>
> 1) create foreign table as a child of a ordinary table
> 2) run ALTER TABLE parent, the foreign table is also changed
> 3) remove foreign table from the inheritance tree by ALTER TABLE child NO
> INHERIT parent
> 4) here we can't do same thing as 2), because it is not a child anymore.
>
> So IMO we should determine which ALTER TABLE features are allowed to foreign
> tables, and allow them regardless of the recursivity.
What I think should be newly allowed to be set on foreign tables is
* ADD table_constraint
* DROP CONSTRAINT
* [NO] INHERIT parent_table
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-02-18 10:29 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: Shigeru Hanada <shigeru.hanada@gmail.com>
1 sibling, 2 replies; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2014-02-18 10:29 UTC (permalink / raw)
To: shigeru.hanada@gmail.com; +Cc: fujita.etsuro@lab.ntt.co.jp; pgsql-hackers
Hello,
> 2014-02-10 21:00 GMT+09:00 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>:
> > (2014/02/07 21:31), Etsuro Fujita wrote:
> >> So, I've modified the patch so
> >> that we continue to disallow SET STORAGE on a foreign table *in the same
> >> manner as before*, but, as your patch does, allow it on an inheritance
> >> hierarchy that contains foreign tables, with the semantics that we
> >> quietly ignore the foreign tables and apply the operation to the plain
> >> tables, by modifying the ALTER TABLE simple recursion mechanism.
> >> Attached is the updated version of the patch.
>
> I'm not sure that allowing ALTER TABLE against parent table affects
> descendants even some of them are foreign table. I think the rule
> should be simple enough to understand for users, of course it should
> be also consistent and have backward compatibility.
Could you guess any use cases in which we are happy with ALTER
TABLE's inheritance tree walking? IMHO, ALTER FOREIGN TABLE
always comes with some changes of the data source so implicitly
invoking of such commands should be defaultly turned off. If the
ALTER'ing the whole familiy is deadfully useful for certain
cases, it might be explicitly turned on by some syntax added to
CREATE FOREIGN TABLE or ALTER TABLE.
It would looks like following,
CREATE FOREIGN TABLE ft1 () INHERITS (pt1 ALLOW_INHERITED_ALTER, pt2);
ALTER TABLE INCLUDE FOREIGN CHILDREN parent ADD COLUMN add1 integer;
These looks quite bad :-( but also seem quite better than
accidentially ALTER'ing foreign children.
If foreign tables were allowed to ALTER'ed with 'ALTER TABLE',
some reconstruction between 'ALTER TABLE' and 'ALTER FOREIGN
TABLE' would be needed.
> If foreign table can be modified through inheritance tree, this kind
> of change can be done.
>
> 1) create foreign table as a child of a ordinary table
> 2) run ALTER TABLE parent, the foreign table is also changed
> 3) remove foreign table from the inheritance tree by ALTER TABLE child
> NO INHERIT parent
> 4) here we can't do same thing as 2), because it is not a child anymore.
>
> So IMO we should determine which ALTER TABLE features are allowed to
> foreign tables, and allow them regardless of the recursivity.
>
> Comments?
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-02-18 14:49 Tom Lane <tgl@sss.pgh.pa.us>
parent: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: Tom Lane @ 2014-02-18 14:49 UTC (permalink / raw)
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: shigeru.hanada@gmail.com; fujita.etsuro@lab.ntt.co.jp; pgsql-hackers
Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> writes:
> Could you guess any use cases in which we are happy with ALTER
> TABLE's inheritance tree walking? IMHO, ALTER FOREIGN TABLE
> always comes with some changes of the data source so implicitly
> invoking of such commands should be defaultly turned off. If the
> ALTER'ing the whole familiy is deadfully useful for certain
> cases, it might be explicitly turned on by some syntax added to
> CREATE FOREIGN TABLE or ALTER TABLE.
> It would looks like following,
> CREATE FOREIGN TABLE ft1 () INHERITS (pt1 ALLOW_INHERITED_ALTER, pt2);
> ALTER TABLE INCLUDE FOREIGN CHILDREN parent ADD COLUMN add1 integer;
Ugh. This adds a lot of logical complication without much real use,
IMO. The problems that have been discussed in this thread are that
certain types of ALTER are sensible to apply to foreign tables and
others are not --- so how does a one-size-fits-all switch help that?
Also, there are some types of ALTER for which recursion to children
*must* occur or things become inconsistent --- ADD COLUMN itself is
an example, and ADD CONSTRAINT is another. It would not be acceptable
for an ALLOW_INHERITED_ALTER switch to suppress that, but if the
switch is leaky then it's even less consistent and harder to explain.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-02-19 00:16 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 0 replies; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2014-02-19 00:16 UTC (permalink / raw)
To: tgl@sss.pgh.pa.us; +Cc: shigeru.hanada@gmail.com; fujita.etsuro@lab.ntt.co.jp; pgsql-hackers
Hello,
At Tue, 18 Feb 2014 09:49:23 -0500, Tom Lane wrote
> Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp> writes:
> > Could you guess any use cases in which we are happy with ALTER
> > TABLE's inheritance tree walking? IMHO, ALTER FOREIGN TABLE
> > always comes with some changes of the data source so implicitly
> > invoking of such commands should be defaultly turned off. If the
> > ALTER'ing the whole familiy is deadfully useful for certain
> > cases, it might be explicitly turned on by some syntax added to
> > CREATE FOREIGN TABLE or ALTER TABLE.
>
> > It would looks like following,
>
> > CREATE FOREIGN TABLE ft1 () INHERITS (pt1 ALLOW_INHERITED_ALTER, pt2);
>
> > ALTER TABLE INCLUDE FOREIGN CHILDREN parent ADD COLUMN add1 integer;
>
> Ugh. This adds a lot of logical complication without much real use,
> IMO.
Yeah! That's exactry the words I wanted for the expamples. Sorry
for bothering you. I also don't see any use case driving us to
overcome the extra complexity.
> The problems that have been discussed in this thread are that
> certain types of ALTER are sensible to apply to foreign tables and
> others are not --- so how does a one-size-fits-all switch help that?
None, I think. I intended only to display what this ALTER's
inheritance walkthrough brings in.
> Also, there are some types of ALTER for which recursion to children
> *must* occur or things become inconsistent --- ADD COLUMN itself is
> an example, and ADD CONSTRAINT is another. It would not be acceptable
> for an ALLOW_INHERITED_ALTER switch to suppress that, but if the
> switch is leaky then it's even less consistent and harder to explain.
Exactly. It is the problem came with allowing to implicit ALTER
on foreign children, Shigeru's last message seems to referred to.
At Tue, 18 Feb 2014 14:47:51 +0900, Shigeru Hanada wrote
> I'm not sure that allowing ALTER TABLE against parent table affects
> descendants even some of them are foreign table.
Avoiding misunderstandings, my opinion on whether ALTER may
applie to foreign chidlren is 'no'.
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-02-19 03:12 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2014-02-19 03:12 UTC (permalink / raw)
To: fujita.etsuro@lab.ntt.co.jp; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
Hello,
At Tue, 18 Feb 2014 19:24:50 +0900, "Etsuro Fujita" wrote
> > From: Shigeru Hanada [mailto:shigeru.hanada@gmail.com]
> > 2014-02-10 21:00 GMT+09:00 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>:
> > > (2014/02/07 21:31), Etsuro Fujita wrote:
> > >> So, I've modified the patch so
> > >> that we continue to disallow SET STORAGE on a foreign table *in the
> > >> same manner as before*, but, as your patch does, allow it on an
> > >> inheritance hierarchy that contains foreign tables, with the
> > >> semantics that we quietly ignore the foreign tables and apply the
> > >> operation to the plain tables, by modifying the ALTER TABLE simple
> > recursion mechanism.
> > >> Attached is the updated version of the patch.
> >
> > I'm not sure that allowing ALTER TABLE against parent table affects
> > descendants even some of them are foreign table. I think the rule should
> > be simple enough to understand for users, of course it should be also
> > consistent and have backward compatibility.
>
> Yeah, the understandability is important. But I think the
> flexibility is also important. In other words, I think it is a
> bit too inflexible that we disallow e.g., SET STORAGE to be set
> on an inheritance tree that contains foreign table(s) because
> we disallow SET STORAGE to be set on foreign tables directly.
What use case needs such a flexibility precedes the lost behavior
predictivity of ALTER TABLE and/or code "maintenancibility"(more
ordinally words must be...) ? I don't agree with the idea that
ALTER TABLE implicitly affects foreign children for the reason in
the upthread. Also turning on/off feature implemented as special
syntax seems little hope.
If you still want that, I suppose ALTER FOREIGN TABLE is usable
chainging so as to walk through the inheritance tree and affects
only foreign tables along the way. It can naturally affects
foregin tables with no unanticipated behavor.
Thoughts?
> > If foreign table can be modified through inheritance tree, this kind of
> > change can be done.
> >
> > 1) create foreign table as a child of a ordinary table
> > 2) run ALTER TABLE parent, the foreign table is also changed
> > 3) remove foreign table from the inheritance tree by ALTER TABLE child NO
> > INHERIT parent
> > 4) here we can't do same thing as 2), because it is not a child anymore.
> >
> > So IMO we should determine which ALTER TABLE features are allowed to foreign
> > tables, and allow them regardless of the recursivity.
>
> What I think should be newly allowed to be set on foreign tables is
>
> * ADD table_constraint
> * DROP CONSTRAINT
As of 9.3
http://www.postgresql.org/docs/9.3/static/sql-alterforeigntable.html
> 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.
So I belive implicit and automatic application of any constraint
on foreign childs are considerably danger.
> * [NO] INHERIT parent_table
Is this usable for inheritance foreign children? NO INHERIT
removes all foreign children but INHERIT is nop.
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-02-19 07:17 Shigeru Hanada <shigeru.hanada@gmail.com>
parent: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: Shigeru Hanada @ 2014-02-19 07:17 UTC (permalink / raw)
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; pgsql-hackers
Hi Horiguchi-san,
2014-02-18 19:29 GMT+09:00 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>:
> Could you guess any use cases in which we are happy with ALTER
> TABLE's inheritance tree walking? IMHO, ALTER FOREIGN TABLE
> always comes with some changes of the data source so implicitly
> invoking of such commands should be defaultly turned off.
Imagine a case that foreign data source have attributes (A, B, C, D)
but foreign tables and their parent ware defined as (A, B, C). If
user wants to use D as well, ALTER TABLE parent ADD COLUMN D type
would be useful (rather necessary?) to keep consistency.
Changing data type from compatible one (i.e., int to numeric,
varchar(n) to text), adding CHECK/NOT NULL constraint would be also
possible.
--
Shigeru HANADA
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-02-19 10:40 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-02-19 10:40 UTC (permalink / raw)
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
(2014/02/19 12:12), Kyotaro HORIGUCHI wrote:
> At Tue, 18 Feb 2014 19:24:50 +0900, "Etsuro Fujita" wrote
>>> From: Shigeru Hanada [mailto:shigeru.hanada@gmail.com]
>>> I'm not sure that allowing ALTER TABLE against parent table affects
>>> descendants even some of them are foreign table. I think the rule should
>>> be simple enough to understand for users, of course it should be also
>>> consistent and have backward compatibility.
>>
>> Yeah, the understandability is important. But I think the
>> flexibility is also important. In other words, I think it is a
>> bit too inflexible that we disallow e.g., SET STORAGE to be set
>> on an inheritance tree that contains foreign table(s) because
>> we disallow SET STORAGE to be set on foreign tables directly.
>
> What use case needs such a flexibility precedes the lost behavior
> predictivity of ALTER TABLE and/or code "maintenancibility"(more
> ordinally words must be...) ? I don't agree with the idea that
> ALTER TABLE implicitly affects foreign children for the reason in
> the upthread. Also turning on/off feature implemented as special
> syntax seems little hope.
It is just my personal opinion, but I think it would be convenient for
users to alter inheritance trees that contain foreign tables the same
way as inheritance trees that don't contain any foreign tables, without
making user conscious of the inheritance trees contains foreign tables
or not. Imagine we have an inheritance tree that contains only plain
tables and then add a foreign table as a child of the inheritance tree.
Without the flexiblity, we would need to change the way of altering
the structure of the inheritance tree (e.g., ADD CONSTRAINT) to a
totally different one, immediately when adding the foreign table. I
don't think that would be easy to use.
>> What I think should be newly allowed to be set on foreign tables is
>>
>> * ADD table_constraint
>> * DROP CONSTRAINT
>
> As of 9.3
>
> http://www.postgresql.org/docs/9.3/static/sql-alterforeigntable.html
>
>> 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.
>
> So I belive implicit and automatic application of any constraint
> on foreign childs are considerably danger.
We spent a lot of time discussing this issue, and the consensus is that
it's users' fault if there are some tuples on the remote side violating
a given constraint, as mentioned in the documentation.
>> * [NO] INHERIT parent_table
>
> Is this usable for inheritance foreign children? NO INHERIT
> removes all foreign children but INHERIT is nop.
I didn't express clearly. Sorry for that. Let me explain about it.
* ALTER FOREIGN TABLE target_table *INHERIT* parent_table: Add the
target table as a new child of the parent table.
* ALTER FOREIGN TABLE target_table *NO INHERIT* parent_table: Remove the
target table from the list of children of the parent table.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-02-20 06:47 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2014-02-20 06:47 UTC (permalink / raw)
To: fujita.etsuro@lab.ntt.co.jp; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
Hello,
> It is just my personal opinion, but I think it would be convenient for
> users to alter inheritance trees that contain foreign tables the same
> way as inheritance trees that don't contain any foreign tables,
> without making user conscious of the inheritance trees contains
> foreign tables or not. Imagine we have an inheritance tree that
> contains only plain tables and then add a foreign table as a child of
> the inheritance tree. Without the flexiblity, we would need to change
> the way of altering the structure of the inheritance tree (e.g., ADD
> CONSTRAINT) to a totally different one, immediately when adding the
> foreign table. I don't think that would be easy to use.
I personally don't see significant advantages such a
flexibility. Although my concerns here are only two points,
unanticipated application and "maintenancibility". I gave a
consideration on these issues again.
Then, I think it could be enough by giving feedback to operators
for the first issue.
=# ALTER TABLE parent ADD CHECK (tempmin < tempmax),
ALTER tempmin SET NOT NULL,
ALTER tempmin SET DEFAULT 0;
NOTICE: Child foregn table child01 is affected.
NOTICE: Child foregn table child02 is affected
NOTICE: Child foregn table child03 rejected 'alter tempmin set default'
What do you think about this? It looks a bit too loud for me
though...
Then the second issue, however I don't have enough idea of how
ALTER TABLE works, the complexity would be reduced if acceptance
chek for alter "action"s would done on foreign server or data
wrapper side, not on the core of ALTER TABLE. It would also be a
help to output error messages like above. However, (NO)INHERIT
looks a little different..
> > http://www.postgresql.org/docs/9.3/static/sql-alterforeigntable.html
> >
> >> 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.
> >
> > So I belive implicit and automatic application of any constraint
> > on foreign childs are considerably danger.
>
> We spent a lot of time discussing this issue, and the consensus is
> that it's users' fault if there are some tuples on the remote side
> violating a given constraint, as mentioned in the documentation.
I'm worried about not that but the changes and possible
inconsistency would take place behind operators' backs. And this
looks to cause such inconsistencies for me.
> >> * [NO] INHERIT parent_table
> >
> > Is this usable for inheritance foreign children? NO INHERIT
> > removes all foreign children but INHERIT is nop.
>
> I didn't express clearly. Sorry for that. Let me explain about it.
>
> * ALTER FOREIGN TABLE target_table *INHERIT* parent_table: Add the
> * target table as a new child of the parent table.
> * ALTER FOREIGN TABLE target_table *NO INHERIT* parent_table: Remove the
> * target table from the list of children of the parent table.
I got it, thank you. It alone seems no probmen but also doesn't
seem to be a matter of 'ALTER TABLE'. Could you tell me how this
is related to 'ALTER TABLE'?
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-02-20 07:05 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: Shigeru Hanada <shigeru.hanada@gmail.com>
0 siblings, 0 replies; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2014-02-20 07:05 UTC (permalink / raw)
To: shigeru.hanada@gmail.com; +Cc: fujita.etsuro@lab.ntt.co.jp; pgsql-hackers
Hi,
At Wed, 19 Feb 2014 16:17:05 +0900, Shigeru Hanada wrote
> 2014-02-18 19:29 GMT+09:00 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>:
> > Could you guess any use cases in which we are happy with ALTER
> > TABLE's inheritance tree walking? IMHO, ALTER FOREIGN TABLE
> > always comes with some changes of the data source so implicitly
> > invoking of such commands should be defaultly turned off.
>
> Imagine a case that foreign data source have attributes (A, B, C, D)
> but foreign tables and their parent ware defined as (A, B, C). If
> user wants to use D as well, ALTER TABLE parent ADD COLUMN D type
> would be useful (rather necessary?) to keep consistency.
Hmm. I seems to me an issue of mis-configuration at first
step. However, my anxiety is - as in my message just before -
ALTER'ing foreign table definitions without any notice to
operatos and irregular or random logic on check applicability(?)
of ALTER actions.
> Changing data type from compatible one (i.e., int to numeric,
> varchar(n) to text), adding CHECK/NOT NULL constraint would be also
> possible.
I see, thank you. Changing data types are surely valuable but
also seems difficult to check validity:(
Anyway, I gave a second thought on this issue. Please have a look
on that.
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-02-20 10:55 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
0 siblings, 2 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-02-20 10:55 UTC (permalink / raw)
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
(2014/02/20 15:47), Kyotaro HORIGUCHI wrote:
> Although my concerns here are only two points,
> unanticipated application and "maintenancibility". I gave a
> consideration on these issues again.
Sorry, I misunderstood what you mean by "unanticipated application".
> Then, I think it could be enough by giving feedback to operators
> for the first issue.
>
> =# ALTER TABLE parent ADD CHECK (tempmin < tempmax),
> ALTER tempmin SET NOT NULL,
> ALTER tempmin SET DEFAULT 0;
> NOTICE: Child foregn table child01 is affected.
> NOTICE: Child foregn table child02 is affected
> NOTICE: Child foregn table child03 rejected 'alter tempmin set default'
>
> What do you think about this? It looks a bit too loud for me
> though...
I think that's a good idea. What do others think?
> Then the second issue, however I don't have enough idea of how
> ALTER TABLE works, the complexity would be reduced if acceptance
> chek for alter "action"s would done on foreign server or data
> wrapper side, not on the core of ALTER TABLE. It would also be a
> help to output error messages like above.
I'm not sure it's worth having such an mechanism inside/outside the PG
core. I might misunderstand your concern here, but is it the risk of
constraint violation? If so, I'd like to vote for an idea of avoiding
that violation by making the FDW in itself perform ExecQual() for each
tuple retrived from the remote server at the query time.
>> We spent a lot of time discussing this issue, and the consensus is
>> that it's users' fault if there are some tuples on the remote side
>> violating a given constraint, as mentioned in the documentation.
>
> I'm worried about not that but the changes and possible
> inconsistency would take place behind operators' backs. And this
> looks to cause such inconsistencies for me.
That is what you mean by "unanticipated application", right?
>>>> * [NO] INHERIT parent_table
>>>
>>> Is this usable for inheritance foreign children? NO INHERIT
>>> removes all foreign children but INHERIT is nop.
>>
>> I didn't express clearly. Sorry for that. Let me explain about it.
>>
>> * ALTER FOREIGN TABLE target_table *INHERIT* parent_table: Add the
>> * target table as a new child of the parent table.
>> * ALTER FOREIGN TABLE target_table *NO INHERIT* parent_table: Remove the
>> * target table from the list of children of the parent table.
>
> I got it, thank you. It alone seems no probmen but also doesn't
> seem to be a matter of 'ALTER TABLE'. Could you tell me how this
> is related to 'ALTER TABLE'?
These are not related to ALTER TABLE. [NO] INHERIT parent_table (and
ADD/DROP CONSTRAINT) are what I think should be newly allowed to apply
to foreign tables directly.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-02-21 02:38 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-02-21 02:38 UTC (permalink / raw)
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
(2014/02/20 19:55), Etsuro Fujita wrote:
> (2014/02/20 15:47), Kyotaro HORIGUCHI wrote:
>> Although my concerns here are only two points,
>> unanticipated application and "maintenancibility". I gave a
>> consideration on these issues again.
>
> Sorry, I misunderstood what you mean by "unanticipated application".
>
>> Then, I think it could be enough by giving feedback to operators
>> for the first issue.
>>
>> =# ALTER TABLE parent ADD CHECK (tempmin < tempmax),
>> ALTER tempmin SET NOT NULL,
>> ALTER tempmin SET DEFAULT 0;
>> NOTICE: Child foregn table child01 is affected.
>> NOTICE: Child foregn table child02 is affected
>> NOTICE: Child foregn table child03 rejected 'alter tempmin set default'
>>
>> What do you think about this? It looks a bit too loud for me
>> though...
>
> I think that's a good idea.
I just thought those messages would be shown for the user to readily
notice the changes of the structures of child tables that are foreign,
done by the recursive altering operation. But I overlooked the third line:
NOTICE: Child foregn table child03 rejected 'alter tempmin set default'
What does "rejected" in this message mean?
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-02-21 06:23 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2014-02-21 06:23 UTC (permalink / raw)
To: fujita.etsuro@lab.ntt.co.jp; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
Hi,
> >> NOTICE: Child foregn table child01 is affected.
> >> NOTICE: Child foregn table child02 is affected
> >> NOTICE: Child foregn table child03 rejected 'alter tempmin set
> >> default'
> >>
> >> What do you think about this? It looks a bit too loud for me
> >> though...
> >
> > I think that's a good idea.
>
> I just thought those messages would be shown for the user to readily
> notice the changes of the structures of child tables that are foreign,
> done by the recursive altering operation. But I overlooked the third
> line:
>
> NOTICE: Child foregn table child03 rejected 'alter tempmin set
> default'
>
> What does "rejected" in this message mean?
It says that child03 had no ability to perform the requested
action, in this case setting a default value. It might be better
to reject ALTER on the parent as a whole when any children
doesn't accept any action.
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-02-21 07:03 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 0 replies; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2014-02-21 07:03 UTC (permalink / raw)
To: fujita.etsuro@lab.ntt.co.jp; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
Hello,
> > Then the second issue, however I don't have enough idea of how
> > ALTER TABLE works, the complexity would be reduced if acceptance
> > chek for alter "action"s would done on foreign server or data
> > wrapper side, not on the core of ALTER TABLE. It would also be a
> > help to output error messages like above.
>
> I'm not sure it's worth having such an mechanism inside/outside the PG
> core. I might misunderstand your concern here, but is it the risk of
> constraint violation?
A bit different:) It's the problem of how and who decides whether
each ALTER action given can be performed on each child. Set of
available actions vary according to the nature of each foreign
table/server/driver and also according to their functional
evolutions made in future, largely independent of the core,
maybe. The core oughtn't to.. couldn't maintain such a function
judging availability of every possible combination of action and
foreign table.
> If so, I'd like to vote for an idea of avoiding
> that violation by making the FDW in itself perform ExecQual() for each
> tuple retrived from the remote server at the query time.
In my humble opition, it is not so serious problem that
functionally acceptable actions can cause any kind of
inconsistency by the nature of fdw so far. It is well documented
and operators should be aware of such inconsistencies after being
informed of what they did, by the NOTICE messages. I think.
> >> * ALTER FOREIGN TABLE target_table *INHERIT* parent_table: Add the
> >> * target table as a new child of the parent table.
> >> * ALTER FOREIGN TABLE target_table *NO INHERIT* parent_table: Remove the
> >> * target table from the list of children of the parent table.
> >
> > I got it, thank you. It alone seems no probmen but also doesn't
> > seem to be a matter of 'ALTER TABLE'. Could you tell me how this
> > is related to 'ALTER TABLE'?
>
> These are not related to ALTER TABLE. [NO] INHERIT parent_table (and
> ADD/DROP CONSTRAINT) are what I think should be newly allowed to apply
> to foreign tables directly.
Thank you, I understood that. I thought it already existed.
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-02-21 07:33 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-02-21 07:33 UTC (permalink / raw)
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
(2014/02/21 15:23), Kyotaro HORIGUCHI wrote:
>>>> NOTICE: Child foregn table child01 is affected.
>>>> NOTICE: Child foregn table child02 is affected
>>>> NOTICE: Child foregn table child03 rejected 'alter tempmin set
>>>> default'
>>>>
>>>> What do you think about this? It looks a bit too loud for me
>>>> though...
>>>
>>> I think that's a good idea.
>>
>> I just thought those messages would be shown for the user to readily
>> notice the changes of the structures of child tables that are foreign,
>> done by the recursive altering operation. But I overlooked the third
>> line:
>>
>> NOTICE: Child foregn table child03 rejected 'alter tempmin set
>> default'
>>
>> What does "rejected" in this message mean?
>
> It says that child03 had no ability to perform the requested
> action, in this case setting a default value. It might be better
> to reject ALTER on the parent as a whole when any children
> doesn't accept any action.
Now understood, thougn I'm not sure it's worth implementing such a
checking mechanism in the recursive altering operation...
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-02-21 08:01 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 0 replies; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2014-02-21 08:01 UTC (permalink / raw)
To: fujita.etsuro@lab.ntt.co.jp; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
Hello,
At Fri, 21 Feb 2014 16:33:32 +0900, Etsuro Fujita wrote
> (2014/02/21 15:23), Kyotaro HORIGUCHI wrote:
> >>>> NOTICE: Child foregn table child01 is affected.
> >>>> NOTICE: Child foregn table child02 is affected
> >>>> NOTICE: Child foregn table child03 rejected 'alter tempmin set
> >>>> default'
> > It says that child03 had no ability to perform the requested
> > action, in this case setting a default value. It might be better
> > to reject ALTER on the parent as a whole when any children
> > doesn't accept any action.
>
> Now understood, thougn I'm not sure it's worth implementing such a
> checking mechanism in the recursive altering operation...
Did you mean foreign tables can sometimes silently ignore ALTER
actions which it can't perform? It will cause inconsistency which
operators didn't anticipate. This example uses "add column" for
perspicuitly but all types of action could result like this.
==============
=# ALTER TABLE parent ADD COLUMN x integer;
ALTER TABLE
=# \d parent
Table "public.parent"
Column | Type | Modifiers
--------+---------+--------------------
a | integer |
b | integer |
x | integer |
Number of child tables: 2 (Use \d+ to list them.)
=# \d child1
Foreign table "public.child1"
Column | Type | Modifiers | FDW Options
----------+---------+-----------+-------------
a | integer |
b | integer |
=# (Op: Ouch!)
==============
I think this should result as,
==============
=# ALTER TABLE parent ADD COLUMN x integer;
ERROR: Foreign child table child1 could not perform some of the actions.
=#
==============
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-02-25 10:45 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-02-25 10:45 UTC (permalink / raw)
To: Shigeru Hanada <shigeru.hanada@gmail.com>; +Cc: pgsql-hackers
In addition to an issue pointed out recently by Horiguchi-san, I've
found there is another issue we have to discuss. That is, we can't
build any parameterized Append paths for an inheritance tree that
contains at least one foreign table, in set_append_rel_pathlist(). This
is because the patch doesn't take into consideration
"reparameterization" for paths for a foreign table, which attempts to
modify these paths to have greater parameterization so that each child
path in the inheritance tree can have the exact same parameterization.
To do so, I think we would need to add code for the foreign-table case
to reparameterize_path(). And I think we should introduce a new FDW
routine, say ReparameterizeForeignPath() because the processing would be
performed best by the FDW itself.
Comments are welcome!
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-02-26 03:48 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2014-02-26 03:48 UTC (permalink / raw)
To: fujita.etsuro@lab.ntt.co.jp; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
Hello, I tried minimal implementation to do that.
At Tue, 25 Feb 2014 19:45:56 +0900, Etsuro Fujita wrote
> In addition to an issue pointed out recently by Horiguchi-san, I've
> found there is another issue we have to discuss. That is, we can't
> build any parameterized Append paths for an inheritance tree that
> contains at least one foreign table, in set_append_rel_pathlist(). This
> is because the patch doesn't take into consideration
> "reparameterization" for paths for a foreign table, which attempts to
> modify these paths to have greater parameterization so that each child
> path in the inheritance tree can have the exact same parameterization.
> To do so, I think we would need to add code for the foreign-table case
> to reparameterize_path(). And I think we should introduce a new FDW
> routine, say ReparameterizeForeignPath() because the processing would be
> performed best by the FDW itself.
>
> Comments are welcome!
I think the problem is foreign childs in inheritance tables
prevents all menber in the inheritance relation from using
parameterized paths, correct?
|=# explain select * from p join (select uname from c1 limit 1) s on s.uname = p.uname;
| QUERY PLAN
|-------------------------------------------------------------------------------
| Hash Join (cost=0.04..244.10 rows=50 width=58)
| Hash Cond: (p.uname = c1_1.uname)
| -> Append (cost=0.00..206.01 rows=10012 width=50)
| -> Seq Scan on p (cost=0.00..0.00 rows=1 width=168)
| -> Seq Scan on c1 (cost=0.00..204.01 rows=10001 width=50)
| -> Foreign Scan on c2 (cost=0.00..2.00 rows=10 width=168)
| Foreign File: /etc/passwd
| Foreign File Size: 1851
| -> Hash (cost=0.03..0.03 rows=1 width=8)
| -> Limit (cost=0.00..0.02 rows=1 width=8)
| -> Seq Scan on c1 c1_1 (cost=0.00..204.01 rows=10001 width=8)
| Planning time: 1.095 ms
Hmm. I tried minimal implementation to do that. This omits cost
recalculation but seems to work as expected. This seems enough if
cost recalc is trivial here.
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;
}
....
|=# explain select * from p join (select uname from c1 limit 1) s on s.uname = p.uname;
| QUERY PLAN
|----------------------------------------------------------------------------
| Nested Loop (cost=0.00..10.46 rows=50 width=58)
| -> Limit (cost=0.00..0.02 rows=1 width=8)
| -> Seq Scan on c1 c1_1 (cost=0.00..204.01 rows=10001 width=8)
| -> Append (cost=0.00..10.30 rows=12 width=158)
| -> Seq Scan on p (cost=0.00..0.00 rows=1 width=168)
| Filter: (c1_1.uname = uname)
| -> Index Scan using i_c1 on c1 (cost=0.29..8.30 rows=1 width=50)
| Index Cond: (uname = c1_1.uname)
| -> Foreign Scan on c2 (cost=0.00..2.00 rows=10 width=168)
| Filter: (c1_1.uname = uname)
| Foreign File: /etc/passwd
| Foreign File Size: 1851
| Planning time: 2.044 ms
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-03-10 08:29 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
0 siblings, 2 replies; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2014-03-10 08:29 UTC (permalink / raw)
To: fujita.etsuro@lab.ntt.co.jp; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
Hello. As a minimal implementation, I made an attempt that emit
NOTICE message when alter table affects foreign tables. It looks
like following,
| =# alter table passwd add column added int, add column added2 int;
| NOTICE: This command affects foreign relation "cf1"
| NOTICE: This command affects foreign relation "cf1"
| ALTER TABLE
| =# select * from passwd;
| ERROR: missing data for column "added"
| CONTEXT: COPY cf1, line 1: "root:x:0:0:root:/root:/bin/bash"
| =#
This seems far better than silently performing the command,
except for the duplicated message:( New bitmap might required to
avoid the duplication..
I made the changes above and below as an attempt in the attached
patch foreign_inherit-v4.patch
> I think the problem is foreign childs in inheritance tables
> prevents all menber in the inheritance relation from using
> parameterized paths, correct?
...
> Hmm. I tried minimal implementation to do that. This omits cost
> recalculation but seems to work as expected. This seems enough if
> cost recalc is trivial here.
Any comments?
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-patch] foreign_inherit-v4.patch (31.8K, ../../20140310.172911.79971075.horiguchi.kyotaro@lab.ntt.co.jp/2-foreign_inherit-v4.patch)
download | inline diff:
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.
</para>
+ <note>
+ <para>
+ 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
+ <xref linkend="ddl-inherit">) to off-load to multiple servers.
+ </para>
+ </note>
+
<sect2 id="ddl-constraints-check-constraints">
<title>Check Constraints</title>
@@ -2017,8 +2028,8 @@ CREATE TABLE capitals (
</para>
<para>
- In <productname>PostgreSQL</productname>, a table can inherit from
- zero or more other tables, and a query can reference either all
+ In <productname>PostgreSQL</productname>, 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 ] <replaceable class="PARAMETER">name</replaceab
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
@@ -178,6 +180,26 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
</varlistentry>
<varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be a plain table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
<listitem>
<para>
@@ -306,6 +328,16 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">parent_name</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be a plain table.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
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 ] <replaceable class="PARAMETER">table_name
<replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
+[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
@@ -159,6 +160,18 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for the
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
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 '@');
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-03-11 05:07 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2014-03-11 05:07 UTC (permalink / raw)
To: fujita.etsuro@lab.ntt.co.jp; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
Hello,
> This seems far better than silently performing the command,
> except for the duplicated message:( New bitmap might required to
> avoid the duplication..
I rewrote it in more tidy way. ATController collects all affected
tables on ATRewriteCatalogs as first stage, then emit NOTICE
message according to the affected relations list. The message
looks like,
| =# alter table passwd alter column uname set default 'hoge';
| NOTICE: This command affects 2 foreign tables: cf1, cf2
| ALTER TABLE
Do you feel this too large or complicated? I think so a bit..
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-patch] foreign_inherit-v5.patch (42.8K, ../../20140311.140751.146429295.horiguchi.kyotaro@lab.ntt.co.jp/2-foreign_inherit-v5.patch)
download | inline diff:
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.
</para>
+ <note>
+ <para>
+ 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
+ <xref linkend="ddl-inherit">) to off-load to multiple servers.
+ </para>
+ </note>
+
<sect2 id="ddl-constraints-check-constraints">
<title>Check Constraints</title>
@@ -2017,8 +2028,8 @@ CREATE TABLE capitals (
</para>
<para>
- In <productname>PostgreSQL</productname>, a table can inherit from
- zero or more other tables, and a query can reference either all
+ In <productname>PostgreSQL</productname>, 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 ] <replaceable class="PARAMETER">name</replaceab
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
@@ -178,6 +180,26 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
</varlistentry>
<varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be a plain table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
<listitem>
<para>
@@ -306,6 +328,16 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">parent_name</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be a plain table.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
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 ] <replaceable class="PARAMETER">table_name
<replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
+[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
@@ -159,6 +160,18 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for the
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
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..6859ff0 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -299,26 +299,30 @@ static void validateForeignKeyConstraint(char *conname,
static void createForeignKeyTriggers(Relation rel, Oid refRelOid,
Constraint *fkconstraint,
Oid constraintOid, Oid indexOid);
+static void PrintForeignNotice(List *affected, Relation rel);
static void ATController(Relation rel, List *cmds, bool recurse, LOCKMODE lockmode);
static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
bool recurse, bool recursing, LOCKMODE lockmode);
-static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode);
-static void ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
- AlterTableCmd *cmd, LOCKMODE lockmode);
+static void ATRewriteCatalogs(List **wqueue, List **affectd, LOCKMODE lockmode);
+static void ATExecCmd(List **wqueue, List **affected, AlteredTableInfo *tab,
+ Relation rel, AlterTableCmd *cmd, LOCKMODE lockmode);
static void ATRewriteTables(List **wqueue, LOCKMODE lockmode);
static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode);
static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
+static void ATAddAffectedRelid(List **affected, Oid relid);
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,
DropBehavior behavior);
static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
AlterTableCmd *cmd, LOCKMODE lockmode);
-static void ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
+static void ATExecAddColumn(List **wqueue, List **affected,
+ AlteredTableInfo *tab, Relation rel,
ColumnDef *colDef, bool isOid,
bool recurse, bool recursing, LOCKMODE lockmode);
static void check_for_column_name_collision(Relation rel, const char *colname);
@@ -341,13 +345,14 @@ static void ATExecSetStorage(Relation rel, const char *colName,
Node *newValue, LOCKMODE lockmode);
static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
AlterTableCmd *cmd, LOCKMODE lockmode);
-static void ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+static void ATExecDropColumn(List **wqueue, List **affected,
+ Relation rel, const char *colName,
DropBehavior behavior,
bool recurse, bool recursing,
bool missing_ok, LOCKMODE lockmode);
static void ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
-static void ATExecAddConstraint(List **wqueue,
+static void ATExecAddConstraint(List **wqueue, List **affected,
AlteredTableInfo *tab, Relation rel,
Constraint *newConstraint, bool recurse, bool is_readd,
LOCKMODE lockmode);
@@ -467,10 +472,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,
@@ -2940,9 +2941,71 @@ AlterTableGetLockLevel(List *cmds)
}
static void
+PrintForeignNotice(List *affected, Relation rel)
+{
+ ListCell *l;
+ StringInfo namelist = NULL;
+ bool rel_is_foreign = false;
+ int state = 0;
+ int n = 0;
+
+ if (!affected) return;
+
+ foreach (l, affected)
+ {
+ Oid relid = lfirst_oid(l);
+ Relation r = relation_open(relid, NoLock);
+ if (r->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ if (relid == RelationGetRelid(rel))
+ rel_is_foreign = true;
+
+ switch (state)
+ {
+ case 0:
+ namelist = makeStringInfo();
+ appendStringInfoString(namelist, RelationGetRelationName(r));
+ state = 1;
+ break;
+
+ case 1:
+ if (namelist->len < 32)
+ {
+ appendStringInfoString(namelist, ", ");
+ appendStringInfoString(namelist,
+ RelationGetRelationName(r));
+ }
+ else
+ {
+ appendStringInfoString(namelist, "...");
+ state = 2;
+ }
+ break;
+
+ default:;
+ /* Do nothing */
+ }
+
+ n++;
+ }
+ relation_close(r, NoLock);
+ }
+
+ /*
+ * Don't show this notice if the relation designated in this command is
+ * the only foreign table affected.
+ */
+ if (n > 1 || (n == 1 && !rel_is_foreign))
+ ereport(NOTICE,
+ (errmsg_plural("This command affects %d foreign table: %s",
+ "This command affects %d foreign tables: %s",
+ n, n, namelist->data)));
+}
+
+static void
ATController(Relation rel, List *cmds, bool recurse, LOCKMODE lockmode)
{
- List *wqueue = NIL;
+ List *wqueue = NIL, *affected = NIL;
ListCell *lcmd;
/* Phase 1: preliminary examination of commands, create work queue */
@@ -2957,10 +3020,14 @@ ATController(Relation rel, List *cmds, bool recurse, LOCKMODE lockmode)
relation_close(rel, NoLock);
/* Phase 2: update system catalogs */
- ATRewriteCatalogs(&wqueue, lockmode);
+ ATRewriteCatalogs(&wqueue, &affected, lockmode);
/* Phase 3: scan/rewrite tables as needed */
ATRewriteTables(&wqueue, lockmode);
+
+ /* Notice if foreign tables are affected by this command */
+ if (affected)
+ PrintForeignNotice(affected, rel);
}
/*
@@ -3019,24 +3086,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 +3120,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 +3139,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 +3153,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 +3221,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 +3262,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);
@@ -3217,7 +3294,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
* conflicts).
*/
static void
-ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
+ATRewriteCatalogs(List **wqueue, List **affected, LOCKMODE lockmode)
{
int pass;
ListCell *ltab;
@@ -3248,7 +3325,8 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
rel = relation_open(tab->relid, NoLock);
foreach(lcmd, subcmds)
- ATExecCmd(wqueue, tab, rel, (AlterTableCmd *) lfirst(lcmd), lockmode);
+ ATExecCmd(wqueue, affected, tab, rel,
+ (AlterTableCmd *) lfirst(lcmd), lockmode);
/*
* After the ALTER TYPE pass, do cleanup work (this is not done in
@@ -3277,19 +3355,21 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
* ATExecCmd: dispatch a subcommand to appropriate execution routine
*/
static void
-ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
+ATExecCmd(List **wqueue, List **affected, AlteredTableInfo *tab, Relation rel,
AlterTableCmd *cmd, LOCKMODE lockmode)
{
+ ATAddAffectedRelid(affected, RelationGetRelid(rel));
+
switch (cmd->subtype)
{
case AT_AddColumn: /* ADD COLUMN */
case AT_AddColumnToView: /* add column via CREATE OR REPLACE
* VIEW */
- ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+ ATExecAddColumn(wqueue, affected, tab, rel, (ColumnDef *) cmd->def,
false, false, false, lockmode);
break;
case AT_AddColumnRecurse:
- ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+ ATExecAddColumn(wqueue, affected, tab, rel, (ColumnDef *) cmd->def,
false, true, false, lockmode);
break;
case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */
@@ -3314,11 +3394,11 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
break;
case AT_DropColumn: /* DROP COLUMN */
- ATExecDropColumn(wqueue, rel, cmd->name,
+ ATExecDropColumn(wqueue, affected, rel, cmd->name,
cmd->behavior, false, false, cmd->missing_ok, lockmode);
break;
case AT_DropColumnRecurse: /* DROP COLUMN with recursion */
- ATExecDropColumn(wqueue, rel, cmd->name,
+ ATExecDropColumn(wqueue, affected, rel, cmd->name,
cmd->behavior, true, false, cmd->missing_ok, lockmode);
break;
case AT_AddIndex: /* ADD INDEX */
@@ -3328,16 +3408,19 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true, lockmode);
break;
case AT_AddConstraint: /* ADD CONSTRAINT */
- ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
+ ATExecAddConstraint(wqueue, affected, tab, rel,
+ (Constraint *) cmd->def,
false, false, lockmode);
break;
case AT_AddConstraintRecurse: /* ADD CONSTRAINT with recursion */
- ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
+ ATExecAddConstraint(wqueue, affected, tab, rel,
+ (Constraint *) cmd->def,
true, false, lockmode);
break;
case AT_ReAddConstraint: /* Re-add pre-existing check
* constraint */
- ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
+ ATExecAddConstraint(wqueue, affected,
+ tab, rel, (Constraint *) cmd->def,
false, true, lockmode);
break;
case AT_AddIndexConstraint: /* ADD CONSTRAINT USING INDEX */
@@ -3383,13 +3466,15 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_AddOids: /* SET WITH OIDS */
/* Use the ADD COLUMN code, unless prep decided to do nothing */
if (cmd->def != NULL)
- ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+ ATExecAddColumn(wqueue, affected,
+ tab, rel, (ColumnDef *) cmd->def,
true, false, false, lockmode);
break;
case AT_AddOidsRecurse: /* SET WITH OIDS */
/* Use the ADD COLUMN code, unless prep decided to do nothing */
if (cmd->def != NULL)
- ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+ ATExecAddColumn(wqueue, affected,
+ tab, rel, (ColumnDef *) cmd->def,
true, true, false, lockmode);
break;
case AT_DropOids: /* SET WITHOUT OIDS */
@@ -4009,6 +4094,23 @@ ATGetQueueEntry(List **wqueue, Relation rel)
}
/*
+ * ATAddAffectedRelid: add a relid to affected list.
+ */
+static void
+ATAddAffectedRelid(List **affected, Oid relid)
+{
+ ListCell *l;
+
+ foreach(l, *affected)
+ {
+ if (lfirst_oid(l) == relid)
+ return;
+ }
+
+ *affected = lappend_oid(*affected, relid);
+}
+
+/*
* ATSimplePermissions
*
* - Ensure that it is a relation (or possibly a view)
@@ -4125,7 +4227,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 +4256,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);
}
}
@@ -4421,7 +4528,8 @@ ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
}
static void
-ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
+ATExecAddColumn(List **wqueue, List **affected,
+ AlteredTableInfo *tab, Relation rel,
ColumnDef *colDef, bool isOid,
bool recurse, bool recursing, LOCKMODE lockmode)
{
@@ -4442,9 +4550,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
ListCell *child;
AclResult aclresult;
+ ATAddAffectedRelid(affected, myrelid);
+
/* 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);
@@ -4746,7 +4856,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
childtab = ATGetQueueEntry(wqueue, childrel);
/* Recurse to child */
- ATExecAddColumn(wqueue, childtab, childrel,
+ ATExecAddColumn(wqueue, affected, childtab, childrel,
colDef, isOid, recurse, true, lockmode);
heap_close(childrel, NoLock);
@@ -4859,7 +4969,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;
+ }
}
/*
@@ -5327,7 +5449,8 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
}
static void
-ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+ATExecDropColumn(List **wqueue, List **affected,
+ Relation rel, const char *colName,
DropBehavior behavior,
bool recurse, bool recursing,
bool missing_ok, LOCKMODE lockmode)
@@ -5338,9 +5461,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
List *children;
ObjectAddress object;
+ ATAddAffectedRelid(affected, RelationGetRelid(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);
/*
* get the number of the attribute
@@ -5426,7 +5551,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (childatt->attinhcount == 1 && !childatt->attislocal)
{
/* Time to delete this child column, too */
- ATExecDropColumn(wqueue, childrel, colName,
+ ATExecDropColumn(wqueue, affected, childrel, colName,
behavior, true, true,
false, lockmode);
}
@@ -5644,12 +5769,15 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
* ALTER TABLE ADD CONSTRAINT
*/
static void
-ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
+ATExecAddConstraint(List **wqueue, List **affected,
+ AlteredTableInfo *tab, Relation rel,
Constraint *newConstraint, bool recurse, bool is_readd,
LOCKMODE lockmode)
{
Assert(IsA(newConstraint, Constraint));
+ ATAddAffectedRelid(affected, RelationGetRelid(rel));
+
/*
* Currently, we only expect to see CONSTR_CHECK and CONSTR_FOREIGN nodes
* arriving here (see the preprocessing done in parse_utilcmd.c). Use a
@@ -5732,7 +5860,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 +5878,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 +7368,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 +7703,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 '@');
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-03-11 05:43 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
0 siblings, 0 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-03-11 05:43 UTC (permalink / raw)
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
(2014/03/11 14:07), Kyotaro HORIGUCHI wrote:
>> This seems far better than silently performing the command,
>> except for the duplicated message:( New bitmap might required to
>> avoid the duplication..
>
> I rewrote it in more tidy way. ATController collects all affected
> tables on ATRewriteCatalogs as first stage, then emit NOTICE
> message according to the affected relations list. The message
> looks like,
>
> | =# alter table passwd alter column uname set default 'hoge';
> | NOTICE: This command affects 2 foreign tables: cf1, cf2
> | ALTER TABLE
>
> Do you feel this too large or complicated? I think so a bit..
No. I think that would be a useful message for the user.
My feeling is it would be better to show this kind of messages for all
the affected tables whether or not the affected ones are foreign. How
about introducing a VERBOSE option for ALTER TABLE? Though, I think
that should be implemented as a separate patch.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-03-13 13:00 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-03-13 13:00 UTC (permalink / raw)
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
Hi Horiguchi-san,
Thank you for working this patch!
(2014/03/10 17:29), Kyotaro HORIGUCHI wrote:
> Hello. As a minimal implementation, I made an attempt that emit
> NOTICE message when alter table affects foreign tables. It looks
> like following,
>
> | =# alter table passwd add column added int, add column added2 int;
> | NOTICE: This command affects foreign relation "cf1"
> | NOTICE: This command affects foreign relation "cf1"
> | ALTER TABLE
> | =# select * from passwd;
> | ERROR: missing data for column "added"
> | CONTEXT: COPY cf1, line 1: "root:x:0:0:root:/root:/bin/bash"
> | =#
>
> This seems far better than silently performing the command,
> except for the duplicated message:( New bitmap might required to
> avoid the duplication..
As I said before, I think it would be better to show this kind of
information on each of the affected tables whether or not the affected
one is foreign. I also think it would be better to show it only when
the user has specified an option to do so, similar to a VERBOSE option
of other commands. ISTM this should be implemented as a separate patch.
> I made the changes above and below as an attempt in the attached
> patch foreign_inherit-v4.patch
>
>> I think the problem is foreign childs in inheritance tables
>> prevents all menber in the inheritance relation from using
>> parameterized paths, correct?
Yes, I think so, too.
>> Hmm. I tried minimal implementation to do that. This omits cost
>> recalculation but seems to work as expected. This seems enough if
>> cost recalc is trivial here.
I think we should redo the cost/size estimate, because for example,
greater parameterization leads to a smaller rowcount estimate, if I
understand correctly. In addition, I think this reparameterization
should be done by the FDW itself, becasuse the FDW has more knowledge
about it than the PG core. So, I think we should introduce a new FDW
routine for that, say ReparameterizeForeignPath(), as proposed in [1].
Attached is an updated version of the patch. Due to the above reason, I
removed from the patch the message displaying function you added.
Sorry for the delay.
[1] http://www.postgresql.org/message-id/530C7464.6020006@lab.ntt.co.jp
Best regards,
Etsuro Fujita
*** a/contrib/file_fdw/file_fdw.c
--- b/contrib/file_fdw/file_fdw.c
***************
*** 117,122 **** static void fileGetForeignRelSize(PlannerInfo *root,
--- 117,126 ----
static void fileGetForeignPaths(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid);
+ static ForeignPath *fileReparameterizeForeignPath(PlannerInfo *root,
+ RelOptInfo *baserel,
+ Path *path,
+ Relids required_outer);
static ForeignScan *fileGetForeignPlan(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
***************
*** 145,150 **** static bool check_selective_binary_conversion(RelOptInfo *baserel,
--- 149,155 ----
static void estimate_size(PlannerInfo *root, RelOptInfo *baserel,
FileFdwPlanState *fdw_private);
static void estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
+ ParamPathInfo *param_info,
FileFdwPlanState *fdw_private,
Cost *startup_cost, Cost *total_cost);
static int file_acquire_sample_rows(Relation onerel, int elevel,
***************
*** 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,
! NULL, fdw_private,
&startup_cost, &total_cost);
/*
***************
*** 542,547 **** fileGetForeignPaths(PlannerInfo *root,
--- 549,595 ----
}
/*
+ * fileReparameterizeForeignPath
+ * Attempt to modify a given path to have greater parameterization
+ */
+ static ForeignPath *
+ fileReparameterizeForeignPath(PlannerInfo *root,
+ RelOptInfo *baserel,
+ Path *path,
+ Relids required_outer)
+ {
+ ForeignPath *newpath = makeNode(ForeignPath);
+ ParamPathInfo *param_info;
+ FileFdwPlanState *fdw_private = (FileFdwPlanState *) baserel->fdw_private;
+ Cost startup_cost;
+ Cost total_cost;
+
+ /*
+ * To avoid re-checking whether to selectively perform binary conversion,
+ * we create a ForeignPath node from by flat-copying the given path node,
+ * rewriting the param_info and row estimate, and redoing the cost estimate.
+ */
+ memcpy(newpath, (ForeignPath *) path, sizeof(ForeignPath));
+
+ /* Get the ParamPathInfo */
+ param_info = get_baserel_parampathinfo(root, baserel, required_outer);
+ Assert(param_info);
+
+ newpath->path.param_info = param_info;
+ newpath->path.rows = param_info->ppi_rows;
+
+ /* Redo the cost estimate */
+ estimate_costs(root, baserel,
+ param_info, fdw_private,
+ &startup_cost, &total_cost);
+
+ newpath->path.startup_cost = startup_cost;
+ newpath->path.total_cost = total_cost;
+
+ return newpath;
+ }
+
+ /*
* fileGetForeignPlan
* Create a ForeignScan plan node for scanning the foreign table
*/
***************
*** 970,981 **** estimate_size(PlannerInfo *root, RelOptInfo *baserel,
--- 1018,1031 ----
*/
static void
estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
+ ParamPathInfo *param_info,
FileFdwPlanState *fdw_private,
Cost *startup_cost, Cost *total_cost)
{
BlockNumber pages = fdw_private->pages;
double ntuples = fdw_private->ntuples;
Cost run_cost = 0;
+ QualCost qpqual_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;
}
--- 1036,1045 ----
*/
run_cost += seq_page_cost * pages;
! get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost);
!
! *startup_cost = qpqual_cost.startup;
! cpu_per_tuple = cpu_tuple_cost * 10 + qpqual_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,250 ----
static void postgresGetForeignPaths(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid);
+ static ForeignPath *postgresReparameterizeForeignPath(PlannerInfo *root,
+ RelOptInfo *baserel,
+ Path *path,
+ Relids required_outer);
static ForeignScan *postgresGetForeignPlan(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
***************
*** 342,347 **** postgres_fdw_handler(PG_FUNCTION_ARGS)
--- 346,352 ----
/* 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,
--- 734,780 ----
}
/*
+ * postgresReparameterizeForeignPath
+ * Attempt to modify a given path to have greater parameterization
+ */
+ static ForeignPath *
+ postgresReparameterizeForeignPath(PlannerInfo *root,
+ RelOptInfo *baserel,
+ Path *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 estimate */
+ 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 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;
--- 1821,1828 ----
}
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;
--- 1835,1863 ----
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.
</para>
+ <note>
+ <para>
+ 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
+ <xref linkend="ddl-inherit">) to off-load to multiple servers.
+ </para>
+ </note>
+
<sect2 id="ddl-constraints-check-constraints">
<title>Check Constraints</title>
***************
*** 2017,2024 **** CREATE TABLE capitals (
</para>
<para>
! In <productname>PostgreSQL</productname>, 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 ----
</para>
<para>
! In <productname>PostgreSQL</productname>, 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/ref/alter_foreign_table.sgml
--- b/doc/src/sgml/ref/alter_foreign_table.sgml
***************
*** 42,47 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 42,49 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 178,183 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 180,205 ----
</varlistentry>
<varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be a plain table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
<listitem>
<para>
***************
*** 306,311 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 328,343 ----
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">parent_name</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be a plain table.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
*** a/doc/src/sgml/ref/create_foreign_table.sgml
--- b/doc/src/sgml/ref/create_foreign_table.sgml
***************
*** 22,27 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 22,28 ----
<replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 160,177 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for the
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
*** a/src/backend/catalog/pg_inherits.c
--- b/src/backend/catalog/pg_inherits.c
***************
*** 256,261 **** has_subclass(Oid relationId)
--- 256,303 ----
/*
+ * 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.
*/
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 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;
--- 115,122 ----
* analyze_rel() -- analyze one relation
*/
void
! analyze_rel(Oid relid, VacuumStmt *vacstmt, VacuumMode vacmode,
! BufferAccessStrategy bstrategy)
{
Relation onerel;
int elevel;
***************
*** 270,276 **** analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
/*
* If there are child tables, do recursive ANALYZE.
*/
! if (onerel->rd_rel->relhassubclass)
do_analyze_rel(onerel, vacstmt, acquirefunc, relpages, true, elevel);
/*
--- 271,279 ----
/*
* If there are child tables, do recursive ANALYZE.
*/
! if (onerel->rd_rel->relhassubclass &&
! (vacmode == VAC_MODE_SINGLE ||
! !contains_foreign(RelationGetRelid(onerel), AccessShareLock)))
do_analyze_rel(onerel, vacstmt, acquirefunc, relpages, true, elevel);
/*
***************
*** 1452,1463 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1455,1469 ----
{
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,1491 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1492,1499 ----
* 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,1518 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
}
rels[nrels] = childrel;
! relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
totalblocks += relblocks[nrels];
nrels++;
}
/*
* 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
--- 1515,1573 ----
}
rels[nrels] = 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,1530 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1580,1586 ----
{
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 &&
--- 1596,1607 ----
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);
! /* 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);
! /* 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);
! /* 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 */
! /* 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,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);
***************
*** 4859,4865 **** ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
--- 4871,4889 ----
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,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
--- 5364,5370 ----
/* 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
--- 5756,5769 ----
/* 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 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,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 */
--- 5774,5790 ----
* 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);
--- 7264,7270 ----
/* 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,
--- 7599,7608 ----
* alter would put them out of step.
*/
if (recurse)
! {
! /* 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,
*** a/src/backend/commands/vacuum.c
--- b/src/backend/commands/vacuum.c
***************
*** 98,103 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
--- 98,104 ----
BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel)
{
const char *stmttype;
+ VacuumMode vacmode;
volatile bool in_outer_xact,
use_own_xacts;
List *relations;
***************
*** 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)
+ 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,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, vacmode, vac_strategy);
if (use_own_xacts)
{
*** a/src/backend/optimizer/path/costsize.c
--- b/src/backend/optimizer/path/costsize.c
***************
*** 132,140 **** static MergeScanSelCache *cached_scansel(PlannerInfo *root,
static void cost_rescan(PlannerInfo *root, Path *path,
Cost *rescan_startup_cost, Cost *rescan_total_cost);
static bool cost_qual_eval_walker(Node *node, cost_qual_eval_context *context);
- static void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
- ParamPathInfo *param_info,
- QualCost *qpqual_cost);
static bool has_indexed_join_quals(NestPath *joinpath);
static double approx_tuple_count(PlannerInfo *root, JoinPath *path,
List *quals);
--- 132,137 ----
***************
*** 3157,3163 **** cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
* some of the quals. We assume baserestrictcost was previously set by
* set_baserel_size_estimates().
*/
! static void
get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
ParamPathInfo *param_info,
QualCost *qpqual_cost)
--- 3154,3160 ----
* some of the quals. We assume baserestrictcost was previously set by
* set_baserel_size_estimates().
*/
! void
get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
ParamPathInfo *param_info,
QualCost *qpqual_cost)
*** 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 <math.h>
+ #include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
***************
*** 2062,2067 **** reparameterize_path(PlannerInfo *root, Path *path,
--- 2063,2073 ----
case T_SubqueryScan:
return create_subqueryscan_path(root, rel, path->pathkeys,
required_outer);
+ case T_ForeignScan:
+ return (Path *) rel->fdwroutine->ReparameterizeForeignPath(root,
+ rel,
+ path,
+ required_outer);
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/catalog/pg_inherits_fn.h
--- b/src/include/catalog/pg_inherits_fn.h
***************
*** 21,26 **** extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode);
--- 21,27 ----
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 */
*** 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,41 ----
RelOptInfo *baserel,
Oid foreigntableid);
+ typedef ForeignPath *(*ReparameterizeForeignPath_function) (PlannerInfo *root,
+ RelOptInfo *baserel,
+ Path *path,
+ Relids required_outer);
+
typedef ForeignScan *(*GetForeignPlan_function) (PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
***************
*** 117,122 **** typedef struct FdwRoutine
--- 122,128 ----
/* 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/include/optimizer/cost.h
--- b/src/include/optimizer/cost.h
***************
*** 143,148 **** extern void final_cost_hashjoin(PlannerInfo *root, HashPath *path,
--- 143,151 ----
extern void cost_subplan(PlannerInfo *root, SubPlan *subplan, Plan *plan);
extern void cost_qual_eval(QualCost *cost, List *quals, PlannerInfo *root);
extern void cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root);
+ extern void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
+ ParamPathInfo *param_info,
+ QualCost *qpqual_cost);
extern void compute_semi_anti_join_factors(PlannerInfo *root,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
*** 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 '@');
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/plain] foreign_inherit-v6.patch (51.5K, ../../5321ABD2.6000104@lab.ntt.co.jp/2-foreign_inherit-v6.patch)
download | inline diff:
*** a/contrib/file_fdw/file_fdw.c
--- b/contrib/file_fdw/file_fdw.c
***************
*** 117,122 **** static void fileGetForeignRelSize(PlannerInfo *root,
--- 117,126 ----
static void fileGetForeignPaths(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid);
+ static ForeignPath *fileReparameterizeForeignPath(PlannerInfo *root,
+ RelOptInfo *baserel,
+ Path *path,
+ Relids required_outer);
static ForeignScan *fileGetForeignPlan(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
***************
*** 145,150 **** static bool check_selective_binary_conversion(RelOptInfo *baserel,
--- 149,155 ----
static void estimate_size(PlannerInfo *root, RelOptInfo *baserel,
FileFdwPlanState *fdw_private);
static void estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
+ ParamPathInfo *param_info,
FileFdwPlanState *fdw_private,
Cost *startup_cost, Cost *total_cost);
static int file_acquire_sample_rows(Relation onerel, int elevel,
***************
*** 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,
! NULL, fdw_private,
&startup_cost, &total_cost);
/*
***************
*** 542,547 **** fileGetForeignPaths(PlannerInfo *root,
--- 549,595 ----
}
/*
+ * fileReparameterizeForeignPath
+ * Attempt to modify a given path to have greater parameterization
+ */
+ static ForeignPath *
+ fileReparameterizeForeignPath(PlannerInfo *root,
+ RelOptInfo *baserel,
+ Path *path,
+ Relids required_outer)
+ {
+ ForeignPath *newpath = makeNode(ForeignPath);
+ ParamPathInfo *param_info;
+ FileFdwPlanState *fdw_private = (FileFdwPlanState *) baserel->fdw_private;
+ Cost startup_cost;
+ Cost total_cost;
+
+ /*
+ * To avoid re-checking whether to selectively perform binary conversion,
+ * we create a ForeignPath node from by flat-copying the given path node,
+ * rewriting the param_info and row estimate, and redoing the cost estimate.
+ */
+ memcpy(newpath, (ForeignPath *) path, sizeof(ForeignPath));
+
+ /* Get the ParamPathInfo */
+ param_info = get_baserel_parampathinfo(root, baserel, required_outer);
+ Assert(param_info);
+
+ newpath->path.param_info = param_info;
+ newpath->path.rows = param_info->ppi_rows;
+
+ /* Redo the cost estimate */
+ estimate_costs(root, baserel,
+ param_info, fdw_private,
+ &startup_cost, &total_cost);
+
+ newpath->path.startup_cost = startup_cost;
+ newpath->path.total_cost = total_cost;
+
+ return newpath;
+ }
+
+ /*
* fileGetForeignPlan
* Create a ForeignScan plan node for scanning the foreign table
*/
***************
*** 970,981 **** estimate_size(PlannerInfo *root, RelOptInfo *baserel,
--- 1018,1031 ----
*/
static void
estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
+ ParamPathInfo *param_info,
FileFdwPlanState *fdw_private,
Cost *startup_cost, Cost *total_cost)
{
BlockNumber pages = fdw_private->pages;
double ntuples = fdw_private->ntuples;
Cost run_cost = 0;
+ QualCost qpqual_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;
}
--- 1036,1045 ----
*/
run_cost += seq_page_cost * pages;
! get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost);
!
! *startup_cost = qpqual_cost.startup;
! cpu_per_tuple = cpu_tuple_cost * 10 + qpqual_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,250 ----
static void postgresGetForeignPaths(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid);
+ static ForeignPath *postgresReparameterizeForeignPath(PlannerInfo *root,
+ RelOptInfo *baserel,
+ Path *path,
+ Relids required_outer);
static ForeignScan *postgresGetForeignPlan(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
***************
*** 342,347 **** postgres_fdw_handler(PG_FUNCTION_ARGS)
--- 346,352 ----
/* 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,
--- 734,780 ----
}
/*
+ * postgresReparameterizeForeignPath
+ * Attempt to modify a given path to have greater parameterization
+ */
+ static ForeignPath *
+ postgresReparameterizeForeignPath(PlannerInfo *root,
+ RelOptInfo *baserel,
+ Path *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 estimate */
+ 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 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;
--- 1821,1828 ----
}
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;
--- 1835,1863 ----
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.
</para>
+ <note>
+ <para>
+ 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
+ <xref linkend="ddl-inherit">) to off-load to multiple servers.
+ </para>
+ </note>
+
<sect2 id="ddl-constraints-check-constraints">
<title>Check Constraints</title>
***************
*** 2017,2024 **** CREATE TABLE capitals (
</para>
<para>
! In <productname>PostgreSQL</productname>, 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 ----
</para>
<para>
! In <productname>PostgreSQL</productname>, 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/ref/alter_foreign_table.sgml
--- b/doc/src/sgml/ref/alter_foreign_table.sgml
***************
*** 42,47 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 42,49 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 178,183 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 180,205 ----
</varlistentry>
<varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be a plain table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
<listitem>
<para>
***************
*** 306,311 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 328,343 ----
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">parent_name</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be a plain table.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
*** a/doc/src/sgml/ref/create_foreign_table.sgml
--- b/doc/src/sgml/ref/create_foreign_table.sgml
***************
*** 22,27 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 22,28 ----
<replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 160,177 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for the
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
*** a/src/backend/catalog/pg_inherits.c
--- b/src/backend/catalog/pg_inherits.c
***************
*** 256,261 **** has_subclass(Oid relationId)
--- 256,303 ----
/*
+ * 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.
*/
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 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;
--- 115,122 ----
* analyze_rel() -- analyze one relation
*/
void
! analyze_rel(Oid relid, VacuumStmt *vacstmt, VacuumMode vacmode,
! BufferAccessStrategy bstrategy)
{
Relation onerel;
int elevel;
***************
*** 270,276 **** analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
/*
* If there are child tables, do recursive ANALYZE.
*/
! if (onerel->rd_rel->relhassubclass)
do_analyze_rel(onerel, vacstmt, acquirefunc, relpages, true, elevel);
/*
--- 271,279 ----
/*
* If there are child tables, do recursive ANALYZE.
*/
! if (onerel->rd_rel->relhassubclass &&
! (vacmode == VAC_MODE_SINGLE ||
! !contains_foreign(RelationGetRelid(onerel), AccessShareLock)))
do_analyze_rel(onerel, vacstmt, acquirefunc, relpages, true, elevel);
/*
***************
*** 1452,1463 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1455,1469 ----
{
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,1491 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1492,1499 ----
* 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,1518 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
}
rels[nrels] = childrel;
! relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
totalblocks += relblocks[nrels];
nrels++;
}
/*
* 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
--- 1515,1573 ----
}
rels[nrels] = 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,1530 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1580,1586 ----
{
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 &&
--- 1596,1607 ----
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);
! /* 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);
! /* 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);
! /* 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 */
! /* 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,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);
***************
*** 4859,4865 **** ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
--- 4871,4889 ----
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,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
--- 5364,5370 ----
/* 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
--- 5756,5769 ----
/* 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 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,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 */
--- 5774,5790 ----
* 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);
--- 7264,7270 ----
/* 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,
--- 7599,7608 ----
* alter would put them out of step.
*/
if (recurse)
! {
! /* 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,
*** a/src/backend/commands/vacuum.c
--- b/src/backend/commands/vacuum.c
***************
*** 98,103 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
--- 98,104 ----
BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel)
{
const char *stmttype;
+ VacuumMode vacmode;
volatile bool in_outer_xact,
use_own_xacts;
List *relations;
***************
*** 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)
+ 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,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, vacmode, vac_strategy);
if (use_own_xacts)
{
*** a/src/backend/optimizer/path/costsize.c
--- b/src/backend/optimizer/path/costsize.c
***************
*** 132,140 **** static MergeScanSelCache *cached_scansel(PlannerInfo *root,
static void cost_rescan(PlannerInfo *root, Path *path,
Cost *rescan_startup_cost, Cost *rescan_total_cost);
static bool cost_qual_eval_walker(Node *node, cost_qual_eval_context *context);
- static void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
- ParamPathInfo *param_info,
- QualCost *qpqual_cost);
static bool has_indexed_join_quals(NestPath *joinpath);
static double approx_tuple_count(PlannerInfo *root, JoinPath *path,
List *quals);
--- 132,137 ----
***************
*** 3157,3163 **** cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
* some of the quals. We assume baserestrictcost was previously set by
* set_baserel_size_estimates().
*/
! static void
get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
ParamPathInfo *param_info,
QualCost *qpqual_cost)
--- 3154,3160 ----
* some of the quals. We assume baserestrictcost was previously set by
* set_baserel_size_estimates().
*/
! void
get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
ParamPathInfo *param_info,
QualCost *qpqual_cost)
*** 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 <math.h>
+ #include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
***************
*** 2062,2067 **** reparameterize_path(PlannerInfo *root, Path *path,
--- 2063,2073 ----
case T_SubqueryScan:
return create_subqueryscan_path(root, rel, path->pathkeys,
required_outer);
+ case T_ForeignScan:
+ return (Path *) rel->fdwroutine->ReparameterizeForeignPath(root,
+ rel,
+ path,
+ required_outer);
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/catalog/pg_inherits_fn.h
--- b/src/include/catalog/pg_inherits_fn.h
***************
*** 21,26 **** extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode);
--- 21,27 ----
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 */
*** 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,41 ----
RelOptInfo *baserel,
Oid foreigntableid);
+ typedef ForeignPath *(*ReparameterizeForeignPath_function) (PlannerInfo *root,
+ RelOptInfo *baserel,
+ Path *path,
+ Relids required_outer);
+
typedef ForeignScan *(*GetForeignPlan_function) (PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
***************
*** 117,122 **** typedef struct FdwRoutine
--- 122,128 ----
/* 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/include/optimizer/cost.h
--- b/src/include/optimizer/cost.h
***************
*** 143,148 **** extern void final_cost_hashjoin(PlannerInfo *root, HashPath *path,
--- 143,151 ----
extern void cost_subplan(PlannerInfo *root, SubPlan *subplan, Plan *plan);
extern void cost_qual_eval(QualCost *cost, List *quals, PlannerInfo *root);
extern void cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root);
+ extern void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
+ ParamPathInfo *param_info,
+ QualCost *qpqual_cost);
extern void compute_semi_anti_join_factors(PlannerInfo *root,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
*** 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 '@');
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-03-17 08:30 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2014-03-17 08:30 UTC (permalink / raw)
To: fujita.etsuro@lab.ntt.co.jp; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
Hi Fujita-san,
> Thank you for working this patch!
No problem, but my point seems always out of the main target a bit:(
> > | =# alter table passwd add column added int, add column added2 int;
> > | NOTICE: This command affects foreign relation "cf1"
> > | NOTICE: This command affects foreign relation "cf1"
> > | ALTER TABLE
> > | =# select * from passwd;
> > | ERROR: missing data for column "added"
> > | CONTEXT: COPY cf1, line 1: "root:x:0:0:root:/root:/bin/bash"
> > | =#
> >
> > This seems far better than silently performing the command,
> > except for the duplicated message:( New bitmap might required to
> > avoid the duplication..
>
> As I said before, I think it would be better to show this kind of
> information on each of the affected tables whether or not the affected
> one is foreign. I also think it would be better to show it only when
> the user has specified an option to do so, similar to a VERBOSE option
> of other commands. ISTM this should be implemented as a separate
> patch.
Hmm. I *wish* this kind of indication to be with this patch even
only for foreign tables which would have inconsistency
potentially. Expanding to other objects and/or knobs are no
problem to be added later.
> >> Hmm. I tried minimal implementation to do that. This omits cost
> >> recalculation but seems to work as expected. This seems enough if
> >> cost recalc is trivial here.
>
> I think we should redo the cost/size estimate, because for example,
> greater parameterization leads to a smaller rowcount estimate, if I
> understand correctly. In addition, I think this reparameterization
> should be done by the FDW itself, becasuse the FDW has more knowledge
> about it than the PG core. So, I think we should introduce a new FDW
> routine for that, say ReparameterizeForeignPath(), as proposed in
> [1]. Attached is an updated version of the patch. Due to the above
> reason, I removed from the patch the message displaying function you
> added.
>
> Sorry for the delay.
>
> [1]
> http://www.postgresql.org/message-id/530C7464.6020006@lab.ntt.co.jp
I had a rough look on foreign reparameterize stuff. It seems ok
generally.
By the way, Can I have a simple script to build an environment to
run this on?
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-03-18 09:38 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2014-03-18 09:38 UTC (permalink / raw)
To: fujita.etsuro@lab.ntt.co.jp; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
Hello,
> By the way, Can I have a simple script to build an environment to
> run this on?
I built test environment and ran the simple test using
postgres_fdw and got parameterized path from v3 patch on the
following operation as shown there, and v6 also gives one, but I
haven't seen the reparameterization of v6 patch work.
# How could I think to have got it work before?
Do you have any idea to make postgreReparameterizeForeignPath on
foreign (child) tables works effectively?
regards,
====
### on pg1/pg2:
create table pu1 (a int not null, b int not null, c int, d text);
create unique index i_pu1_ab on pu1 (a, b);
create unique index i_pu1_c on pu1 (c);
create table cu11 (like pu1 including all) inherits (pu1);
create table cu12 (like pu1 including all) inherits (pu1);
insert into cu11 (select a / 5, 4 - (a % 5), a, 'cu11' from generate_series(000000, 099999) a);
insert into cu12 (select a / 5, 4 - (a % 5), a, 'cu12' from generate_series(100000, 199999) a);
### on pg1:
create extension postgres_fdw;
create server pg2 foreign data wrapper postgres_fdw options (host '/tmp', port '5433', dbname 'postgres');
create user mapping for current_user server pg2 options (user 'horiguti');
create foreign table _cu11 (a int, b int, c int, d text) server pg2 options (table_name 'cu11', use_remote_estimate 'true');
create foreign table _cu12 (a int, b int, c int, d text) server pg2 options (table_name 'cu12', use_remote_estimate 'true');
create table rpu1 (a int, b int, c int, d text);
alter foreign table _cu11 inherit rpu1;
alter foreign table _cu12 inherit rpu1;
analyze rpu1;
=# explain analyze select pu1.*
from pu1 join rpu1 on (pu1.c = rpu1.c) where pu1.a = 3;
QUERY PLAN
-------------------------------------------------------------------------------
Nested Loop (cost=0.00..2414.57 rows=11 width=19)
(actual time=0.710..7.167 rows=5 loops=1)
-> Append (cost=0.00..30.76 rows=11 width=19)
<local side.. ommitted>
-> Append (cost=0.00..216.68 rows=3 width=4)
(actual time=0.726..1.419 rows=1 loops=5)
-> Seq Scan on rpu1 (cost=0.00..0.00 rows=1 width=4)
(actual time=0.000..0.000 rows=0 loops=5)
Filter: (pu1.c = c)
-> Foreign Scan on _cu11 (cost=100.30..108.34 rows=1 width=4)
(actual time=0.602..0.603 rows=1 loops=5)
-> Foreign Scan on _cu12 (cost=100.30..108.34 rows=1 width=4)
(actual time=0.566..0.566 rows=0 loops=5)
Planning time: 4.452 ms
Total runtime: 7.663 ms
(15 rows)
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-03-20 12:59 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
0 siblings, 2 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-03-20 12:59 UTC (permalink / raw)
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
(2014/03/18 18:38), Kyotaro HORIGUCHI wrote:
>> By the way, Can I have a simple script to build an environment to
>> run this on?
>
> I built test environment and ran the simple test using
> postgres_fdw and got parameterized path from v3 patch on the
> following operation as shown there, and v6 also gives one, but I
> haven't seen the reparameterization of v6 patch work.
>
> # How could I think to have got it work before?
>
> Do you have any idea to make postgreReparameterizeForeignPath on
> foreign (child) tables works effectively?
> =# explain analyze select pu1.*
> from pu1 join rpu1 on (pu1.c = rpu1.c) where pu1.a = 3;
ISTM postgresReparameterizeForeignPath() cannot be called in this query
in principle. Here is a simple example for the case where the
use_remote_estimate option is true:
# On mydatabase
mydatabase=# CREATE TABLE mytable (id INTEGER, x INTEGER);
CREATE TABLE
mydatabase=# INSERT INTO mytable SELECT x, x FROM generate_series(0,
9999) x;
INSERT 0 10000
# On postgres
postgres=# CREATE TABLE inttable (id INTEGER);
CREATE TABLE
postgres=# INSERT INTO inttable SELECT x FROM generate_series(0, 9999) x;
INSERT 0 10000
postgres=# ANALYZE inttable;
ANALYZE
postgres=# CREATE TABLE patest0 (id INTEGER, x INTEGER);
CREATE TABLE
postgres=# CREATE TABLE patest1 () INHERITS (patest0);
CREATE TABLE
postgres=# INSERT INTO patest1 SELECT x, x FROM generate_series(0, 9999) x;
INSERT 0 10000
postgres=# CREATE INDEX patest1_id_idx ON patest1(id);
CREATE INDEX
postgres=# CREATE SERVER myserver FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host 'localhost', dbname 'mydatabase');
CREATE SERVER
postgres=# CREATE USER MAPPING FOR PUBLIC SERVER myserver OPTIONS (user
'pgsql');
CREATE USER MAPPING
postgres=# CREATE FOREIGN TABLE patest2 () INHERITS (patest0) SERVER
myserver OPTIONS (table_name 'mytable');
CREATE FOREIGN TABLE
postgres=# ANALYZE patest0;
ANALYZE
postgres=# ANALYZE patest1;
ANALYZE
postgres=# ANALYZE patest2;
ANALYZE
postgres=# EXPLAIN VERBOSE SELECT * FROM patest0 join (SELECT id FROM
inttable LIMIT 1) ss ON patest0.id = ss.id;
QUERY PLAN
-------------------------------------------------------------------------------------------------
Nested Loop (cost=0.00..478.36 rows=2 width=12)
Output: patest0.id, patest0.x, inttable.id
-> Limit (cost=0.00..0.01 rows=1 width=4)
Output: inttable.id
-> Seq Scan on public.inttable (cost=0.00..145.00 rows=10000
width=4)
Output: inttable.id
-> Append (cost=0.00..478.31 rows=3 width=8)
-> Seq Scan on public.patest0 (cost=0.00..0.00 rows=1 width=8)
Output: patest0.id, patest0.x
Filter: (inttable.id = patest0.id)
-> Index Scan using patest1_id_idx on public.patest1
(cost=0.29..8.30 rows=1 width=8)
Output: patest1.id, patest1.x
Index Cond: (patest1.id = inttable.id)
-> Foreign Scan on public.patest2 (cost=100.00..470.00
rows=1 width=8)
Output: patest2.id, patest2.x
Remote SQL: SELECT id, x FROM public.mytable WHERE
(($1::integer = id))
Planning time: 0.233 ms
(17 rows)
I revised the patch. Patche attached, though I plan to update the
documentation further early next week.
Thanks,
Best regards,
Etsuro Fujita
*** a/contrib/file_fdw/file_fdw.c
--- b/contrib/file_fdw/file_fdw.c
***************
*** 117,122 **** static void fileGetForeignRelSize(PlannerInfo *root,
--- 117,126 ----
static void fileGetForeignPaths(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid);
+ static ForeignPath *fileReparameterizeForeignPath(PlannerInfo *root,
+ RelOptInfo *baserel,
+ Path *path,
+ Relids required_outer);
static ForeignScan *fileGetForeignPlan(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
***************
*** 145,150 **** static bool check_selective_binary_conversion(RelOptInfo *baserel,
--- 149,155 ----
static void estimate_size(PlannerInfo *root, RelOptInfo *baserel,
FileFdwPlanState *fdw_private);
static void estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
+ List *join_conds,
FileFdwPlanState *fdw_private,
Cost *startup_cost, Cost *total_cost);
static int file_acquire_sample_rows(Relation onerel, int elevel,
***************
*** 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,
! NIL, fdw_private,
&startup_cost, &total_cost);
/*
***************
*** 542,547 **** fileGetForeignPaths(PlannerInfo *root,
--- 549,588 ----
}
/*
+ * fileReparameterizeForeignPath
+ * Attempt to modify a given path to have greater parameterization
+ */
+ static ForeignPath *
+ fileReparameterizeForeignPath(PlannerInfo *root,
+ RelOptInfo *baserel,
+ Path *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 estimate */
+ estimate_costs(root, baserel,
+ param_info->ppi_clauses,
+ fdw_private,
+ &startup_cost, &total_cost);
+
+ /* Make a new path */
+ return create_foreignscan_path(root, baserel,
+ param_info->ppi_rows,
+ startup_cost,
+ total_cost,
+ NIL, /* no pathkeys */
+ required_outer,
+ ((ForeignPath *) path)->fdw_private);
+ }
+
+ /*
* fileGetForeignPlan
* Create a ForeignScan plan node for scanning the foreign table
*/
***************
*** 970,981 **** estimate_size(PlannerInfo *root, RelOptInfo *baserel,
--- 1011,1024 ----
*/
static void
estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
+ List *join_conds,
FileFdwPlanState *fdw_private,
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,250 ----
static void postgresGetForeignPaths(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid);
+ static ForeignPath *postgresReparameterizeForeignPath(PlannerInfo *root,
+ RelOptInfo *baserel,
+ Path *path,
+ Relids required_outer);
static ForeignScan *postgresGetForeignPlan(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
***************
*** 342,347 **** postgres_fdw_handler(PG_FUNCTION_ARGS)
--- 346,352 ----
/* 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,
--- 734,780 ----
}
/*
+ * postgresReparameterizeForeignPath
+ * Attempt to modify a given path to have greater parameterization
+ */
+ static ForeignPath *
+ postgresReparameterizeForeignPath(PlannerInfo *root,
+ RelOptInfo *baserel,
+ Path *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 estimate */
+ 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 a 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;
--- 1821,1828 ----
}
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;
--- 1835,1863 ----
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.
</para>
+ <note>
+ <para>
+ 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
+ <xref linkend="ddl-inherit">) to off-load to multiple servers.
+ </para>
+ </note>
+
<sect2 id="ddl-constraints-check-constraints">
<title>Check Constraints</title>
***************
*** 2017,2024 **** CREATE TABLE capitals (
</para>
<para>
! In <productname>PostgreSQL</productname>, 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 ----
</para>
<para>
! In <productname>PostgreSQL</productname>, 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/ref/alter_foreign_table.sgml
--- b/doc/src/sgml/ref/alter_foreign_table.sgml
***************
*** 42,47 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 42,49 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 178,183 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 180,205 ----
</varlistentry>
<varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be a plain table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
<listitem>
<para>
***************
*** 306,311 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 328,343 ----
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">parent_name</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be a plain table.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
*** a/doc/src/sgml/ref/create_foreign_table.sgml
--- b/doc/src/sgml/ref/create_foreign_table.sgml
***************
*** 22,27 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 22,28 ----
<replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 160,177 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for the
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 82,90 **** int default_statistics_target = 100;
/* A few variables that don't seem worth passing around as parameters */
static MemoryContext anl_context = NULL;
static BufferAccessStrategy vac_strategy;
-
static void do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
AcquireSampleRowsFunc acquirefunc, BlockNumber relpages,
bool inh, int elevel);
--- 82,90 ----
/* 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;
static void do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
AcquireSampleRowsFunc acquirefunc, BlockNumber relpages,
bool inh, int elevel);
***************
*** 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;
--- 115,123 ----
* 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)
--- 131,137 ----
elevel = DEBUG2;
/* Set up static variables */
+ vac_mode = vacmode;
vac_strategy = bstrategy;
/*
***************
*** 1452,1459 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1455,1464 ----
{
List *tableOIDs;
Relation *rels;
+ AcquireSampleRowsFunc *acquirefunc;
double *relblocks;
double totalblocks;
+ Relation saved_rel;
int numrows,
nrels,
i;
***************
*** 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 \"%s\" inheritance tree --- 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);
! /* 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);
! /* 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);
! /* 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 */
! /* 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,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,4763 ----
/* 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 inheritance tree \"%s\" because it contains foreign table \"%s\"",
+ RelationGetRelationName(rel),
+ 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
--- 5358,5364 ----
/* 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
--- 5750,5763 ----
/* 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 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,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 */
--- 5768,5784 ----
* 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);
--- 7258,7264 ----
/* 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,
--- 7593,7602 ----
* alter would put them out of step.
*/
if (recurse)
! {
! /* 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,
*** a/src/backend/commands/vacuum.c
--- b/src/backend/commands/vacuum.c
***************
*** 98,103 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
--- 98,104 ----
BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel)
{
const char *stmttype;
+ VacuumMode vacmode;
volatile bool in_outer_xact,
use_own_xacts;
List *relations;
***************
*** 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)
+ 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,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, vacmode, 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 <math.h>
+ #include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
***************
*** 2062,2067 **** reparameterize_path(PlannerInfo *root, Path *path,
--- 2063,2075 ----
case T_SubqueryScan:
return create_subqueryscan_path(root, rel, path->pathkeys,
required_outer);
+ case T_ForeignScan:
+ if (rel->fdwroutine->ReparameterizeForeignPath != NULL)
+ return (Path *)
+ rel->fdwroutine->ReparameterizeForeignPath(root,
+ rel,
+ path,
+ required_outer);
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,41 ----
RelOptInfo *baserel,
Oid foreigntableid);
+ typedef ForeignPath *(*ReparameterizeForeignPath_function) (PlannerInfo *root,
+ RelOptInfo *baserel,
+ Path *path,
+ Relids required_outer);
+
typedef ForeignScan *(*GetForeignPlan_function) (PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
***************
*** 117,122 **** typedef struct FdwRoutine
--- 122,128 ----
/* 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 '@');
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/plain] foreign_inherit-v7.patch (47.3K, ../../532AE62A.6020307@lab.ntt.co.jp/2-foreign_inherit-v7.patch)
download | inline diff:
*** a/contrib/file_fdw/file_fdw.c
--- b/contrib/file_fdw/file_fdw.c
***************
*** 117,122 **** static void fileGetForeignRelSize(PlannerInfo *root,
--- 117,126 ----
static void fileGetForeignPaths(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid);
+ static ForeignPath *fileReparameterizeForeignPath(PlannerInfo *root,
+ RelOptInfo *baserel,
+ Path *path,
+ Relids required_outer);
static ForeignScan *fileGetForeignPlan(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
***************
*** 145,150 **** static bool check_selective_binary_conversion(RelOptInfo *baserel,
--- 149,155 ----
static void estimate_size(PlannerInfo *root, RelOptInfo *baserel,
FileFdwPlanState *fdw_private);
static void estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
+ List *join_conds,
FileFdwPlanState *fdw_private,
Cost *startup_cost, Cost *total_cost);
static int file_acquire_sample_rows(Relation onerel, int elevel,
***************
*** 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,
! NIL, fdw_private,
&startup_cost, &total_cost);
/*
***************
*** 542,547 **** fileGetForeignPaths(PlannerInfo *root,
--- 549,588 ----
}
/*
+ * fileReparameterizeForeignPath
+ * Attempt to modify a given path to have greater parameterization
+ */
+ static ForeignPath *
+ fileReparameterizeForeignPath(PlannerInfo *root,
+ RelOptInfo *baserel,
+ Path *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 estimate */
+ estimate_costs(root, baserel,
+ param_info->ppi_clauses,
+ fdw_private,
+ &startup_cost, &total_cost);
+
+ /* Make a new path */
+ return create_foreignscan_path(root, baserel,
+ param_info->ppi_rows,
+ startup_cost,
+ total_cost,
+ NIL, /* no pathkeys */
+ required_outer,
+ ((ForeignPath *) path)->fdw_private);
+ }
+
+ /*
* fileGetForeignPlan
* Create a ForeignScan plan node for scanning the foreign table
*/
***************
*** 970,981 **** estimate_size(PlannerInfo *root, RelOptInfo *baserel,
--- 1011,1024 ----
*/
static void
estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
+ List *join_conds,
FileFdwPlanState *fdw_private,
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,250 ----
static void postgresGetForeignPaths(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid);
+ static ForeignPath *postgresReparameterizeForeignPath(PlannerInfo *root,
+ RelOptInfo *baserel,
+ Path *path,
+ Relids required_outer);
static ForeignScan *postgresGetForeignPlan(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
***************
*** 342,347 **** postgres_fdw_handler(PG_FUNCTION_ARGS)
--- 346,352 ----
/* 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,
--- 734,780 ----
}
/*
+ * postgresReparameterizeForeignPath
+ * Attempt to modify a given path to have greater parameterization
+ */
+ static ForeignPath *
+ postgresReparameterizeForeignPath(PlannerInfo *root,
+ RelOptInfo *baserel,
+ Path *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 estimate */
+ 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 a 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;
--- 1821,1828 ----
}
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;
--- 1835,1863 ----
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.
</para>
+ <note>
+ <para>
+ 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
+ <xref linkend="ddl-inherit">) to off-load to multiple servers.
+ </para>
+ </note>
+
<sect2 id="ddl-constraints-check-constraints">
<title>Check Constraints</title>
***************
*** 2017,2024 **** CREATE TABLE capitals (
</para>
<para>
! In <productname>PostgreSQL</productname>, 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 ----
</para>
<para>
! In <productname>PostgreSQL</productname>, 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/ref/alter_foreign_table.sgml
--- b/doc/src/sgml/ref/alter_foreign_table.sgml
***************
*** 42,47 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 42,49 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 178,183 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 180,205 ----
</varlistentry>
<varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be a plain table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
<listitem>
<para>
***************
*** 306,311 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 328,343 ----
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">parent_name</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be a plain table.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
*** a/doc/src/sgml/ref/create_foreign_table.sgml
--- b/doc/src/sgml/ref/create_foreign_table.sgml
***************
*** 22,27 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 22,28 ----
<replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 160,177 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for the
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 82,90 **** int default_statistics_target = 100;
/* A few variables that don't seem worth passing around as parameters */
static MemoryContext anl_context = NULL;
static BufferAccessStrategy vac_strategy;
-
static void do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
AcquireSampleRowsFunc acquirefunc, BlockNumber relpages,
bool inh, int elevel);
--- 82,90 ----
/* 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;
static void do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
AcquireSampleRowsFunc acquirefunc, BlockNumber relpages,
bool inh, int elevel);
***************
*** 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;
--- 115,123 ----
* 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)
--- 131,137 ----
elevel = DEBUG2;
/* Set up static variables */
+ vac_mode = vacmode;
vac_strategy = bstrategy;
/*
***************
*** 1452,1459 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1455,1464 ----
{
List *tableOIDs;
Relation *rels;
+ AcquireSampleRowsFunc *acquirefunc;
double *relblocks;
double totalblocks;
+ Relation saved_rel;
int numrows,
nrels,
i;
***************
*** 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 \"%s\" inheritance tree --- 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);
! /* 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);
! /* 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);
! /* 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 */
! /* 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,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,4763 ----
/* 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 inheritance tree \"%s\" because it contains foreign table \"%s\"",
+ RelationGetRelationName(rel),
+ 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
--- 5358,5364 ----
/* 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
--- 5750,5763 ----
/* 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 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,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 */
--- 5768,5784 ----
* 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);
--- 7258,7264 ----
/* 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,
--- 7593,7602 ----
* alter would put them out of step.
*/
if (recurse)
! {
! /* 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,
*** a/src/backend/commands/vacuum.c
--- b/src/backend/commands/vacuum.c
***************
*** 98,103 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
--- 98,104 ----
BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel)
{
const char *stmttype;
+ VacuumMode vacmode;
volatile bool in_outer_xact,
use_own_xacts;
List *relations;
***************
*** 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)
+ 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,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, vacmode, 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 <math.h>
+ #include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
***************
*** 2062,2067 **** reparameterize_path(PlannerInfo *root, Path *path,
--- 2063,2075 ----
case T_SubqueryScan:
return create_subqueryscan_path(root, rel, path->pathkeys,
required_outer);
+ case T_ForeignScan:
+ if (rel->fdwroutine->ReparameterizeForeignPath != NULL)
+ return (Path *)
+ rel->fdwroutine->ReparameterizeForeignPath(root,
+ rel,
+ path,
+ required_outer);
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,41 ----
RelOptInfo *baserel,
Oid foreigntableid);
+ typedef ForeignPath *(*ReparameterizeForeignPath_function) (PlannerInfo *root,
+ RelOptInfo *baserel,
+ Path *path,
+ Relids required_outer);
+
typedef ForeignScan *(*GetForeignPlan_function) (PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
***************
*** 117,122 **** typedef struct FdwRoutine
--- 122,128 ----
/* 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 '@');
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-03-24 01:47 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-03-24 01:47 UTC (permalink / raw)
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
(2014/03/20 21:59), Etsuro Fujita wrote:
> Here is a simple example for the case where the
> use_remote_estimate option is true:
Sorry, I incorrectly wrote it. The following example is for the case
where the option is *false*, as you see.
> # On mydatabase
>
> mydatabase=# CREATE TABLE mytable (id INTEGER, x INTEGER);
> CREATE TABLE
> mydatabase=# INSERT INTO mytable SELECT x, x FROM generate_series(0,
> 9999) x;
> INSERT 0 10000
>
> # On postgres
>
> postgres=# CREATE TABLE inttable (id INTEGER);
> CREATE TABLE
> postgres=# INSERT INTO inttable SELECT x FROM generate_series(0, 9999) x;
> INSERT 0 10000
> postgres=# ANALYZE inttable;
> ANALYZE
>
> postgres=# CREATE TABLE patest0 (id INTEGER, x INTEGER);
> CREATE TABLE
> postgres=# CREATE TABLE patest1 () INHERITS (patest0);
> CREATE TABLE
> postgres=# INSERT INTO patest1 SELECT x, x FROM generate_series(0, 9999) x;
> INSERT 0 10000
> postgres=# CREATE INDEX patest1_id_idx ON patest1(id);
> CREATE INDEX
> postgres=# CREATE SERVER myserver FOREIGN DATA WRAPPER postgres_fdw
> OPTIONS (host 'localhost', dbname 'mydatabase');
> CREATE SERVER
> postgres=# CREATE USER MAPPING FOR PUBLIC SERVER myserver OPTIONS (user
> 'pgsql');
> CREATE USER MAPPING
> postgres=# CREATE FOREIGN TABLE patest2 () INHERITS (patest0) SERVER
> myserver OPTIONS (table_name 'mytable');
> CREATE FOREIGN TABLE
> postgres=# ANALYZE patest0;
> ANALYZE
> postgres=# ANALYZE patest1;
> ANALYZE
> postgres=# ANALYZE patest2;
> ANALYZE
> postgres=# EXPLAIN VERBOSE SELECT * FROM patest0 join (SELECT id FROM
> inttable LIMIT 1) ss ON patest0.id = ss.id;
> QUERY PLAN
> -------------------------------------------------------------------------------------------------
>
> Nested Loop (cost=0.00..478.36 rows=2 width=12)
> Output: patest0.id, patest0.x, inttable.id
> -> Limit (cost=0.00..0.01 rows=1 width=4)
> Output: inttable.id
> -> Seq Scan on public.inttable (cost=0.00..145.00 rows=10000
> width=4)
> Output: inttable.id
> -> Append (cost=0.00..478.31 rows=3 width=8)
> -> Seq Scan on public.patest0 (cost=0.00..0.00 rows=1 width=8)
> Output: patest0.id, patest0.x
> Filter: (inttable.id = patest0.id)
> -> Index Scan using patest1_id_idx on public.patest1
> (cost=0.29..8.30 rows=1 width=8)
> Output: patest1.id, patest1.x
> Index Cond: (patest1.id = inttable.id)
> -> Foreign Scan on public.patest2 (cost=100.00..470.00
> rows=1 width=8)
> Output: patest2.id, patest2.x
> Remote SQL: SELECT id, x FROM public.mytable WHERE
> (($1::integer = id))
> Planning time: 0.233 ms
> (17 rows)
Sorry for the delay.
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-03-25 12:48 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 0 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-03-25 12:48 UTC (permalink / raw)
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
(2014/03/20 21:59), Etsuro Fujita wrote:
> I revised the patch. Patche attached, though I plan to update the
> documentation further early next week.
I updated the documentation further and revise the patch further.
Attached is an updated version of the patch.
Thanks,
Best regards,
Etsuro Fujita
*** 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.
</para>
+ <note>
+ <para>
+ 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
+ <xref linkend="ddl-inherit">) to off-load to multiple servers.
+ </para>
+ </note>
+
<sect2 id="ddl-constraints-check-constraints">
<title>Check Constraints</title>
***************
*** 2017,2024 **** CREATE TABLE capitals (
</para>
<para>
! In <productname>PostgreSQL</productname>, 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 ----
</para>
<para>
! In <productname>PostgreSQL</productname>, 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 ----
<para>
<programlisting>
+ ForeignPath *
+ ReparameterizeForeignPath (PlannerInfo *root,
+ RelOptInfo *baserel,
+ Oid foreigntableid,
+ ForeignPath *path,
+ Relids required_outer);
+ </programlisting>
+
+ Create an access path for a scan on a foreign table using join
+ clauses, which is called a <quote>parameterized path</>.
+ This is called during query planning.
+ The parameters are as for <function>GetForeignRelSize</>, plus
+ the <structname>ForeignPath</> (previously produced by
+ <function>GetForeignPaths</>), and the IDs of all other tables
+ that provide the join clauses.
+ </para>
+
+ <para>
+ 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.
+ </para>
+
+ <para>
+ See <xref linkend="fdw-planning"> for additional information.
+ </para>
+
+ <para>
+ <programlisting>
ForeignScan *
GetForeignPlan (PlannerInfo *root,
RelOptInfo *baserel,
***************
*** 804,813 **** GetForeignServerByName(const char *name, bool missing_ok);
<para>
The FDW callback functions <function>GetForeignRelSize</>,
! <function>GetForeignPaths</>, <function>GetForeignPlan</>, and
! <function>PlanForeignModify</> must fit into the workings of the
! <productname>PostgreSQL</> planner. Here are some notes about what
! they must do.
</para>
<para>
--- 835,844 ----
<para>
The FDW callback functions <function>GetForeignRelSize</>,
! <function>GetForeignPaths</>, <function>ReparameterizeForeignPath</>,
! <function>GetForeignPlan</>, and <function>PlanForeignModify</> must fit
! into the workings of the <productname>PostgreSQL</> planner. Here are
! some notes about what they must do.
</para>
<para>
***************
*** 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 <literal>baserel</> node is created.
It is useful for passing information forward from
! <function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
! <function>GetForeignPaths</> to <function>GetForeignPlan</>, thereby
! avoiding recalculation.
</para>
<para>
! <function>GetForeignPaths</> can identify the meaning of different
! access paths by storing private information in the
! <structfield>fdw_private</> field of <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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 <literal>baserel</> node is created.
It is useful for passing information forward from
! <function>GetForeignRelSize</> to <function>GetForeignPaths</>,
! <function>GetForeignPaths</> to <function>ReparameterizeForeignPath</>
! and/or <function>GetForeignPaths</> to <function>GetForeignPlan</>,
! thereby avoiding recalculation.
</para>
<para>
! <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
! can identify the meaning of different access paths by storing private
! information in the <structfield>fdw_private</> field of
! <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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);
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
! clause should happen during <function>GetForeignPaths</>, since it would
! affect the cost estimate for the path. The path's
! <structfield>fdw_private</> field would probably include a pointer to
! the identified clause's <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from scan_clauses,
but add the <replaceable>sub_expression</> to <structfield>fdw_exprs</>
to ensure that it gets massaged into executable form. It would probably
--- 920,930 ----
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
! clause should happen during <function>GetForeignPaths</> or
! <function>ReparameterizeForeignPath</>, since it would affect the cost
! estimate for the path. The path's <structfield>fdw_private</> field
! would probably include a pointer to the identified clause's
! <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from scan_clauses,
but add the <replaceable>sub_expression</> to <structfield>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 ] <replaceable class="PARAMETER">name</replaceab
--- 42,51 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ ADD <replaceable class="PARAMETER">table_constraint</replaceable>
+ DROP CONSTRAINT [ IF EXISTS ] <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 149,154 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 153,202 ----
</varlistentry>
<varlistentry>
+ <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable> [ NOT VALID ]</literal></term>
+ <listitem>
+ <para>
+ This form adds a new constraint to a table using the same syntax as
+ <xref linkend="SQL-CREATEFOREIGNTABLE">.
+ 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.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DROP CONSTRAINT [ IF EXISTS ]</literal></term>
+ <listitem>
+ <para>
+ This form drops the specified constraint on a table.
+ If <literal>IF EXISTS</literal> is specified and the constraint
+ does not exist, no error is thrown. In this case a notice is issued instead.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OWNER</literal></term>
<listitem>
<para>
***************
*** 270,275 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 318,341 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">table_constraint</replaceable></term>
+ <listitem>
+ <para>
+ New table constraint for the table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">constraint_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing constraint to drop.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>CASCADE</literal></term>
<listitem>
<para>
***************
*** 306,311 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 372,387 ----
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">parent_name</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
***************
*** 319,328 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint is
! added, or a column type is changed with <literal>SET DATA TYPE</>. It is
! the user's responsibility to ensure that the table definition matches the
! remote side.
</para>
<para>
--- 395,404 ----
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint or
! <literal>CHECK</> constraint is added, or a column type is changed with
! <literal>SET DATA TYPE</>. It is the user's responsibility to ensure that
! the table definition matches the remote side.
</para>
<para>
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 937,942 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 937,948 ----
</para>
<para>
+ Since it is not permitted to add an <literal>oid</literal> system
+ column to foreign tables, a recursive <literal>SET STORAGE</literal>
+ operation will be rejected if any descendant tables are foreign.
+ </para>
+
+ <para>
The <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>,
and <literal>TABLESPACE</> actions never recurse to descendant tables;
that is, they always act as though <literal>ONLY</> were specified.
***************
*** 945,950 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 951,969 ----
</para>
<para>
+ When adding a <literal>CHECK</> constraint with the <literal>NOT VALID
+ </literal> option recursively, an inherited constraint on a descendant
+ table that is foreign will be marked valid without checking
+ consistency with that foreign server.
+ </para>
+
+ <para>
+ A recursive <literal>SET STORAGE</literal> operation will make the
+ storage mode for a descendant table's column unchanged if the table is
+ foreign.
+ </para>
+
+ <para>
Changing any part of a system catalog table is not permitted.
</para>
*** a/doc/src/sgml/ref/analyze.sgml
--- b/doc/src/sgml/ref/analyze.sgml
***************
*** 200,205 **** ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
--- 200,212 ----
</para>
<para>
+ 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
+ <command>ANALYZE</command>, the command prints a warning and does nothing.
+ </para>
+
+ <para>
If the table being analyzed is completely empty, <command>ANALYZE</command>
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 ****
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
--- 19,29 ----
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
! | <replaceable>table_constraint</replaceable> }
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 30,36 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 32,44 ----
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ NOT NULL |
NULL |
+ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ] |
DEFAULT <replaceable>default_expr</replaceable> }
+
+ <phrase>and <replaceable class="PARAMETER">table_constraint</replaceable> is:</phrase>
+
+ [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
+ { CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ] }
</synopsis>
</refsynopsisdiv>
***************
*** 138,143 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 146,177 ----
</varlistentry>
<varlistentry>
+ <term><literal>CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ] </literal></term>
+ <listitem>
+ <para>
+ The <literal>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.
+ </para>
+
+ <para>
+ Currently, <literal>CHECK</literal> expressions cannot contain
+ subqueries nor refer to variables other than columns of the
+ current row. The system column <literal>tableoid</literal>
+ may be referenced, but not any other system column.
+ </para>
+
+ <para>
+ A constraint marked with <literal>NO INHERIT</> will not propagate to
+ child tables.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>DEFAULT
<replaceable>default_expr</replaceable></literal></term>
<listitem>
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 193,210 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for the
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
*** 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 <math.h>
+ #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 '@');
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/plain] foreign_inherit-v8.patch (61.5K, ../../53317B04.9050901@lab.ntt.co.jp/2-foreign_inherit-v8.patch)
download | inline diff:
*** 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.
</para>
+ <note>
+ <para>
+ 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
+ <xref linkend="ddl-inherit">) to off-load to multiple servers.
+ </para>
+ </note>
+
<sect2 id="ddl-constraints-check-constraints">
<title>Check Constraints</title>
***************
*** 2017,2024 **** CREATE TABLE capitals (
</para>
<para>
! In <productname>PostgreSQL</productname>, 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 ----
</para>
<para>
! In <productname>PostgreSQL</productname>, 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 ----
<para>
<programlisting>
+ ForeignPath *
+ ReparameterizeForeignPath (PlannerInfo *root,
+ RelOptInfo *baserel,
+ Oid foreigntableid,
+ ForeignPath *path,
+ Relids required_outer);
+ </programlisting>
+
+ Create an access path for a scan on a foreign table using join
+ clauses, which is called a <quote>parameterized path</>.
+ This is called during query planning.
+ The parameters are as for <function>GetForeignRelSize</>, plus
+ the <structname>ForeignPath</> (previously produced by
+ <function>GetForeignPaths</>), and the IDs of all other tables
+ that provide the join clauses.
+ </para>
+
+ <para>
+ 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.
+ </para>
+
+ <para>
+ See <xref linkend="fdw-planning"> for additional information.
+ </para>
+
+ <para>
+ <programlisting>
ForeignScan *
GetForeignPlan (PlannerInfo *root,
RelOptInfo *baserel,
***************
*** 804,813 **** GetForeignServerByName(const char *name, bool missing_ok);
<para>
The FDW callback functions <function>GetForeignRelSize</>,
! <function>GetForeignPaths</>, <function>GetForeignPlan</>, and
! <function>PlanForeignModify</> must fit into the workings of the
! <productname>PostgreSQL</> planner. Here are some notes about what
! they must do.
</para>
<para>
--- 835,844 ----
<para>
The FDW callback functions <function>GetForeignRelSize</>,
! <function>GetForeignPaths</>, <function>ReparameterizeForeignPath</>,
! <function>GetForeignPlan</>, and <function>PlanForeignModify</> must fit
! into the workings of the <productname>PostgreSQL</> planner. Here are
! some notes about what they must do.
</para>
<para>
***************
*** 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 <literal>baserel</> node is created.
It is useful for passing information forward from
! <function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
! <function>GetForeignPaths</> to <function>GetForeignPlan</>, thereby
! avoiding recalculation.
</para>
<para>
! <function>GetForeignPaths</> can identify the meaning of different
! access paths by storing private information in the
! <structfield>fdw_private</> field of <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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 <literal>baserel</> node is created.
It is useful for passing information forward from
! <function>GetForeignRelSize</> to <function>GetForeignPaths</>,
! <function>GetForeignPaths</> to <function>ReparameterizeForeignPath</>
! and/or <function>GetForeignPaths</> to <function>GetForeignPlan</>,
! thereby avoiding recalculation.
</para>
<para>
! <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
! can identify the meaning of different access paths by storing private
! information in the <structfield>fdw_private</> field of
! <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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);
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
! clause should happen during <function>GetForeignPaths</>, since it would
! affect the cost estimate for the path. The path's
! <structfield>fdw_private</> field would probably include a pointer to
! the identified clause's <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from scan_clauses,
but add the <replaceable>sub_expression</> to <structfield>fdw_exprs</>
to ensure that it gets massaged into executable form. It would probably
--- 920,930 ----
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
! clause should happen during <function>GetForeignPaths</> or
! <function>ReparameterizeForeignPath</>, since it would affect the cost
! estimate for the path. The path's <structfield>fdw_private</> field
! would probably include a pointer to the identified clause's
! <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from scan_clauses,
but add the <replaceable>sub_expression</> to <structfield>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 ] <replaceable class="PARAMETER">name</replaceab
--- 42,51 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ ADD <replaceable class="PARAMETER">table_constraint</replaceable>
+ DROP CONSTRAINT [ IF EXISTS ] <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 149,154 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 153,202 ----
</varlistentry>
<varlistentry>
+ <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable> [ NOT VALID ]</literal></term>
+ <listitem>
+ <para>
+ This form adds a new constraint to a table using the same syntax as
+ <xref linkend="SQL-CREATEFOREIGNTABLE">.
+ 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.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DROP CONSTRAINT [ IF EXISTS ]</literal></term>
+ <listitem>
+ <para>
+ This form drops the specified constraint on a table.
+ If <literal>IF EXISTS</literal> is specified and the constraint
+ does not exist, no error is thrown. In this case a notice is issued instead.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OWNER</literal></term>
<listitem>
<para>
***************
*** 270,275 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 318,341 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">table_constraint</replaceable></term>
+ <listitem>
+ <para>
+ New table constraint for the table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">constraint_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing constraint to drop.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>CASCADE</literal></term>
<listitem>
<para>
***************
*** 306,311 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 372,387 ----
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">parent_name</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
***************
*** 319,328 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint is
! added, or a column type is changed with <literal>SET DATA TYPE</>. It is
! the user's responsibility to ensure that the table definition matches the
! remote side.
</para>
<para>
--- 395,404 ----
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint or
! <literal>CHECK</> constraint is added, or a column type is changed with
! <literal>SET DATA TYPE</>. It is the user's responsibility to ensure that
! the table definition matches the remote side.
</para>
<para>
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 937,942 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 937,948 ----
</para>
<para>
+ Since it is not permitted to add an <literal>oid</literal> system
+ column to foreign tables, a recursive <literal>SET STORAGE</literal>
+ operation will be rejected if any descendant tables are foreign.
+ </para>
+
+ <para>
The <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>,
and <literal>TABLESPACE</> actions never recurse to descendant tables;
that is, they always act as though <literal>ONLY</> were specified.
***************
*** 945,950 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 951,969 ----
</para>
<para>
+ When adding a <literal>CHECK</> constraint with the <literal>NOT VALID
+ </literal> option recursively, an inherited constraint on a descendant
+ table that is foreign will be marked valid without checking
+ consistency with that foreign server.
+ </para>
+
+ <para>
+ A recursive <literal>SET STORAGE</literal> operation will make the
+ storage mode for a descendant table's column unchanged if the table is
+ foreign.
+ </para>
+
+ <para>
Changing any part of a system catalog table is not permitted.
</para>
*** a/doc/src/sgml/ref/analyze.sgml
--- b/doc/src/sgml/ref/analyze.sgml
***************
*** 200,205 **** ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
--- 200,212 ----
</para>
<para>
+ 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
+ <command>ANALYZE</command>, the command prints a warning and does nothing.
+ </para>
+
+ <para>
If the table being analyzed is completely empty, <command>ANALYZE</command>
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 ****
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
--- 19,29 ----
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
! | <replaceable>table_constraint</replaceable> }
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 30,36 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 32,44 ----
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ NOT NULL |
NULL |
+ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ] |
DEFAULT <replaceable>default_expr</replaceable> }
+
+ <phrase>and <replaceable class="PARAMETER">table_constraint</replaceable> is:</phrase>
+
+ [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
+ { CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ] }
</synopsis>
</refsynopsisdiv>
***************
*** 138,143 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 146,177 ----
</varlistentry>
<varlistentry>
+ <term><literal>CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ] </literal></term>
+ <listitem>
+ <para>
+ The <literal>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.
+ </para>
+
+ <para>
+ Currently, <literal>CHECK</literal> expressions cannot contain
+ subqueries nor refer to variables other than columns of the
+ current row. The system column <literal>tableoid</literal>
+ may be referenced, but not any other system column.
+ </para>
+
+ <para>
+ A constraint marked with <literal>NO INHERIT</> will not propagate to
+ child tables.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>DEFAULT
<replaceable>default_expr</replaceable></literal></term>
<listitem>
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 193,210 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for the
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
*** 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 <math.h>
+ #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 '@');
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-03-26 08:14 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2014-03-26 08:14 UTC (permalink / raw)
To: fujita.etsuro@lab.ntt.co.jp; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
Hello, I could see reparameterize for foreign path to work
effectively thanks to your advice. The key point was setting
use_remote_estimate to false and existence of another child to
get parameterized path in prior stages.
The overall patch was applied on HEAD and compiled cleanly except
for a warning.
> analyze.c: In function $B!F(Bacquire_inherited_sample_rows$B!G(B:
> analyze.c:1461: warning: unused variable $B!F(Bsaved_rel$B!G(B
As for postgres-fdw, the point I felt uneasy in previous patch
was fixed already:) - which was coping with omission of
ReparameterizeForeignPath.
And for file-fdw, you made a change to re-create foreignscan node
instead of the previous copy-and-modify. Is the reason you did it
that you considered the cost of 're-checking whether to
selectively perform binary conversion' is low enough? Or other
reasons?
Finally, although I insist the necessity of the warning for child
foreign tables on alter table, if you belive it to be put off,
I'll compromise by putting a note to CF-app that last judgement
is left to committer.
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-03-27 01:49 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
0 siblings, 2 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-03-27 01:49 UTC (permalink / raw)
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
Hi Horiguchi-san,
(2014/03/26 17:14), Kyotaro HORIGUCHI wrote:
> The overall patch was applied on HEAD and compiled cleanly except
> for a warning.
>
>> analyze.c: In function $B!F(Bacquire_inherited_sample_rows$B!G(B:
>> analyze.c:1461: warning: unused variable $B!F(Bsaved_rel$B!G(B
I've fixed this in the latest version (v8) of the patch.
> And for file-fdw, you made a change to re-create foreignscan node
> instead of the previous copy-and-modify. Is the reason you did it
> that you considered the cost of 're-checking whether to
> selectively perform binary conversion' is low enough? Or other
> reasons?
The reason is that we get the result of the recheck from
path->fdw_private. Sorry, I'd forgotten it. So, I modified the code to
simply call create_foreignscan_path().
> Finally, although I insist the necessity of the warning for child
> foreign tables on alter table, if you belive it to be put off,
> I'll compromise by putting a note to CF-app that last judgement
> is left to committer.
OK So, if there are no objections of other, I'll mark this patch as
"ready for committer" and do that.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-03-27 08:09 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2014-03-27 08:09 UTC (permalink / raw)
To: fujita.etsuro@lab.ntt.co.jp; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
Hello,
> >> analyze.c: In function $B!F(Bacquire_inherited_sample_rows$B!G(B:
> >> analyze.c:1461: warning: unused variable $B!F(Bsaved_rel$B!G(B
>
> I've fixed this in the latest version (v8) of the patch.
Mmm. sorry. I missed v8 patch. Then I had a look on that and have
a question.
You've added a check for relkind of baserel of the foreign path
to be reparameterized. If this should be an assertion - not a
conditional branch -, it would be better to put the assertion in
create_foreignscan_path instead of there.
=====
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1723,6 +1723,7 @@ create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel,
List *fdw_private)
{
ForeignPath *pathnode = makeNode(ForeignPath);
+ Assert(rel->rtekind == RTE_RELATION);
pathnode->path.pathtype = T_ForeignScan;
pathnode->path.parent = rel;
=====
> > And for file-fdw, you made a change to re-create foreignscan node
> > instead of the previous copy-and-modify. Is the reason you did it
> > that you considered the cost of 're-checking whether to
> > selectively perform binary conversion' is low enough? Or other
> > reasons?
>
> The reason is that we get the result of the recheck from
> path->fdw_private. Sorry, I'd forgotten it. So, I modified the code to
> simply call create_foreignscan_path().
Anyway you new code seems closer to the basics and the gain by
the previous optimization don't seem to be significant..
> > Finally, although I insist the necessity of the warning for child
> > foreign tables on alter table, if you belive it to be put off,
> > I'll compromise by putting a note to CF-app that last judgement
> > is left to committer.
>
> OK So, if there are no objections of other, I'll mark this patch as
> "ready for committer" and do that.
Thank you.
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-03-27 08:55 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-03-27 08:55 UTC (permalink / raw)
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
Hi Horiguchi-san,
(2014/03/27 17:09), Kyotaro HORIGUCHI wrote:
>>>> analyze.c: In function $B!F(Bacquire_inherited_sample_rows$B!G(B:
>>>> analyze.c:1461: warning: unused variable $B!F(Bsaved_rel$B!G(B
>>
>> I've fixed this in the latest version (v8) of the patch.
>
> Mmm. sorry. I missed v8 patch. Then I had a look on that and have
> a question.
Thank you for the review!
> You've added a check for relkind of baserel of the foreign path
> to be reparameterized. If this should be an assertion - not a
> conditional branch -, it would be better to put the assertion in
> create_foreignscan_path instead of there.
>
> =====
> --- a/src/backend/optimizer/util/pathnode.c
> +++ b/src/backend/optimizer/util/pathnode.c
> @@ -1723,6 +1723,7 @@ create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel,
> List *fdw_private)
> {
> ForeignPath *pathnode = makeNode(ForeignPath);
> + Assert(rel->rtekind == RTE_RELATION);
>
> pathnode->path.pathtype = T_ForeignScan;
> pathnode->path.parent = rel;
> =====
Maybe I'm missing the point, but I don't think it'd be better to put the
assertion in create_foreignscan_path(). And I think it'd be the caller'
responsiblity to ensure that equality, as any other pathnode creation
routine for a baserel in pathnode.c assumes that equality.
>>> And for file-fdw, you made a change to re-create foreignscan node
>>> instead of the previous copy-and-modify. Is the reason you did it
>>> that you considered the cost of 're-checking whether to
>>> selectively perform binary conversion' is low enough? Or other
>>> reasons?
>>
>> The reason is that we get the result of the recheck from
>> path->fdw_private. Sorry, I'd forgotten it. So, I modified the code to
>> simply call create_foreignscan_path().
>
> Anyway you new code seems closer to the basics and the gain by
> the previous optimization don't seem to be significant..
Yeah, that's true. I have to admit that the previous optimization is
nonsense.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-03-27 12:53 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-03-27 12:53 UTC (permalink / raw)
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
(2014/03/27 10:49), Etsuro Fujita wrote:
> (2014/03/26 17:14), Kyotaro HORIGUCHI wrote:
>> Finally, although I insist the necessity of the warning for child
>> foreign tables on alter table, if you belive it to be put off,
>> I'll compromise by putting a note to CF-app that last judgement
>> is left to committer.
>
> OK So, if there are no objections of other, I'll mark this patch as
> "ready for committer" and do that.
I'll do it right now, since there seems to be no objections of others.
I've revised the patch a bit further, mainly the documentation. Patch
attached.
Note: as mentioned above, Horiguchi-san insisted that warning
functionality for recursive ALTER TABLE operations for inheritance trees
that contain one or more children that are foreign. However, that
functionality hasn't been included in the patch, since ISTM that that
functionality should be implemented as a separate patch [1]. We leave
this for commiters to decide.
[1] http://www.postgresql.org/message-id/5321ABD2.6000104@lab.ntt.co.jp
Thanks,
Best regards,
Etsuro Fujita
*** 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/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
***************
*** 162,167 **** GetForeignPaths (PlannerInfo *root,
--- 162,198 ----
<para>
<programlisting>
+ ForeignPath *
+ ReparameterizeForeignPath (PlannerInfo *root,
+ RelOptInfo *baserel,
+ Oid foreigntableid,
+ ForeignPath *path,
+ Relids required_outer);
+ </programlisting>
+
+ Create an access path for a scan on a foreign table using join
+ clauses, which is called a <quote>parameterized path</>.
+ This is called during query planning.
+ The parameters are as for <function>GetForeignRelSize</>, plus
+ the <structname>ForeignPath</> (previously produced by
+ <function>GetForeignPaths</>), and the IDs of all other tables
+ that provide the join clauses.
+ </para>
+
+ <para>
+ 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.
+ </para>
+
+ <para>
+ See <xref linkend="fdw-planning"> for additional information.
+ </para>
+
+ <para>
+ <programlisting>
ForeignScan *
GetForeignPlan (PlannerInfo *root,
RelOptInfo *baserel,
***************
*** 804,813 **** GetForeignServerByName(const char *name, bool missing_ok);
<para>
The FDW callback functions <function>GetForeignRelSize</>,
! <function>GetForeignPaths</>, <function>GetForeignPlan</>, and
! <function>PlanForeignModify</> must fit into the workings of the
! <productname>PostgreSQL</> planner. Here are some notes about what
! they must do.
</para>
<para>
--- 835,844 ----
<para>
The FDW callback functions <function>GetForeignRelSize</>,
! <function>GetForeignPaths</>, <function>ReparameterizeForeignPath</>,
! <function>GetForeignPlan</>, and <function>PlanForeignModify</> must fit
! into the workings of the <productname>PostgreSQL</> planner. Here are
! some notes about what they must do.
</para>
<para>
***************
*** 837,850 **** GetForeignServerByName(const char *name, bool missing_ok);
to initialize it to NULL when the <literal>baserel</> node is created.
It is useful for passing information forward from
<function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
! <function>GetForeignPaths</> to <function>GetForeignPlan</>, thereby
! avoiding recalculation.
</para>
<para>
! <function>GetForeignPaths</> can identify the meaning of different
! access paths by storing private information in the
! <structfield>fdw_private</> field of <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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
--- 868,884 ----
to initialize it to NULL when the <literal>baserel</> node is created.
It is useful for passing information forward from
<function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
! <function>GetForeignPaths</> to <function>ReparameterizeForeignPath</>
! and/or
! <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
! to <function>GetForeignPlan</>, thereby avoiding recalculation.
</para>
<para>
! <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
! can identify the meaning of different access paths by storing private
! information in the <structfield>fdw_private</> field of
! <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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);
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
! clause should happen during <function>GetForeignPaths</>, since it would
! affect the cost estimate for the path. The path's
! <structfield>fdw_private</> field would probably include a pointer to
! the identified clause's <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from scan_clauses,
but add the <replaceable>sub_expression</> to <structfield>fdw_exprs</>
to ensure that it gets massaged into executable form. It would probably
--- 921,931 ----
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
! clause should happen during <function>GetForeignPaths</> or
! <function>ReparameterizeForeignPath</>, since it would affect the cost
! estimate for the path. The path's <structfield>fdw_private</> field
! would probably include a pointer to the identified clause's
! <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from scan_clauses,
but add the <replaceable>sub_expression</> to <structfield>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 ] <replaceable class="PARAMETER">name</replaceab
--- 42,51 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ ADD <replaceable class="PARAMETER">table_constraint</replaceable>
+ DROP CONSTRAINT [ IF EXISTS ] <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 149,154 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 153,202 ----
</varlistentry>
<varlistentry>
+ <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds a new constraint to a table using the same syntax as
+ <xref linkend="SQL-CREATEFOREIGNTABLE">.
+ 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.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DROP CONSTRAINT [ IF EXISTS ]</literal></term>
+ <listitem>
+ <para>
+ This form drops the specified constraint on a table.
+ If <literal>IF EXISTS</literal> is specified and the constraint
+ does not exist, no error is thrown. In this case a notice is issued instead.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OWNER</literal></term>
<listitem>
<para>
***************
*** 270,275 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 318,341 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">table_constraint</replaceable></term>
+ <listitem>
+ <para>
+ New table constraint for the table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">constraint_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing constraint to drop.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>CASCADE</literal></term>
<listitem>
<para>
***************
*** 290,295 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 356,372 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_name</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">new_owner</replaceable></term>
<listitem>
<para>
***************
*** 306,312 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
</para>
</listitem>
</varlistentry>
- </variablelist>
</refsect1>
<refsect1>
--- 383,388 ----
***************
*** 319,328 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint is
! added, or a column type is changed with <literal>SET DATA TYPE</>. It is
! the user's responsibility to ensure that the table definition matches the
! remote side.
</para>
<para>
--- 395,404 ----
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint or
! <literal>CHECK</> constraint is added, or a column type is changed with
! <literal>SET DATA TYPE</>. It is the user's responsibility to ensure that
! the table definition matches the remote side.
</para>
<para>
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 937,942 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 937,949 ----
</para>
<para>
+ If a table has any descendant tables that are foreign, a recursive
+ <literal>SET STORAGE</literal> operation will be rejected since it
+ is not permitted to add an <literal>oid</literal> system column to
+ foreign tables.
+ </para>
+
+ <para>
The <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>,
and <literal>TABLESPACE</> actions never recurse to descendant tables;
that is, they always act as though <literal>ONLY</> were specified.
***************
*** 945,950 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 952,970 ----
</para>
<para>
+ When adding a <literal>CHECK</> constraint with the <literal>NOT VALID
+ </literal> option recursively, an inherited constraint on a descendant
+ table that is foreign will be marked valid without checking
+ consistency with the foreign server.
+ </para>
+
+ <para>
+ A recursive <literal>SET STORAGE</literal> operation will make the
+ storage mode for a descendant table's column unchanged if the table is
+ foreign.
+ </para>
+
+ <para>
Changing any part of a system catalog table is not permitted.
</para>
*** a/doc/src/sgml/ref/analyze.sgml
--- b/doc/src/sgml/ref/analyze.sgml
***************
*** 200,205 **** ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
--- 200,212 ----
</para>
<para>
+ 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
+ <command>ANALYZE</command>, the command prints a warning and does nothing.
+ </para>
+
+ <para>
If the table being analyzed is completely empty, <command>ANALYZE</command>
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 ****
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
--- 19,29 ----
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
! | <replaceable>table_constraint</replaceable> }
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 30,36 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 32,44 ----
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ NOT NULL |
NULL |
+ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ] |
DEFAULT <replaceable>default_expr</replaceable> }
+
+ <phrase>and <replaceable class="PARAMETER">table_constraint</replaceable> is:</phrase>
+
+ [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
+ { CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ] }
</synopsis>
</refsynopsisdiv>
***************
*** 138,143 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 146,177 ----
</varlistentry>
<varlistentry>
+ <term><literal>CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ] </literal></term>
+ <listitem>
+ <para>
+ The <literal>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.
+ </para>
+
+ <para>
+ Currently, <literal>CHECK</literal> expressions cannot contain
+ subqueries nor refer to variables other than columns of the
+ current row. The system column <literal>tableoid</literal>
+ may be referenced, but not any other system column.
+ </para>
+
+ <para>
+ A constraint marked with <literal>NO INHERIT</> will not propagate to
+ child tables.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>DEFAULT
<replaceable>default_expr</replaceable></literal></term>
<listitem>
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 193,210 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for the
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
***************
*** 187,192 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 233,249 ----
</refsect1>
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ Constraints on foreign tables are not enforced on insert or update.
+ Those definitions simply declare the constraints hold for all rows
+ in the foreign tables. It is the user's responsibility to ensure
+ that those definitions match the remote side. Such constraints are
+ used for some kind of query optimization such as constraint exclusion.
+ </para>
+ </refsect1>
<refsect1 id="SQL-CREATEFOREIGNTABLE-examples">
<title>Examples</title>
*** 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,125 ----
* 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)
--- 133,139 ----
elevel = DEBUG2;
/* Set up static variables */
+ vac_mode = vacmode;
vac_strategy = bstrategy;
/*
***************
*** 1452,1457 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1457,1463 ----
{
List *tableOIDs;
Relation *rels;
+ AcquireSampleRowsFunc *acquirefunc;
double *relblocks;
double totalblocks;
int numrows,
***************
*** 1486,1491 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1492,1499 ----
* 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++;
}
--- 1515,1554 ----
}
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,
--- 1566,1572 ----
{
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 &&
--- 1582,1593 ----
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,3159 **** 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;
--- 3151,3167 ----
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);
pass = AT_PASS_MISC;
***************
*** 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 <math.h>
+ #include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
***************
*** 2062,2067 **** reparameterize_path(PlannerInfo *root, Path *path,
--- 2063,2093 ----
case T_SubqueryScan:
return create_subqueryscan_path(root, rel, path->pathkeys,
required_outer);
+ case T_ForeignScan:
+ {
+ ForeignPath *newpath = NULL;
+
+ /* Let the FDW reparameterize the path node if possible */
+ if (rel->fdwroutine->ReparameterizeForeignPath != 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);
+
+ newpath =
+ rel->fdwroutine->ReparameterizeForeignPath(root,
+ rel,
+ rte->relid,
+ (ForeignPath *) path,
+ 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,191 ----
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 '@');
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/plain] foreign_inherit-v9.patch (60.7K, ../../53341F3C.2050609@lab.ntt.co.jp/2-foreign_inherit-v9.patch)
download | inline diff:
*** 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/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
***************
*** 162,167 **** GetForeignPaths (PlannerInfo *root,
--- 162,198 ----
<para>
<programlisting>
+ ForeignPath *
+ ReparameterizeForeignPath (PlannerInfo *root,
+ RelOptInfo *baserel,
+ Oid foreigntableid,
+ ForeignPath *path,
+ Relids required_outer);
+ </programlisting>
+
+ Create an access path for a scan on a foreign table using join
+ clauses, which is called a <quote>parameterized path</>.
+ This is called during query planning.
+ The parameters are as for <function>GetForeignRelSize</>, plus
+ the <structname>ForeignPath</> (previously produced by
+ <function>GetForeignPaths</>), and the IDs of all other tables
+ that provide the join clauses.
+ </para>
+
+ <para>
+ 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.
+ </para>
+
+ <para>
+ See <xref linkend="fdw-planning"> for additional information.
+ </para>
+
+ <para>
+ <programlisting>
ForeignScan *
GetForeignPlan (PlannerInfo *root,
RelOptInfo *baserel,
***************
*** 804,813 **** GetForeignServerByName(const char *name, bool missing_ok);
<para>
The FDW callback functions <function>GetForeignRelSize</>,
! <function>GetForeignPaths</>, <function>GetForeignPlan</>, and
! <function>PlanForeignModify</> must fit into the workings of the
! <productname>PostgreSQL</> planner. Here are some notes about what
! they must do.
</para>
<para>
--- 835,844 ----
<para>
The FDW callback functions <function>GetForeignRelSize</>,
! <function>GetForeignPaths</>, <function>ReparameterizeForeignPath</>,
! <function>GetForeignPlan</>, and <function>PlanForeignModify</> must fit
! into the workings of the <productname>PostgreSQL</> planner. Here are
! some notes about what they must do.
</para>
<para>
***************
*** 837,850 **** GetForeignServerByName(const char *name, bool missing_ok);
to initialize it to NULL when the <literal>baserel</> node is created.
It is useful for passing information forward from
<function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
! <function>GetForeignPaths</> to <function>GetForeignPlan</>, thereby
! avoiding recalculation.
</para>
<para>
! <function>GetForeignPaths</> can identify the meaning of different
! access paths by storing private information in the
! <structfield>fdw_private</> field of <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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
--- 868,884 ----
to initialize it to NULL when the <literal>baserel</> node is created.
It is useful for passing information forward from
<function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
! <function>GetForeignPaths</> to <function>ReparameterizeForeignPath</>
! and/or
! <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
! to <function>GetForeignPlan</>, thereby avoiding recalculation.
</para>
<para>
! <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
! can identify the meaning of different access paths by storing private
! information in the <structfield>fdw_private</> field of
! <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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);
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
! clause should happen during <function>GetForeignPaths</>, since it would
! affect the cost estimate for the path. The path's
! <structfield>fdw_private</> field would probably include a pointer to
! the identified clause's <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from scan_clauses,
but add the <replaceable>sub_expression</> to <structfield>fdw_exprs</>
to ensure that it gets massaged into executable form. It would probably
--- 921,931 ----
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
! clause should happen during <function>GetForeignPaths</> or
! <function>ReparameterizeForeignPath</>, since it would affect the cost
! estimate for the path. The path's <structfield>fdw_private</> field
! would probably include a pointer to the identified clause's
! <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from scan_clauses,
but add the <replaceable>sub_expression</> to <structfield>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 ] <replaceable class="PARAMETER">name</replaceab
--- 42,51 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ ADD <replaceable class="PARAMETER">table_constraint</replaceable>
+ DROP CONSTRAINT [ IF EXISTS ] <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 149,154 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 153,202 ----
</varlistentry>
<varlistentry>
+ <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds a new constraint to a table using the same syntax as
+ <xref linkend="SQL-CREATEFOREIGNTABLE">.
+ 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.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DROP CONSTRAINT [ IF EXISTS ]</literal></term>
+ <listitem>
+ <para>
+ This form drops the specified constraint on a table.
+ If <literal>IF EXISTS</literal> is specified and the constraint
+ does not exist, no error is thrown. In this case a notice is issued instead.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OWNER</literal></term>
<listitem>
<para>
***************
*** 270,275 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 318,341 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">table_constraint</replaceable></term>
+ <listitem>
+ <para>
+ New table constraint for the table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">constraint_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing constraint to drop.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>CASCADE</literal></term>
<listitem>
<para>
***************
*** 290,295 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 356,372 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_name</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">new_owner</replaceable></term>
<listitem>
<para>
***************
*** 306,312 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
</para>
</listitem>
</varlistentry>
- </variablelist>
</refsect1>
<refsect1>
--- 383,388 ----
***************
*** 319,328 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint is
! added, or a column type is changed with <literal>SET DATA TYPE</>. It is
! the user's responsibility to ensure that the table definition matches the
! remote side.
</para>
<para>
--- 395,404 ----
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint or
! <literal>CHECK</> constraint is added, or a column type is changed with
! <literal>SET DATA TYPE</>. It is the user's responsibility to ensure that
! the table definition matches the remote side.
</para>
<para>
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 937,942 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 937,949 ----
</para>
<para>
+ If a table has any descendant tables that are foreign, a recursive
+ <literal>SET STORAGE</literal> operation will be rejected since it
+ is not permitted to add an <literal>oid</literal> system column to
+ foreign tables.
+ </para>
+
+ <para>
The <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>,
and <literal>TABLESPACE</> actions never recurse to descendant tables;
that is, they always act as though <literal>ONLY</> were specified.
***************
*** 945,950 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 952,970 ----
</para>
<para>
+ When adding a <literal>CHECK</> constraint with the <literal>NOT VALID
+ </literal> option recursively, an inherited constraint on a descendant
+ table that is foreign will be marked valid without checking
+ consistency with the foreign server.
+ </para>
+
+ <para>
+ A recursive <literal>SET STORAGE</literal> operation will make the
+ storage mode for a descendant table's column unchanged if the table is
+ foreign.
+ </para>
+
+ <para>
Changing any part of a system catalog table is not permitted.
</para>
*** a/doc/src/sgml/ref/analyze.sgml
--- b/doc/src/sgml/ref/analyze.sgml
***************
*** 200,205 **** ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
--- 200,212 ----
</para>
<para>
+ 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
+ <command>ANALYZE</command>, the command prints a warning and does nothing.
+ </para>
+
+ <para>
If the table being analyzed is completely empty, <command>ANALYZE</command>
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 ****
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
--- 19,29 ----
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
! | <replaceable>table_constraint</replaceable> }
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 30,36 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 32,44 ----
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ NOT NULL |
NULL |
+ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ] |
DEFAULT <replaceable>default_expr</replaceable> }
+
+ <phrase>and <replaceable class="PARAMETER">table_constraint</replaceable> is:</phrase>
+
+ [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
+ { CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ] }
</synopsis>
</refsynopsisdiv>
***************
*** 138,143 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 146,177 ----
</varlistentry>
<varlistentry>
+ <term><literal>CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ] </literal></term>
+ <listitem>
+ <para>
+ The <literal>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.
+ </para>
+
+ <para>
+ Currently, <literal>CHECK</literal> expressions cannot contain
+ subqueries nor refer to variables other than columns of the
+ current row. The system column <literal>tableoid</literal>
+ may be referenced, but not any other system column.
+ </para>
+
+ <para>
+ A constraint marked with <literal>NO INHERIT</> will not propagate to
+ child tables.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>DEFAULT
<replaceable>default_expr</replaceable></literal></term>
<listitem>
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 193,210 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for the
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
***************
*** 187,192 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 233,249 ----
</refsect1>
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ Constraints on foreign tables are not enforced on insert or update.
+ Those definitions simply declare the constraints hold for all rows
+ in the foreign tables. It is the user's responsibility to ensure
+ that those definitions match the remote side. Such constraints are
+ used for some kind of query optimization such as constraint exclusion.
+ </para>
+ </refsect1>
<refsect1 id="SQL-CREATEFOREIGNTABLE-examples">
<title>Examples</title>
*** 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,125 ----
* 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)
--- 133,139 ----
elevel = DEBUG2;
/* Set up static variables */
+ vac_mode = vacmode;
vac_strategy = bstrategy;
/*
***************
*** 1452,1457 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1457,1463 ----
{
List *tableOIDs;
Relation *rels;
+ AcquireSampleRowsFunc *acquirefunc;
double *relblocks;
double totalblocks;
int numrows,
***************
*** 1486,1491 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1492,1499 ----
* 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++;
}
--- 1515,1554 ----
}
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,
--- 1566,1572 ----
{
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 &&
--- 1582,1593 ----
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,3159 **** 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;
--- 3151,3167 ----
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);
pass = AT_PASS_MISC;
***************
*** 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 <math.h>
+ #include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
***************
*** 2062,2067 **** reparameterize_path(PlannerInfo *root, Path *path,
--- 2063,2093 ----
case T_SubqueryScan:
return create_subqueryscan_path(root, rel, path->pathkeys,
required_outer);
+ case T_ForeignScan:
+ {
+ ForeignPath *newpath = NULL;
+
+ /* Let the FDW reparameterize the path node if possible */
+ if (rel->fdwroutine->ReparameterizeForeignPath != 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);
+
+ newpath =
+ rel->fdwroutine->ReparameterizeForeignPath(root,
+ rel,
+ rte->relid,
+ (ForeignPath *) path,
+ 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,191 ----
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 '@');
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-03-28 04:28 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2014-03-28 04:28 UTC (permalink / raw)
To: fujita.etsuro@lab.ntt.co.jp; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
Hi,
> > ForeignPath *pathnode = makeNode(ForeignPath);
> > + Assert(rel->rtekind == RTE_RELATION);
> >
> > pathnode->path.pathtype = T_ForeignScan;
..
> Maybe I'm missing the point, but I don't think it'd be better to put the
> assertion in create_foreignscan_path(). And I think it'd be the caller'
> responsiblity to ensure that equality, as any other pathnode creation
> routine for a baserel in pathnode.c assumes that equality.
Hmm. The assertion (not shown above but you put in
parameterize_path:) seems to say that 'base relation for foreign
paths must be a RTE_RELATION' isn't right? But I don't see
anything putting such a restriction in reparameterize_path
itself. Could you tell me where such a restriction comes from? Or
who needs such a restriction? I think any assertions shouldn't be
anywhere other than where just before needed.
Thoughts?
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-03-31 08:48 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
0 siblings, 0 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-03-31 08:48 UTC (permalink / raw)
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
(2014/03/28 13:28), Kyotaro HORIGUCHI wrote:
> Hmm. The assertion (not shown above but you put in
> parameterize_path:) seems to say that 'base relation for foreign
> paths must be a RTE_RELATION' isn't right?
I think that's right. Please see allpaths.c, especially set_rel_pathlist().
For your information, the same assertion can be found in
create_foreignscan_plan().
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-03-31 10:06 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-03-31 10:06 UTC (permalink / raw)
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
I've found some bugs. Attached is an updated version (v10).
Changes:
* When CREATE FOREIGN TABLE INHERITS, don't allow a foreign table to
inherit from parent tables that have OID system columns.
* When CREATE FOREIGN TABLE INHERITS, reset the storage parameter for
inherited columns that have non-defaut values.
* Don't allow CHECK (expr) *NO INHERIT* on foreign tables, since those
tables cannot have child tables.
* Fix and update the documentation.
Thanks,
Best regards,
Etsuro Fujita
*** 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/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
***************
*** 162,167 **** GetForeignPaths (PlannerInfo *root,
--- 162,198 ----
<para>
<programlisting>
+ ForeignPath *
+ ReparameterizeForeignPath (PlannerInfo *root,
+ RelOptInfo *baserel,
+ Oid foreigntableid,
+ ForeignPath *path,
+ Relids required_outer);
+ </programlisting>
+
+ Create an access path for a scan on a foreign table using join
+ clauses, which is called a <quote>parameterized path</>.
+ This is called during query planning.
+ The parameters are as for <function>GetForeignRelSize</>, plus
+ the <structname>ForeignPath</> (previously produced by
+ <function>GetForeignPaths</>), and the IDs of all other tables
+ that provide the join clauses.
+ </para>
+
+ <para>
+ 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.
+ </para>
+
+ <para>
+ See <xref linkend="fdw-planning"> for additional information.
+ </para>
+
+ <para>
+ <programlisting>
ForeignScan *
GetForeignPlan (PlannerInfo *root,
RelOptInfo *baserel,
***************
*** 804,813 **** GetForeignServerByName(const char *name, bool missing_ok);
<para>
The FDW callback functions <function>GetForeignRelSize</>,
! <function>GetForeignPaths</>, <function>GetForeignPlan</>, and
! <function>PlanForeignModify</> must fit into the workings of the
! <productname>PostgreSQL</> planner. Here are some notes about what
! they must do.
</para>
<para>
--- 835,844 ----
<para>
The FDW callback functions <function>GetForeignRelSize</>,
! <function>GetForeignPaths</>, <function>ReparameterizeForeignPath</>,
! <function>GetForeignPlan</>, and <function>PlanForeignModify</> must fit
! into the workings of the <productname>PostgreSQL</> planner. Here are
! some notes about what they must do.
</para>
<para>
***************
*** 837,850 **** GetForeignServerByName(const char *name, bool missing_ok);
to initialize it to NULL when the <literal>baserel</> node is created.
It is useful for passing information forward from
<function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
! <function>GetForeignPaths</> to <function>GetForeignPlan</>, thereby
! avoiding recalculation.
</para>
<para>
! <function>GetForeignPaths</> can identify the meaning of different
! access paths by storing private information in the
! <structfield>fdw_private</> field of <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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
--- 868,884 ----
to initialize it to NULL when the <literal>baserel</> node is created.
It is useful for passing information forward from
<function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
! <function>GetForeignPaths</> to <function>ReparameterizeForeignPath</>
! and/or
! <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
! to <function>GetForeignPlan</>, thereby avoiding recalculation.
</para>
<para>
! <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
! can identify the meaning of different access paths by storing private
! information in the <structfield>fdw_private</> field of
! <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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);
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
! clause should happen during <function>GetForeignPaths</>, since it would
! affect the cost estimate for the path. The path's
! <structfield>fdw_private</> field would probably include a pointer to
! the identified clause's <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from scan_clauses,
but add the <replaceable>sub_expression</> to <structfield>fdw_exprs</>
to ensure that it gets massaged into executable form. It would probably
--- 921,931 ----
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
! clause should happen during <function>GetForeignPaths</> or
! <function>ReparameterizeForeignPath</>, since it would affect the cost
! estimate for the path. The path's <structfield>fdw_private</> field
! would probably include a pointer to the identified clause's
! <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from scan_clauses,
but add the <replaceable>sub_expression</> to <structfield>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 ] <replaceable class="PARAMETER">name</replaceab
--- 42,51 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ ADD <replaceable class="PARAMETER">table_constraint</replaceable>
+ DROP CONSTRAINT [ IF EXISTS ] <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 149,154 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 153,202 ----
</varlistentry>
<varlistentry>
+ <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds a new constraint to a table using the same syntax as
+ <xref linkend="SQL-CREATEFOREIGNTABLE">.
+ 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.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DROP CONSTRAINT [ IF EXISTS ]</literal></term>
+ <listitem>
+ <para>
+ This form drops the specified constraint on a table.
+ If <literal>IF EXISTS</literal> is specified and the constraint
+ does not exist, no error is thrown. In this case a notice is issued instead.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OWNER</literal></term>
<listitem>
<para>
***************
*** 270,275 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 318,341 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">table_constraint</replaceable></term>
+ <listitem>
+ <para>
+ New table constraint for the table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">constraint_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing constraint to drop.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>CASCADE</literal></term>
<listitem>
<para>
***************
*** 290,295 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 356,371 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">new_owner</replaceable></term>
<listitem>
<para>
***************
*** 319,328 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint is
! added, or a column type is changed with <literal>SET DATA TYPE</>. It is
! the user's responsibility to ensure that the table definition matches the
! remote side.
</para>
<para>
--- 395,404 ----
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint or
! <literal>CHECK</> constraint is added, or a column type is changed with
! <literal>SET DATA TYPE</>. It is the user's responsibility to ensure that
! the table definition matches the remote side.
</para>
<para>
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 937,942 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 937,949 ----
</para>
<para>
+ If a table has any descendant tables that are foreign, a recursive
+ <literal>SET STORAGE</literal> operation will be rejected since it
+ is not permitted to add an <literal>oid</literal> system column to
+ foreign tables.
+ </para>
+
+ <para>
The <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>,
and <literal>TABLESPACE</> actions never recurse to descendant tables;
that is, they always act as though <literal>ONLY</> were specified.
***************
*** 945,950 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 952,970 ----
</para>
<para>
+ When adding a <literal>CHECK</> constraint with the <literal>NOT VALID
+ </literal> option recursively, an inherited constraint on a descendant
+ table that is foreign will be marked valid without checking
+ consistency with the foreign server.
+ </para>
+
+ <para>
+ A recursive <literal>SET STORAGE</literal> operation will make the
+ storage mode for a descendant table's column unchanged if the table is
+ foreign.
+ </para>
+
+ <para>
Changing any part of a system catalog table is not permitted.
</para>
*** a/doc/src/sgml/ref/analyze.sgml
--- b/doc/src/sgml/ref/analyze.sgml
***************
*** 200,205 **** ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
--- 200,212 ----
</para>
<para>
+ 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
+ <command>ANALYZE</command>, the command prints a warning and does nothing.
+ </para>
+
+ <para>
If the table being analyzed is completely empty, <command>ANALYZE</command>
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 ****
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
--- 19,29 ----
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
! | <replaceable>table_constraint</replaceable> }
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 30,36 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 32,44 ----
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ NOT NULL |
NULL |
+ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) |
DEFAULT <replaceable>default_expr</replaceable> }
+
+ <phrase>and <replaceable class="PARAMETER">table_constraint</replaceable> is:</phrase>
+
+ [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
+ { CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) }
</synopsis>
</refsynopsisdiv>
***************
*** 138,143 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 146,172 ----
</varlistentry>
<varlistentry>
+ <term><literal>CHECK ( <replaceable class="PARAMETER">expression</replaceable> )</literal></term>
+ <listitem>
+ <para>
+ The <literal>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.
+ </para>
+
+ <para>
+ Currently, <literal>CHECK</literal> expressions cannot contain
+ subqueries nor refer to variables other than columns of the
+ current row. The system column <literal>tableoid</literal>
+ may be referenced, but not any other system column.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>DEFAULT
<replaceable>default_expr</replaceable></literal></term>
<listitem>
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 188,205 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for the
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
***************
*** 187,192 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 228,250 ----
</refsect1>
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ Constraints on foreign tables are not enforced on insert or update.
+ Those definitions simply declare the constraints hold for all rows
+ in the foreign tables. It is the user's responsibility to ensure
+ that those definitions match the remote side. Such constraints are
+ used for some kind of query optimization such as constraint exclusion.
+ </para>
+
+ <para>
+ Since it is not permitted to add an <literal>oid</> system column to
+ foreign tables, the command will be rejected if any parent tables
+ have <literal>oid</> system columns.
+ </para>
+ </refsect1>
<refsect1 id="SQL-CREATEFOREIGNTABLE-examples">
<title>Examples</title>
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 2217,2222 **** AddRelationNewConstraints(Relation rel,
--- 2217,2228 ----
if (cdef->contype != CONSTR_CHECK)
continue;
+ if (cdef->is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("CHECK constraints on foreign tables cannot be marked NO INHERIT")));
+
if (cdef->raw_expr != NULL)
{
Assert(cdef->cooked_expr == NULL);
*** 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,125 ----
* 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)
--- 133,139 ----
elevel = DEBUG2;
/* Set up static variables */
+ vac_mode = vacmode;
vac_strategy = bstrategy;
/*
***************
*** 1452,1457 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1457,1463 ----
{
List *tableOIDs;
Relation *rels;
+ AcquireSampleRowsFunc *acquirefunc;
double *relblocks;
double totalblocks;
int numrows,
***************
*** 1486,1491 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1492,1499 ----
* 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++;
}
--- 1515,1554 ----
}
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,
--- 1566,1572 ----
{
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 &&
--- 1582,1593 ----
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 ----
***************
*** 557,562 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
--- 554,583 ----
stmt->relation->relpersistence,
&inheritOids, &old_constraints, &parentOidCount);
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * Don't allow foreign tables to inherit from parent tables that have
+ * OID system columns.
+ */
+ if (parentOidCount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot inherit from parent tables with OIDs")));
+
+ /*
+ * Reset the storage parameter for inherited columns that have
+ * non-default values.
+ */
+ foreach(listptr, schema)
+ {
+ ColumnDef *colDef = lfirst(listptr);
+
+ if (colDef->storage != 0)
+ colDef->storage = 0;
+ }
+ }
+
/*
* Create a tuple descriptor from the relation schema. Note that this
* deals with column names, types, and NOT NULL constraints, but not
***************
*** 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;
--- 3040,3067 ----
* 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;
--- 3074,3081 ----
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)
--- 3093,3099 ----
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)
--- 3107,3113 ----
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,3159 **** 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;
--- 3175,3191 ----
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);
pass = AT_PASS_MISC;
***************
*** 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);
--- 3216,3221 ----
***************
*** 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
--- 4156,4163 ----
*/
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);
}
}
--- 4185,4196 ----
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);
--- 4480,4486 ----
/* 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,
--- 4776,4786 ----
/* 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
--- 5381,5387 ----
/* 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
--- 5773,5786 ----
/* 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 */
--- 5791,5807 ----
* 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);
--- 7281,7287 ----
/* 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,
--- 7616,7625 ----
* 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 <math.h>
+ #include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
***************
*** 2062,2067 **** reparameterize_path(PlannerInfo *root, Path *path,
--- 2063,2093 ----
case T_SubqueryScan:
return create_subqueryscan_path(root, rel, path->pathkeys,
required_outer);
+ case T_ForeignScan:
+ {
+ ForeignPath *newpath = NULL;
+
+ /* Let the FDW reparameterize the path node if possible */
+ if (rel->fdwroutine->ReparameterizeForeignPath != 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);
+
+ newpath =
+ rel->fdwroutine->ReparameterizeForeignPath(root,
+ rel,
+ rte->relid,
+ (ForeignPath *) path,
+ 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,191 ----
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 '@');
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/plain] foreign_inherit-v10.patch (61.9K, ../../53393E18.3080901@lab.ntt.co.jp/2-foreign_inherit-v10.patch)
download | inline diff:
*** 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/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
***************
*** 162,167 **** GetForeignPaths (PlannerInfo *root,
--- 162,198 ----
<para>
<programlisting>
+ ForeignPath *
+ ReparameterizeForeignPath (PlannerInfo *root,
+ RelOptInfo *baserel,
+ Oid foreigntableid,
+ ForeignPath *path,
+ Relids required_outer);
+ </programlisting>
+
+ Create an access path for a scan on a foreign table using join
+ clauses, which is called a <quote>parameterized path</>.
+ This is called during query planning.
+ The parameters are as for <function>GetForeignRelSize</>, plus
+ the <structname>ForeignPath</> (previously produced by
+ <function>GetForeignPaths</>), and the IDs of all other tables
+ that provide the join clauses.
+ </para>
+
+ <para>
+ 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.
+ </para>
+
+ <para>
+ See <xref linkend="fdw-planning"> for additional information.
+ </para>
+
+ <para>
+ <programlisting>
ForeignScan *
GetForeignPlan (PlannerInfo *root,
RelOptInfo *baserel,
***************
*** 804,813 **** GetForeignServerByName(const char *name, bool missing_ok);
<para>
The FDW callback functions <function>GetForeignRelSize</>,
! <function>GetForeignPaths</>, <function>GetForeignPlan</>, and
! <function>PlanForeignModify</> must fit into the workings of the
! <productname>PostgreSQL</> planner. Here are some notes about what
! they must do.
</para>
<para>
--- 835,844 ----
<para>
The FDW callback functions <function>GetForeignRelSize</>,
! <function>GetForeignPaths</>, <function>ReparameterizeForeignPath</>,
! <function>GetForeignPlan</>, and <function>PlanForeignModify</> must fit
! into the workings of the <productname>PostgreSQL</> planner. Here are
! some notes about what they must do.
</para>
<para>
***************
*** 837,850 **** GetForeignServerByName(const char *name, bool missing_ok);
to initialize it to NULL when the <literal>baserel</> node is created.
It is useful for passing information forward from
<function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
! <function>GetForeignPaths</> to <function>GetForeignPlan</>, thereby
! avoiding recalculation.
</para>
<para>
! <function>GetForeignPaths</> can identify the meaning of different
! access paths by storing private information in the
! <structfield>fdw_private</> field of <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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
--- 868,884 ----
to initialize it to NULL when the <literal>baserel</> node is created.
It is useful for passing information forward from
<function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
! <function>GetForeignPaths</> to <function>ReparameterizeForeignPath</>
! and/or
! <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
! to <function>GetForeignPlan</>, thereby avoiding recalculation.
</para>
<para>
! <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
! can identify the meaning of different access paths by storing private
! information in the <structfield>fdw_private</> field of
! <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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);
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
! clause should happen during <function>GetForeignPaths</>, since it would
! affect the cost estimate for the path. The path's
! <structfield>fdw_private</> field would probably include a pointer to
! the identified clause's <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from scan_clauses,
but add the <replaceable>sub_expression</> to <structfield>fdw_exprs</>
to ensure that it gets massaged into executable form. It would probably
--- 921,931 ----
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
! clause should happen during <function>GetForeignPaths</> or
! <function>ReparameterizeForeignPath</>, since it would affect the cost
! estimate for the path. The path's <structfield>fdw_private</> field
! would probably include a pointer to the identified clause's
! <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from scan_clauses,
but add the <replaceable>sub_expression</> to <structfield>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 ] <replaceable class="PARAMETER">name</replaceab
--- 42,51 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ ADD <replaceable class="PARAMETER">table_constraint</replaceable>
+ DROP CONSTRAINT [ IF EXISTS ] <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 149,154 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 153,202 ----
</varlistentry>
<varlistentry>
+ <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds a new constraint to a table using the same syntax as
+ <xref linkend="SQL-CREATEFOREIGNTABLE">.
+ 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.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DROP CONSTRAINT [ IF EXISTS ]</literal></term>
+ <listitem>
+ <para>
+ This form drops the specified constraint on a table.
+ If <literal>IF EXISTS</literal> is specified and the constraint
+ does not exist, no error is thrown. In this case a notice is issued instead.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OWNER</literal></term>
<listitem>
<para>
***************
*** 270,275 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 318,341 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">table_constraint</replaceable></term>
+ <listitem>
+ <para>
+ New table constraint for the table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">constraint_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing constraint to drop.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>CASCADE</literal></term>
<listitem>
<para>
***************
*** 290,295 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 356,371 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">new_owner</replaceable></term>
<listitem>
<para>
***************
*** 319,328 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint is
! added, or a column type is changed with <literal>SET DATA TYPE</>. It is
! the user's responsibility to ensure that the table definition matches the
! remote side.
</para>
<para>
--- 395,404 ----
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint or
! <literal>CHECK</> constraint is added, or a column type is changed with
! <literal>SET DATA TYPE</>. It is the user's responsibility to ensure that
! the table definition matches the remote side.
</para>
<para>
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 937,942 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 937,949 ----
</para>
<para>
+ If a table has any descendant tables that are foreign, a recursive
+ <literal>SET STORAGE</literal> operation will be rejected since it
+ is not permitted to add an <literal>oid</literal> system column to
+ foreign tables.
+ </para>
+
+ <para>
The <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>,
and <literal>TABLESPACE</> actions never recurse to descendant tables;
that is, they always act as though <literal>ONLY</> were specified.
***************
*** 945,950 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 952,970 ----
</para>
<para>
+ When adding a <literal>CHECK</> constraint with the <literal>NOT VALID
+ </literal> option recursively, an inherited constraint on a descendant
+ table that is foreign will be marked valid without checking
+ consistency with the foreign server.
+ </para>
+
+ <para>
+ A recursive <literal>SET STORAGE</literal> operation will make the
+ storage mode for a descendant table's column unchanged if the table is
+ foreign.
+ </para>
+
+ <para>
Changing any part of a system catalog table is not permitted.
</para>
*** a/doc/src/sgml/ref/analyze.sgml
--- b/doc/src/sgml/ref/analyze.sgml
***************
*** 200,205 **** ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
--- 200,212 ----
</para>
<para>
+ 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
+ <command>ANALYZE</command>, the command prints a warning and does nothing.
+ </para>
+
+ <para>
If the table being analyzed is completely empty, <command>ANALYZE</command>
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 ****
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
--- 19,29 ----
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
! | <replaceable>table_constraint</replaceable> }
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 30,36 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 32,44 ----
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ NOT NULL |
NULL |
+ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) |
DEFAULT <replaceable>default_expr</replaceable> }
+
+ <phrase>and <replaceable class="PARAMETER">table_constraint</replaceable> is:</phrase>
+
+ [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
+ { CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) }
</synopsis>
</refsynopsisdiv>
***************
*** 138,143 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 146,172 ----
</varlistentry>
<varlistentry>
+ <term><literal>CHECK ( <replaceable class="PARAMETER">expression</replaceable> )</literal></term>
+ <listitem>
+ <para>
+ The <literal>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.
+ </para>
+
+ <para>
+ Currently, <literal>CHECK</literal> expressions cannot contain
+ subqueries nor refer to variables other than columns of the
+ current row. The system column <literal>tableoid</literal>
+ may be referenced, but not any other system column.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>DEFAULT
<replaceable>default_expr</replaceable></literal></term>
<listitem>
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 188,205 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for the
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
***************
*** 187,192 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 228,250 ----
</refsect1>
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ Constraints on foreign tables are not enforced on insert or update.
+ Those definitions simply declare the constraints hold for all rows
+ in the foreign tables. It is the user's responsibility to ensure
+ that those definitions match the remote side. Such constraints are
+ used for some kind of query optimization such as constraint exclusion.
+ </para>
+
+ <para>
+ Since it is not permitted to add an <literal>oid</> system column to
+ foreign tables, the command will be rejected if any parent tables
+ have <literal>oid</> system columns.
+ </para>
+ </refsect1>
<refsect1 id="SQL-CREATEFOREIGNTABLE-examples">
<title>Examples</title>
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 2217,2222 **** AddRelationNewConstraints(Relation rel,
--- 2217,2228 ----
if (cdef->contype != CONSTR_CHECK)
continue;
+ if (cdef->is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("CHECK constraints on foreign tables cannot be marked NO INHERIT")));
+
if (cdef->raw_expr != NULL)
{
Assert(cdef->cooked_expr == NULL);
*** 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,125 ----
* 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)
--- 133,139 ----
elevel = DEBUG2;
/* Set up static variables */
+ vac_mode = vacmode;
vac_strategy = bstrategy;
/*
***************
*** 1452,1457 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1457,1463 ----
{
List *tableOIDs;
Relation *rels;
+ AcquireSampleRowsFunc *acquirefunc;
double *relblocks;
double totalblocks;
int numrows,
***************
*** 1486,1491 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1492,1499 ----
* 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++;
}
--- 1515,1554 ----
}
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,
--- 1566,1572 ----
{
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 &&
--- 1582,1593 ----
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 ----
***************
*** 557,562 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
--- 554,583 ----
stmt->relation->relpersistence,
&inheritOids, &old_constraints, &parentOidCount);
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * Don't allow foreign tables to inherit from parent tables that have
+ * OID system columns.
+ */
+ if (parentOidCount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot inherit from parent tables with OIDs")));
+
+ /*
+ * Reset the storage parameter for inherited columns that have
+ * non-default values.
+ */
+ foreach(listptr, schema)
+ {
+ ColumnDef *colDef = lfirst(listptr);
+
+ if (colDef->storage != 0)
+ colDef->storage = 0;
+ }
+ }
+
/*
* Create a tuple descriptor from the relation schema. Note that this
* deals with column names, types, and NOT NULL constraints, but not
***************
*** 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;
--- 3040,3067 ----
* 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;
--- 3074,3081 ----
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)
--- 3093,3099 ----
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)
--- 3107,3113 ----
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,3159 **** 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;
--- 3175,3191 ----
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);
pass = AT_PASS_MISC;
***************
*** 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);
--- 3216,3221 ----
***************
*** 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
--- 4156,4163 ----
*/
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);
}
}
--- 4185,4196 ----
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);
--- 4480,4486 ----
/* 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,
--- 4776,4786 ----
/* 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
--- 5381,5387 ----
/* 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
--- 5773,5786 ----
/* 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 */
--- 5791,5807 ----
* 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);
--- 7281,7287 ----
/* 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,
--- 7616,7625 ----
* 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 <math.h>
+ #include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
***************
*** 2062,2067 **** reparameterize_path(PlannerInfo *root, Path *path,
--- 2063,2093 ----
case T_SubqueryScan:
return create_subqueryscan_path(root, rel, path->pathkeys,
required_outer);
+ case T_ForeignScan:
+ {
+ ForeignPath *newpath = NULL;
+
+ /* Let the FDW reparameterize the path node if possible */
+ if (rel->fdwroutine->ReparameterizeForeignPath != 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);
+
+ newpath =
+ rel->fdwroutine->ReparameterizeForeignPath(root,
+ rel,
+ rte->relid,
+ (ForeignPath *) path,
+ 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,191 ----
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 '@');
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-04-02 12:25 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-04-02 12:25 UTC (permalink / raw)
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
Attached is v11.
Changes:
* Rebased to head
* Improve an error message added to tablecmd.c to match it to existing
ones there
* Improve the documentaion a bit
Thanks,
Best regards,
Etsuro Fujita
*** 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/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
***************
*** 162,167 **** GetForeignPaths (PlannerInfo *root,
--- 162,198 ----
<para>
<programlisting>
+ ForeignPath *
+ ReparameterizeForeignPath (PlannerInfo *root,
+ RelOptInfo *baserel,
+ Oid foreigntableid,
+ ForeignPath *path,
+ Relids required_outer);
+ </programlisting>
+
+ Create an access path for a scan on a foreign table using join
+ clauses, which is called a <quote>parameterized path</>.
+ This is called during query planning.
+ The parameters are as for <function>GetForeignRelSize</>, plus
+ the <structname>ForeignPath</> (previously produced by
+ <function>GetForeignPaths</>), and the IDs of all other tables
+ that provide the join clauses.
+ </para>
+
+ <para>
+ 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.
+ </para>
+
+ <para>
+ See <xref linkend="fdw-planning"> for additional information.
+ </para>
+
+ <para>
+ <programlisting>
ForeignScan *
GetForeignPlan (PlannerInfo *root,
RelOptInfo *baserel,
***************
*** 808,817 **** GetForeignServerByName(const char *name, bool missing_ok);
<para>
The FDW callback functions <function>GetForeignRelSize</>,
! <function>GetForeignPaths</>, <function>GetForeignPlan</>, and
! <function>PlanForeignModify</> must fit into the workings of the
! <productname>PostgreSQL</> planner. Here are some notes about what
! they must do.
</para>
<para>
--- 839,848 ----
<para>
The FDW callback functions <function>GetForeignRelSize</>,
! <function>GetForeignPaths</>, <function>ReparameterizeForeignPath</>,
! <function>GetForeignPlan</>, and <function>PlanForeignModify</> must fit
! into the workings of the <productname>PostgreSQL</> planner. Here are
! some notes about what they must do.
</para>
<para>
***************
*** 841,854 **** GetForeignServerByName(const char *name, bool missing_ok);
to initialize it to NULL when the <literal>baserel</> node is created.
It is useful for passing information forward from
<function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
! <function>GetForeignPaths</> to <function>GetForeignPlan</>, thereby
! avoiding recalculation.
</para>
<para>
! <function>GetForeignPaths</> can identify the meaning of different
! access paths by storing private information in the
! <structfield>fdw_private</> field of <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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
--- 872,888 ----
to initialize it to NULL when the <literal>baserel</> node is created.
It is useful for passing information forward from
<function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
! <function>GetForeignPaths</> to <function>ReparameterizeForeignPath</>
! and/or
! <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
! to <function>GetForeignPlan</>, thereby avoiding recalculation.
</para>
<para>
! <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
! can identify the meaning of different access paths by storing private
! information in the <structfield>fdw_private</> field of
! <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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
***************
*** 891,900 **** GetForeignServerByName(const char *name, bool missing_ok);
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
! clause should happen during <function>GetForeignPaths</>, since it would
! affect the cost estimate for the path. The path's
! <structfield>fdw_private</> field would probably include a pointer to
! the identified clause's <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from scan_clauses,
but add the <replaceable>sub_expression</> to <structfield>fdw_exprs</>
to ensure that it gets massaged into executable form. It would probably
--- 925,935 ----
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
! clause should happen during <function>GetForeignPaths</> or
! <function>ReparameterizeForeignPath</>, since it would affect the cost
! estimate for the path. The path's <structfield>fdw_private</> field
! would probably include a pointer to the identified clause's
! <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from scan_clauses,
but add the <replaceable>sub_expression</> to <structfield>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 ] <replaceable class="PARAMETER">name</replaceab
--- 42,51 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ ADD <replaceable class="PARAMETER">table_constraint</replaceable>
+ DROP CONSTRAINT [ IF EXISTS ] <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 149,154 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 153,202 ----
</varlistentry>
<varlistentry>
+ <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds a new constraint to a table using the same syntax as
+ <xref linkend="SQL-CREATEFOREIGNTABLE">.
+ 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.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DROP CONSTRAINT [ IF EXISTS ]</literal></term>
+ <listitem>
+ <para>
+ This form drops the specified constraint on a table.
+ If <literal>IF EXISTS</literal> is specified and the constraint
+ does not exist, no error is thrown. In this case a notice is issued instead.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OWNER</literal></term>
<listitem>
<para>
***************
*** 270,275 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 318,341 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">table_constraint</replaceable></term>
+ <listitem>
+ <para>
+ New table constraint for the table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">constraint_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing constraint to drop.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>CASCADE</literal></term>
<listitem>
<para>
***************
*** 290,295 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 356,371 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">new_owner</replaceable></term>
<listitem>
<para>
***************
*** 319,328 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint is
! added, or a column type is changed with <literal>SET DATA TYPE</>. It is
! the user's responsibility to ensure that the table definition matches the
! remote side.
</para>
<para>
--- 395,404 ----
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint or
! <literal>CHECK</> constraint is added, or a column type is changed with
! <literal>SET DATA TYPE</>. It is the user's responsibility to ensure that
! the table definition matches the remote side.
</para>
<para>
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 937,942 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 937,949 ----
</para>
<para>
+ If a table has any descendant tables that are foreign, a recursive
+ <literal>SET STORAGE</literal> operation will be rejected since it
+ is not permitted to add an <literal>oid</literal> system column to
+ foreign tables.
+ </para>
+
+ <para>
The <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>,
and <literal>TABLESPACE</> actions never recurse to descendant tables;
that is, they always act as though <literal>ONLY</> were specified.
***************
*** 945,950 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 952,970 ----
</para>
<para>
+ When adding a <literal>CHECK</> constraint with the <literal>NOT VALID
+ </literal> option recursively, an inherited constraint on a descendant
+ table that is foreign will be marked valid without checking
+ consistency with the foreign server.
+ </para>
+
+ <para>
+ A recursive <literal>SET STORAGE</literal> operation will make the
+ storage mode for a descendant table's column unchanged if the table is
+ foreign.
+ </para>
+
+ <para>
Changing any part of a system catalog table is not permitted.
</para>
*** a/doc/src/sgml/ref/analyze.sgml
--- b/doc/src/sgml/ref/analyze.sgml
***************
*** 200,205 **** ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
--- 200,212 ----
</para>
<para>
+ 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
+ <command>ANALYZE</command>, the command prints a warning and does nothing.
+ </para>
+
+ <para>
If the table being analyzed is completely empty, <command>ANALYZE</command>
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 ****
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
--- 19,29 ----
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
! | <replaceable>table_constraint</replaceable> }
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 30,36 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 32,44 ----
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ NOT NULL |
NULL |
+ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) |
DEFAULT <replaceable>default_expr</replaceable> }
+
+ <phrase>and <replaceable class="PARAMETER">table_constraint</replaceable> is:</phrase>
+
+ [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
+ { CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) }
</synopsis>
</refsynopsisdiv>
***************
*** 138,143 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 146,172 ----
</varlistentry>
<varlistentry>
+ <term><literal>CHECK ( <replaceable class="PARAMETER">expression</replaceable> )</literal></term>
+ <listitem>
+ <para>
+ The <literal>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.
+ </para>
+
+ <para>
+ Currently, <literal>CHECK</literal> expressions cannot contain
+ subqueries nor refer to variables other than columns of the
+ current row. The system column <literal>tableoid</literal>
+ may be referenced, but not any other system column.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>DEFAULT
<replaceable>default_expr</replaceable></literal></term>
<listitem>
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 188,205 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for the
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
***************
*** 187,192 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 228,251 ----
</refsect1>
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ Constraints on foreign tables are not enforced on insert or update.
+ Those definitions simply declare the constraints hold for all rows
+ in the foreign tables. It is the user's responsibility to ensure
+ that those definitions match the remote side. Such constraints are
+ used for some kind of query optimization such as constraint exclusion
+ for partitioned tables (see <xref linkend="ddl-partitioning">).
+ </para>
+
+ <para>
+ Since it is not permitted to add an <literal>oid</> system column to
+ foreign tables, the command will be rejected if any parent tables
+ have <literal>oid</> system columns.
+ </para>
+ </refsect1>
<refsect1 id="SQL-CREATEFOREIGNTABLE-examples">
<title>Examples</title>
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 2217,2222 **** AddRelationNewConstraints(Relation rel,
--- 2217,2228 ----
if (cdef->contype != CONSTR_CHECK)
continue;
+ if (cdef->is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("CHECK constraints on foreign tables cannot be marked NO INHERIT")));
+
if (cdef->raw_expr != NULL)
{
Assert(cdef->cooked_expr == NULL);
*** 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,125 ----
* 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)
--- 133,139 ----
elevel = DEBUG2;
/* Set up static variables */
+ vac_mode = vacmode;
vac_strategy = bstrategy;
/*
***************
*** 1452,1457 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1457,1463 ----
{
List *tableOIDs;
Relation *rels;
+ AcquireSampleRowsFunc *acquirefunc;
double *relblocks;
double totalblocks;
int numrows,
***************
*** 1486,1491 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1492,1499 ----
* 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++;
}
--- 1515,1554 ----
}
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,
--- 1566,1572 ----
{
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 &&
--- 1582,1593 ----
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 ----
***************
*** 557,562 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
--- 554,583 ----
stmt->relation->relpersistence,
&inheritOids, &old_constraints, &parentOidCount);
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * Don't allow a foreign table to inherit from parents that have OID
+ * system columns.
+ */
+ if (parentOidCount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot inherit from relation with OIDs")));
+
+ /*
+ * Reset the storage parameter for inherited attributes that have
+ * non-default values.
+ */
+ foreach(listptr, schema)
+ {
+ ColumnDef *colDef = lfirst(listptr);
+
+ if (colDef->storage != 0)
+ colDef->storage = 0;
+ }
+ }
+
/*
* Create a tuple descriptor from the relation schema. Note that this
* deals with column names, types, and NOT NULL constraints, but not
***************
*** 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;
--- 3040,3067 ----
* 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;
--- 3074,3081 ----
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)
--- 3093,3099 ----
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)
--- 3107,3113 ----
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,3159 **** 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;
--- 3175,3191 ----
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);
pass = AT_PASS_MISC;
***************
*** 3187,3193 **** 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);
--- 3219,3224 ----
***************
*** 4128,4134 **** 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
--- 4159,4166 ----
*/
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
***************
*** 4156,4163 **** 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);
}
}
--- 4188,4199 ----
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);
}
}
***************
*** 4447,4453 **** 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);
--- 4483,4489 ----
/* 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);
***************
*** 4743,4748 **** ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4779,4789 ----
/* 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 */
***************
*** 5343,5349 **** 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
--- 5384,5390 ----
/* 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
***************
*** 5735,5741 **** 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
--- 5776,5789 ----
/* 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
***************
*** 5746,5754 **** 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 */
--- 5794,5810 ----
* 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 */
***************
*** 7228,7234 **** 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);
--- 7284,7290 ----
/* 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);
***************
*** 7563,7569 **** 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,
--- 7619,7628 ----
* 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 <math.h>
+ #include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
***************
*** 2062,2067 **** reparameterize_path(PlannerInfo *root, Path *path,
--- 2063,2093 ----
case T_SubqueryScan:
return create_subqueryscan_path(root, rel, path->pathkeys,
required_outer);
+ case T_ForeignScan:
+ {
+ ForeignPath *newpath = NULL;
+
+ /* Let the FDW reparameterize the path node if possible */
+ if (rel->fdwroutine->ReparameterizeForeignPath != 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);
+
+ newpath =
+ rel->fdwroutine->ReparameterizeForeignPath(root,
+ rel,
+ rte->relid,
+ (ForeignPath *) path,
+ 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,191 ----
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 '@');
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/plain] foreign_inherit-v11.patch (62.0K, ../../533C01C7.6030809@lab.ntt.co.jp/2-foreign_inherit-v11.patch)
download | inline diff:
*** 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/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
***************
*** 162,167 **** GetForeignPaths (PlannerInfo *root,
--- 162,198 ----
<para>
<programlisting>
+ ForeignPath *
+ ReparameterizeForeignPath (PlannerInfo *root,
+ RelOptInfo *baserel,
+ Oid foreigntableid,
+ ForeignPath *path,
+ Relids required_outer);
+ </programlisting>
+
+ Create an access path for a scan on a foreign table using join
+ clauses, which is called a <quote>parameterized path</>.
+ This is called during query planning.
+ The parameters are as for <function>GetForeignRelSize</>, plus
+ the <structname>ForeignPath</> (previously produced by
+ <function>GetForeignPaths</>), and the IDs of all other tables
+ that provide the join clauses.
+ </para>
+
+ <para>
+ 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.
+ </para>
+
+ <para>
+ See <xref linkend="fdw-planning"> for additional information.
+ </para>
+
+ <para>
+ <programlisting>
ForeignScan *
GetForeignPlan (PlannerInfo *root,
RelOptInfo *baserel,
***************
*** 808,817 **** GetForeignServerByName(const char *name, bool missing_ok);
<para>
The FDW callback functions <function>GetForeignRelSize</>,
! <function>GetForeignPaths</>, <function>GetForeignPlan</>, and
! <function>PlanForeignModify</> must fit into the workings of the
! <productname>PostgreSQL</> planner. Here are some notes about what
! they must do.
</para>
<para>
--- 839,848 ----
<para>
The FDW callback functions <function>GetForeignRelSize</>,
! <function>GetForeignPaths</>, <function>ReparameterizeForeignPath</>,
! <function>GetForeignPlan</>, and <function>PlanForeignModify</> must fit
! into the workings of the <productname>PostgreSQL</> planner. Here are
! some notes about what they must do.
</para>
<para>
***************
*** 841,854 **** GetForeignServerByName(const char *name, bool missing_ok);
to initialize it to NULL when the <literal>baserel</> node is created.
It is useful for passing information forward from
<function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
! <function>GetForeignPaths</> to <function>GetForeignPlan</>, thereby
! avoiding recalculation.
</para>
<para>
! <function>GetForeignPaths</> can identify the meaning of different
! access paths by storing private information in the
! <structfield>fdw_private</> field of <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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
--- 872,888 ----
to initialize it to NULL when the <literal>baserel</> node is created.
It is useful for passing information forward from
<function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
! <function>GetForeignPaths</> to <function>ReparameterizeForeignPath</>
! and/or
! <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
! to <function>GetForeignPlan</>, thereby avoiding recalculation.
</para>
<para>
! <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
! can identify the meaning of different access paths by storing private
! information in the <structfield>fdw_private</> field of
! <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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
***************
*** 891,900 **** GetForeignServerByName(const char *name, bool missing_ok);
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
! clause should happen during <function>GetForeignPaths</>, since it would
! affect the cost estimate for the path. The path's
! <structfield>fdw_private</> field would probably include a pointer to
! the identified clause's <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from scan_clauses,
but add the <replaceable>sub_expression</> to <structfield>fdw_exprs</>
to ensure that it gets massaged into executable form. It would probably
--- 925,935 ----
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
! clause should happen during <function>GetForeignPaths</> or
! <function>ReparameterizeForeignPath</>, since it would affect the cost
! estimate for the path. The path's <structfield>fdw_private</> field
! would probably include a pointer to the identified clause's
! <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from scan_clauses,
but add the <replaceable>sub_expression</> to <structfield>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 ] <replaceable class="PARAMETER">name</replaceab
--- 42,51 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ ADD <replaceable class="PARAMETER">table_constraint</replaceable>
+ DROP CONSTRAINT [ IF EXISTS ] <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 149,154 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 153,202 ----
</varlistentry>
<varlistentry>
+ <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds a new constraint to a table using the same syntax as
+ <xref linkend="SQL-CREATEFOREIGNTABLE">.
+ 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.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DROP CONSTRAINT [ IF EXISTS ]</literal></term>
+ <listitem>
+ <para>
+ This form drops the specified constraint on a table.
+ If <literal>IF EXISTS</literal> is specified and the constraint
+ does not exist, no error is thrown. In this case a notice is issued instead.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OWNER</literal></term>
<listitem>
<para>
***************
*** 270,275 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 318,341 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">table_constraint</replaceable></term>
+ <listitem>
+ <para>
+ New table constraint for the table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">constraint_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing constraint to drop.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>CASCADE</literal></term>
<listitem>
<para>
***************
*** 290,295 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 356,371 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">new_owner</replaceable></term>
<listitem>
<para>
***************
*** 319,328 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint is
! added, or a column type is changed with <literal>SET DATA TYPE</>. It is
! the user's responsibility to ensure that the table definition matches the
! remote side.
</para>
<para>
--- 395,404 ----
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint or
! <literal>CHECK</> constraint is added, or a column type is changed with
! <literal>SET DATA TYPE</>. It is the user's responsibility to ensure that
! the table definition matches the remote side.
</para>
<para>
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 937,942 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 937,949 ----
</para>
<para>
+ If a table has any descendant tables that are foreign, a recursive
+ <literal>SET STORAGE</literal> operation will be rejected since it
+ is not permitted to add an <literal>oid</literal> system column to
+ foreign tables.
+ </para>
+
+ <para>
The <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>,
and <literal>TABLESPACE</> actions never recurse to descendant tables;
that is, they always act as though <literal>ONLY</> were specified.
***************
*** 945,950 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 952,970 ----
</para>
<para>
+ When adding a <literal>CHECK</> constraint with the <literal>NOT VALID
+ </literal> option recursively, an inherited constraint on a descendant
+ table that is foreign will be marked valid without checking
+ consistency with the foreign server.
+ </para>
+
+ <para>
+ A recursive <literal>SET STORAGE</literal> operation will make the
+ storage mode for a descendant table's column unchanged if the table is
+ foreign.
+ </para>
+
+ <para>
Changing any part of a system catalog table is not permitted.
</para>
*** a/doc/src/sgml/ref/analyze.sgml
--- b/doc/src/sgml/ref/analyze.sgml
***************
*** 200,205 **** ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
--- 200,212 ----
</para>
<para>
+ 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
+ <command>ANALYZE</command>, the command prints a warning and does nothing.
+ </para>
+
+ <para>
If the table being analyzed is completely empty, <command>ANALYZE</command>
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 ****
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
--- 19,29 ----
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
! | <replaceable>table_constraint</replaceable> }
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 30,36 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 32,44 ----
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ NOT NULL |
NULL |
+ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) |
DEFAULT <replaceable>default_expr</replaceable> }
+
+ <phrase>and <replaceable class="PARAMETER">table_constraint</replaceable> is:</phrase>
+
+ [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
+ { CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) }
</synopsis>
</refsynopsisdiv>
***************
*** 138,143 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 146,172 ----
</varlistentry>
<varlistentry>
+ <term><literal>CHECK ( <replaceable class="PARAMETER">expression</replaceable> )</literal></term>
+ <listitem>
+ <para>
+ The <literal>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.
+ </para>
+
+ <para>
+ Currently, <literal>CHECK</literal> expressions cannot contain
+ subqueries nor refer to variables other than columns of the
+ current row. The system column <literal>tableoid</literal>
+ may be referenced, but not any other system column.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>DEFAULT
<replaceable>default_expr</replaceable></literal></term>
<listitem>
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 188,205 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for the
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
***************
*** 187,192 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 228,251 ----
</refsect1>
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ Constraints on foreign tables are not enforced on insert or update.
+ Those definitions simply declare the constraints hold for all rows
+ in the foreign tables. It is the user's responsibility to ensure
+ that those definitions match the remote side. Such constraints are
+ used for some kind of query optimization such as constraint exclusion
+ for partitioned tables (see <xref linkend="ddl-partitioning">).
+ </para>
+
+ <para>
+ Since it is not permitted to add an <literal>oid</> system column to
+ foreign tables, the command will be rejected if any parent tables
+ have <literal>oid</> system columns.
+ </para>
+ </refsect1>
<refsect1 id="SQL-CREATEFOREIGNTABLE-examples">
<title>Examples</title>
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 2217,2222 **** AddRelationNewConstraints(Relation rel,
--- 2217,2228 ----
if (cdef->contype != CONSTR_CHECK)
continue;
+ if (cdef->is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("CHECK constraints on foreign tables cannot be marked NO INHERIT")));
+
if (cdef->raw_expr != NULL)
{
Assert(cdef->cooked_expr == NULL);
*** 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,125 ----
* 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)
--- 133,139 ----
elevel = DEBUG2;
/* Set up static variables */
+ vac_mode = vacmode;
vac_strategy = bstrategy;
/*
***************
*** 1452,1457 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1457,1463 ----
{
List *tableOIDs;
Relation *rels;
+ AcquireSampleRowsFunc *acquirefunc;
double *relblocks;
double totalblocks;
int numrows,
***************
*** 1486,1491 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1492,1499 ----
* 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++;
}
--- 1515,1554 ----
}
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,
--- 1566,1572 ----
{
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 &&
--- 1582,1593 ----
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 ----
***************
*** 557,562 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
--- 554,583 ----
stmt->relation->relpersistence,
&inheritOids, &old_constraints, &parentOidCount);
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * Don't allow a foreign table to inherit from parents that have OID
+ * system columns.
+ */
+ if (parentOidCount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot inherit from relation with OIDs")));
+
+ /*
+ * Reset the storage parameter for inherited attributes that have
+ * non-default values.
+ */
+ foreach(listptr, schema)
+ {
+ ColumnDef *colDef = lfirst(listptr);
+
+ if (colDef->storage != 0)
+ colDef->storage = 0;
+ }
+ }
+
/*
* Create a tuple descriptor from the relation schema. Note that this
* deals with column names, types, and NOT NULL constraints, but not
***************
*** 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;
--- 3040,3067 ----
* 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;
--- 3074,3081 ----
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)
--- 3093,3099 ----
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)
--- 3107,3113 ----
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,3159 **** 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;
--- 3175,3191 ----
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);
pass = AT_PASS_MISC;
***************
*** 3187,3193 **** 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);
--- 3219,3224 ----
***************
*** 4128,4134 **** 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
--- 4159,4166 ----
*/
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
***************
*** 4156,4163 **** 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);
}
}
--- 4188,4199 ----
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);
}
}
***************
*** 4447,4453 **** 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);
--- 4483,4489 ----
/* 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);
***************
*** 4743,4748 **** ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4779,4789 ----
/* 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 */
***************
*** 5343,5349 **** 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
--- 5384,5390 ----
/* 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
***************
*** 5735,5741 **** 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
--- 5776,5789 ----
/* 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
***************
*** 5746,5754 **** 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 */
--- 5794,5810 ----
* 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 */
***************
*** 7228,7234 **** 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);
--- 7284,7290 ----
/* 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);
***************
*** 7563,7569 **** 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,
--- 7619,7628 ----
* 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 <math.h>
+ #include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
***************
*** 2062,2067 **** reparameterize_path(PlannerInfo *root, Path *path,
--- 2063,2093 ----
case T_SubqueryScan:
return create_subqueryscan_path(root, rel, path->pathkeys,
required_outer);
+ case T_ForeignScan:
+ {
+ ForeignPath *newpath = NULL;
+
+ /* Let the FDW reparameterize the path node if possible */
+ if (rel->fdwroutine->ReparameterizeForeignPath != 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);
+
+ newpath =
+ rel->fdwroutine->ReparameterizeForeignPath(root,
+ rel,
+ rte->relid,
+ (ForeignPath *) path,
+ 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,191 ----
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 '@');
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-05-12 06:52 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-05-12 06:52 UTC (permalink / raw)
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
(2014/04/02 21:25), Etsuro Fujita wrote:
> Attached is v11.
>
> Changes:
>
> * Rebased to head
> * Improve an error message added to tablecmd.c to match it to existing
> ones there
> * Improve the documentaion a bit
I moved this to 2014-06.
Since I've merged with the initial patch by Hanada-san (1) a feature to
allow the inherited stats to be computed by the ANALYZE command and (2)
a new FDW routine for path reparameterization, I put my name on the author.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-06-20 08:04 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 2 replies; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2014-06-20 08:04 UTC (permalink / raw)
To: fujita.etsuro@lab.ntt.co.jp; +Cc: shigeru.hanada@gmail.com; pgsql-hackers
Hello,
Before continueing discussion, I tried this patch on the current
head.
This patch applies cleanly except one hunk because of a
modification just AFTER it, which did no harm. Finally all
regression tests suceeded.
Attached is the rebased patch of v11 up to the current master.
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-patch] foreign_inherit-v12.patch (51.0K, ../../20140620.170406.123403564.horiguchi.kyotaro@lab.ntt.co.jp/2-foreign_inherit-v12.patch)
download | inline diff:
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 5a4d5aa..5a9aec0 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -115,6 +115,11 @@ static void fileGetForeignRelSize(PlannerInfo *root,
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,
@@ -143,7 +148,7 @@ 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,
+ 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,
@@ -161,6 +166,7 @@ file_fdw_handler(PG_FUNCTION_ARGS)
fdwroutine->GetForeignRelSize = fileGetForeignRelSize;
fdwroutine->GetForeignPaths = fileGetForeignPaths;
+ fdwroutine->ReparameterizeForeignPath = fileReparameterizeForeignPath;
fdwroutine->GetForeignPlan = fileGetForeignPlan;
fdwroutine->ExplainForeignScan = fileExplainForeignScan;
fdwroutine->BeginForeignScan = fileBeginForeignScan;
@@ -509,7 +515,8 @@ fileGetForeignPaths(PlannerInfo *root,
(Node *) columns));
/* Estimate costs */
- estimate_costs(root, baserel, fdw_private,
+ estimate_costs(root, baserel,
+ fdw_private, NIL,
&startup_cost, &total_cost);
/*
@@ -534,6 +541,41 @@ fileGetForeignPaths(PlannerInfo *root,
}
/*
+ * 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
*/
@@ -962,12 +1004,13 @@ estimate_size(PlannerInfo *root, RelOptInfo *baserel,
*/
static void
estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
- FileFdwPlanState *fdw_private,
+ 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;
/*
@@ -978,8 +1021,11 @@ 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;
+ 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;
}
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 7dd43a9..3f7f7c2 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -239,6 +239,11 @@ static void postgresGetForeignRelSize(PlannerInfo *root,
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,
@@ -340,6 +345,7 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
/* Functions for scanning foreign tables */
routine->GetForeignRelSize = postgresGetForeignRelSize;
routine->GetForeignPaths = postgresGetForeignPaths;
+ routine->ReparameterizeForeignPath = postgresReparameterizeForeignPath;
routine->GetForeignPlan = postgresGetForeignPlan;
routine->BeginForeignScan = postgresBeginForeignScan;
routine->IterateForeignScan = postgresIterateForeignScan;
@@ -727,6 +733,48 @@ postgresGetForeignPaths(PlannerInfo *root,
}
/*
+ * 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
*/
@@ -1773,11 +1821,8 @@ 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);
+ Selectivity join_sel;
+ QualCost join_cost;
/* Use rows/width estimates made by set_baserel_size_estimates. */
rows = baserel->rows;
@@ -1790,17 +1835,29 @@ estimate_path_cost_size(PlannerInfo *root,
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 are being evaluated remotely,
- * too.
+ * 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;
- startup_cost += baserel->baserestrictcost.startup;
- cpu_per_tuple = cpu_tuple_cost + baserel->baserestrictcost.per_tuple;
+ 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;
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 6b5c8b7..ec1492a 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -162,6 +162,37 @@ GetForeignPaths (PlannerInfo *root,
<para>
<programlisting>
+ForeignPath *
+ReparameterizeForeignPath (PlannerInfo *root,
+ RelOptInfo *baserel,
+ Oid foreigntableid,
+ ForeignPath *path,
+ Relids required_outer);
+</programlisting>
+
+ Create an access path for a scan on a foreign table using join
+ clauses, which is called a <quote>parameterized path</>.
+ This is called during query planning.
+ The parameters are as for <function>GetForeignRelSize</>, plus
+ the <structname>ForeignPath</> (previously produced by
+ <function>GetForeignPaths</>), and the IDs of all other tables
+ that provide the join clauses.
+ </para>
+
+ <para>
+ 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.
+ </para>
+
+ <para>
+ See <xref linkend="fdw-planning"> for additional information.
+ </para>
+
+ <para>
+<programlisting>
ForeignScan *
GetForeignPlan (PlannerInfo *root,
RelOptInfo *baserel,
@@ -808,10 +839,10 @@ GetForeignServerByName(const char *name, bool missing_ok);
<para>
The FDW callback functions <function>GetForeignRelSize</>,
- <function>GetForeignPaths</>, <function>GetForeignPlan</>, and
- <function>PlanForeignModify</> must fit into the workings of the
- <productname>PostgreSQL</> planner. Here are some notes about what
- they must do.
+ <function>GetForeignPaths</>, <function>ReparameterizeForeignPath</>,
+ <function>GetForeignPlan</>, and <function>PlanForeignModify</> must fit
+ into the workings of the <productname>PostgreSQL</> planner. Here are
+ some notes about what they must do.
</para>
<para>
@@ -841,14 +872,17 @@ GetForeignServerByName(const char *name, bool missing_ok);
to initialize it to NULL when the <literal>baserel</> node is created.
It is useful for passing information forward from
<function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
- <function>GetForeignPaths</> to <function>GetForeignPlan</>, thereby
- avoiding recalculation.
+ <function>GetForeignPaths</> to <function>ReparameterizeForeignPath</>
+ and/or
+ <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
+ to <function>GetForeignPlan</>, thereby avoiding recalculation.
</para>
<para>
- <function>GetForeignPaths</> can identify the meaning of different
- access paths by storing private information in the
- <structfield>fdw_private</> field of <structname>ForeignPath</> nodes.
+ <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
+ can identify the meaning of different access paths by storing private
+ information in the <structfield>fdw_private</> field of
+ <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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
@@ -891,10 +925,11 @@ GetForeignServerByName(const char *name, bool missing_ok);
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
- clause should happen during <function>GetForeignPaths</>, since it would
- affect the cost estimate for the path. The path's
- <structfield>fdw_private</> field would probably include a pointer to
- the identified clause's <structname>RestrictInfo</> node. Then
+ clause should happen during <function>GetForeignPaths</> or
+ <function>ReparameterizeForeignPath</>, since it would affect the cost
+ estimate for the path. The path's <structfield>fdw_private</> field
+ would probably include a pointer to the identified clause's
+ <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from <literal>scan_clauses</>,
but add the <replaceable>sub_expression</> to <structfield>fdw_exprs</>
to ensure that it gets massaged into executable form. It would probably
diff --git a/doc/src/sgml/ref/alter_foreign_table.sgml b/doc/src/sgml/ref/alter_foreign_table.sgml
index 4d8cfc5..10461d5 100644
--- a/doc/src/sgml/ref/alter_foreign_table.sgml
+++ b/doc/src/sgml/ref/alter_foreign_table.sgml
@@ -42,6 +42,10 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ ADD <replaceable class="PARAMETER">table_constraint</replaceable>
+ DROP CONSTRAINT [ IF EXISTS ] <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
@@ -149,6 +153,50 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
</varlistentry>
<varlistentry>
+ <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds a new constraint to a table using the same syntax as
+ <xref linkend="SQL-CREATEFOREIGNTABLE">.
+ 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.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DROP CONSTRAINT [ IF EXISTS ]</literal></term>
+ <listitem>
+ <para>
+ This form drops the specified constraint on a table.
+ If <literal>IF EXISTS</literal> is specified and the constraint
+ does not exist, no error is thrown. In this case a notice is issued instead.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OWNER</literal></term>
<listitem>
<para>
@@ -270,6 +318,24 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">table_constraint</replaceable></term>
+ <listitem>
+ <para>
+ New table constraint for the table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">constraint_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing constraint to drop.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>CASCADE</literal></term>
<listitem>
<para>
@@ -290,6 +356,16 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">new_owner</replaceable></term>
<listitem>
<para>
@@ -319,10 +395,10 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
- <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint is
- added, or a column type is changed with <literal>SET DATA TYPE</>. It is
- the user's responsibility to ensure that the table definition matches the
- remote side.
+ <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint or
+ <literal>CHECK</> constraint is added, or a column type is changed with
+ <literal>SET DATA TYPE</>. It is the user's responsibility to ensure that
+ the table definition matches the remote side.
</para>
<para>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 69a1e14..eb1352e 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -967,6 +967,13 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
</para>
<para>
+ If a table has any descendant tables that are foreign, a recursive
+ <literal>SET STORAGE</literal> operation will be rejected since it
+ is not permitted to add an <literal>oid</literal> system column to
+ foreign tables.
+ </para>
+
+ <para>
The <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>,
and <literal>TABLESPACE</> actions never recurse to descendant tables;
that is, they always act as though <literal>ONLY</> were specified.
@@ -975,6 +982,19 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
</para>
<para>
+ When adding a <literal>CHECK</> constraint with the <literal>NOT VALID
+ </literal> option recursively, an inherited constraint on a descendant
+ table that is foreign will be marked valid without checking
+ consistency with the foreign server.
+ </para>
+
+ <para>
+ A recursive <literal>SET STORAGE</literal> operation will make the
+ storage mode for a descendant table's column unchanged if the table is
+ foreign.
+ </para>
+
+ <para>
Changing any part of a system catalog table is not permitted.
</para>
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 08d316a..ec257c5 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -200,6 +200,13 @@ ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
</para>
<para>
+ 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
+ <command>ANALYZE</command>, the command prints a warning and does nothing.
+ </para>
+
+ <para>
If the table being analyzed is completely empty, <command>ANALYZE</command>
will not record new statistics for that table. Any existing statistics
will be retained.
diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml
index 4a8cf38..1755b38 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -19,9 +19,11 @@
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
- <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+ { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+ | <replaceable>table_constraint</replaceable> }
[, ... ]
] )
+[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
@@ -30,7 +32,13 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ NOT NULL |
NULL |
+ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) |
DEFAULT <replaceable>default_expr</replaceable> }
+
+<phrase>and <replaceable class="PARAMETER">table_constraint</replaceable> is:</phrase>
+
+[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
+{ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) }
</synopsis>
</refsynopsisdiv>
@@ -138,6 +146,27 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
</varlistentry>
<varlistentry>
+ <term><literal>CHECK ( <replaceable class="PARAMETER">expression</replaceable> )</literal></term>
+ <listitem>
+ <para>
+ The <literal>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.
+ </para>
+
+ <para>
+ Currently, <literal>CHECK</literal> expressions cannot contain
+ subqueries nor refer to variables other than columns of the
+ current row. The system column <literal>tableoid</literal>
+ may be referenced, but not any other system column.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>DEFAULT
<replaceable>default_expr</replaceable></literal></term>
<listitem>
@@ -159,6 +188,18 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for the
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
@@ -187,6 +228,24 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
</refsect1>
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ Constraints on foreign tables are not enforced on insert or update.
+ Those definitions simply declare the constraints hold for all rows
+ in the foreign tables. It is the user's responsibility to ensure
+ that those definitions match the remote side. Such constraints are
+ used for some kind of query optimization such as constraint exclusion
+ for partitioned tables (see <xref linkend="ddl-partitioning">).
+ </para>
+
+ <para>
+ Since it is not permitted to add an <literal>oid</> system column to
+ foreign tables, the command will be rejected if any parent tables
+ have <literal>oid</> system columns.
+ </para>
+ </refsect1>
<refsect1 id="SQL-CREATEFOREIGNTABLE-examples">
<title>Examples</title>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 33eef9f..21d32c5 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2217,6 +2217,12 @@ AddRelationNewConstraints(Relation rel,
if (cdef->contype != CONSTR_CHECK)
continue;
+ if (cdef->is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("CHECK constraints on foreign tables cannot be marked NO INHERIT")));
+
if (cdef->raw_expr != NULL)
{
Assert(cdef->cooked_expr == NULL);
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c09ca7e..f67273c 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -82,6 +82,7 @@ int default_statistics_target = 100;
/* 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,7 +116,10 @@ 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;
@@ -129,6 +133,7 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
elevel = DEBUG2;
/* Set up static variables */
+ vac_mode = vacmode;
vac_strategy = bstrategy;
/*
@@ -1452,6 +1457,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
{
List *tableOIDs;
Relation *rels;
+ AcquireSampleRowsFunc *acquirefunc;
double *relblocks;
double totalblocks;
int numrows,
@@ -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,7 +1515,40 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
}
rels[nrels] = childrel;
- relblocks[nrels] = (double) RelationGetNumberOfBlocks(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,6 +1566,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 +1582,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 341262b..9e09906 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -310,7 +310,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,
@@ -466,10 +467,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,
@@ -556,6 +553,30 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
stmt->relation->relpersistence,
&inheritOids, &old_constraints, &parentOidCount);
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * Don't allow a foreign table to inherit from parents that have OID
+ * system columns.
+ */
+ if (parentOidCount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot inherit from relation with OIDs")));
+
+ /*
+ * Reset the storage parameter for inherited attributes that have
+ * non-default values.
+ */
+ foreach(listptr, schema)
+ {
+ ColumnDef *colDef = lfirst(listptr);
+
+ if (colDef->storage != 0)
+ colDef->storage = 0;
+ }
+ }
+
/*
* Create a tuple descriptor from the relation schema. Note that this
* deals with column names, types, and NOT NULL constraints, but not
@@ -3065,24 +3086,28 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
* rules.
*/
ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
- ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+ /* 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);
- ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+ /* 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);
- ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+ /* 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 */
- ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+ /* 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;
@@ -3095,7 +3120,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;
@@ -3113,7 +3139,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)
@@ -3127,7 +3153,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)
@@ -3195,11 +3221,17 @@ 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);
pass = AT_PASS_MISC;
@@ -3233,7 +3265,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);
@@ -4174,7 +4205,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
@@ -4202,8 +4234,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);
}
}
@@ -4493,7 +4529,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);
@@ -4789,6 +4825,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
/* 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 */
@@ -5389,7 +5430,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
@@ -5781,7 +5822,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 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
@@ -5792,9 +5840,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 = (Constraint *) 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 */
@@ -7274,7 +7330,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);
@@ -7609,7 +7665,10 @@ ATPrepAlterColumnType(List **wqueue,
* alter would put them out of step.
*/
if (recurse)
- ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+ {
+ /* 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,
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 3d2c739..b5e3ccf 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -61,6 +61,7 @@ int vacuum_multixact_freeze_table_age;
/* 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,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)
+ 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,7 +263,7 @@ vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
PushActiveSnapshot(GetTransactionSnapshot());
}
- analyze_rel(relid, vacstmt, vac_strategy);
+ analyze_rel(relid, vacstmt, vac_mode, vac_strategy);
if (use_own_xacts)
{
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 0410fdd..836fae6 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -1338,11 +1338,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 4e05dcd..880595f 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -16,6 +16,7 @@
#include <math.h>
+#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
@@ -2062,6 +2063,31 @@ reparameterize_path(PlannerInfo *root, Path *path,
case T_SubqueryScan:
return create_subqueryscan_path(root, rel, path->pathkeys,
required_outer);
+ case T_ForeignScan:
+ {
+ ForeignPath *newpath = NULL;
+
+ /* Let the FDW reparameterize the path node if possible */
+ if (rel->fdwroutine->ReparameterizeForeignPath != 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);
+
+ newpath =
+ rel->fdwroutine->ReparameterizeForeignPath(root,
+ rel,
+ rte->relid,
+ (ForeignPath *) path,
+ required_outer);
+ }
+ return (Path *) newpath;
+ }
default:
break;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 605c9b4..a206501 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -4213,32 +4213,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 7c1939f..949392a 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/commands/vacuum.h b/src/include/commands/vacuum.h
index d33552a..400e373 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,9 @@ 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/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 1b735da..5f3996a 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -31,6 +31,12 @@ typedef void (*GetForeignPaths_function) (PlannerInfo *root,
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,6 +123,7 @@ typedef struct FdwRoutine
/* Functions for scanning foreign tables */
GetForeignRelSize_function GetForeignRelSize;
GetForeignPaths_function GetForeignPaths;
+ ReparameterizeForeignPath_function ReparameterizeForeignPath;
GetForeignPlan_function GetForeignPlan;
BeginForeignScan_function BeginForeignScan;
IterateForeignScan_function IterateForeignScan;
diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out
index ff203b2..36df338 100644
--- a/src/test/regress/expected/foreign_data.out
+++ b/src/test/regress/expected/foreign_data.out
@@ -748,16 +748,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 0f0869e..8f9ea86 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 '@');
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-06-23 09:35 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: Ashutosh Bapat @ 2014-06-23 09:35 UTC (permalink / raw)
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
Hi,
Selecting tableoid on parent causes an error, "ERROR: cannot extract
system attribute from virtual tuple". The foreign table has an OID which
can be reported as tableoid for the rows coming from that foreign table. Do
we want to do that?
On Fri, Jun 20, 2014 at 1:34 PM, Kyotaro HORIGUCHI <
horiguchi.kyotaro@lab.ntt.co.jp> wrote:
> Hello,
>
> Before continueing discussion, I tried this patch on the current
> head.
>
> This patch applies cleanly except one hunk because of a
> modification just AFTER it, which did no harm. Finally all
> regression tests suceeded.
>
> Attached is the rebased patch of v11 up to the current master.
>
> regards,
>
> --
> Kyotaro Horiguchi
> NTT Open Source Software Center
>
>
> --
> Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-hackers
>
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-06-24 07:30 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 2 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-06-24 07:30 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
Hi Ashutosh,
Thank you for the review.
(2014/06/23 18:35), Ashutosh Bapat wrote:
> Hi,
> Selecting tableoid on parent causes an error, "ERROR: cannot extract
> system attribute from virtual tuple". The foreign table has an OID which
> can be reported as tableoid for the rows coming from that foreign table.
> Do we want to do that?
No. I think it's a bug. I'll fix it.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-06-30 06:52 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 2 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-06-30 06:52 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
(2014/06/24 16:30), Etsuro Fujita wrote:
> (2014/06/23 18:35), Ashutosh Bapat wrote:
>> Selecting tableoid on parent causes an error, "ERROR: cannot extract
>> system attribute from virtual tuple". The foreign table has an OID which
>> can be reported as tableoid for the rows coming from that foreign table.
>> Do we want to do that?
>
> No. I think it's a bug. I'll fix it.
Done. I think this is because create_foreignscan_plan() makes reference
to attr_needed, which isn't computed for inheritance children. To aboid
this, I've modified create_foreignscan_plan() to see reltargetlist and
baserestrictinfo, instead of attr_needed. Please find attached an
updated version of the patch.
Sorry for the delay.
Best regards,
Etsuro Fujita
*** a/contrib/file_fdw/file_fdw.c
--- b/contrib/file_fdw/file_fdw.c
***************
*** 115,120 **** static void fileGetForeignRelSize(PlannerInfo *root,
--- 115,125 ----
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,
***************
*** 143,149 **** 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,
--- 148,154 ----
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,
***************
*** 161,166 **** file_fdw_handler(PG_FUNCTION_ARGS)
--- 166,172 ----
fdwroutine->GetForeignRelSize = fileGetForeignRelSize;
fdwroutine->GetForeignPaths = fileGetForeignPaths;
+ fdwroutine->ReparameterizeForeignPath = fileReparameterizeForeignPath;
fdwroutine->GetForeignPlan = fileGetForeignPlan;
fdwroutine->ExplainForeignScan = fileExplainForeignScan;
fdwroutine->BeginForeignScan = fileBeginForeignScan;
***************
*** 509,515 **** fileGetForeignPaths(PlannerInfo *root,
(Node *) columns));
/* Estimate costs */
! estimate_costs(root, baserel, fdw_private,
&startup_cost, &total_cost);
/*
--- 515,522 ----
(Node *) columns));
/* Estimate costs */
! estimate_costs(root, baserel,
! fdw_private, NIL,
&startup_cost, &total_cost);
/*
***************
*** 534,539 **** fileGetForeignPaths(PlannerInfo *root,
--- 541,581 ----
}
/*
+ * 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
*/
***************
*** 962,973 **** 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;
/*
--- 1004,1016 ----
*/
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;
/*
***************
*** 978,985 **** 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;
}
--- 1021,1031 ----
*/
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
***************
*** 239,244 **** static void postgresGetForeignRelSize(PlannerInfo *root,
--- 239,249 ----
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,
***************
*** 340,345 **** postgres_fdw_handler(PG_FUNCTION_ARGS)
--- 345,351 ----
/* Functions for scanning foreign tables */
routine->GetForeignRelSize = postgresGetForeignRelSize;
routine->GetForeignPaths = postgresGetForeignPaths;
+ routine->ReparameterizeForeignPath = postgresReparameterizeForeignPath;
routine->GetForeignPlan = postgresGetForeignPlan;
routine->BeginForeignScan = postgresBeginForeignScan;
routine->IterateForeignScan = postgresIterateForeignScan;
***************
*** 727,732 **** postgresGetForeignPaths(PlannerInfo *root,
--- 733,780 ----
}
/*
+ * 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
*/
***************
*** 1773,1783 **** 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;
--- 1821,1828 ----
}
else
{
! Selectivity join_sel;
! QualCost join_cost;
/* Use rows/width estimates made by set_baserel_size_estimates. */
rows = baserel->rows;
***************
*** 1790,1806 **** 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;
--- 1835,1863 ----
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/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
***************
*** 162,167 **** GetForeignPaths (PlannerInfo *root,
--- 162,198 ----
<para>
<programlisting>
+ ForeignPath *
+ ReparameterizeForeignPath (PlannerInfo *root,
+ RelOptInfo *baserel,
+ Oid foreigntableid,
+ ForeignPath *path,
+ Relids required_outer);
+ </programlisting>
+
+ Create an access path for a scan on a foreign table using join
+ clauses, which is called a <quote>parameterized path</>.
+ This is called during query planning.
+ The parameters are as for <function>GetForeignRelSize</>, plus
+ the <structname>ForeignPath</> (previously produced by
+ <function>GetForeignPaths</>), and the IDs of all other tables
+ that provide the join clauses.
+ </para>
+
+ <para>
+ 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.
+ </para>
+
+ <para>
+ See <xref linkend="fdw-planning"> for additional information.
+ </para>
+
+ <para>
+ <programlisting>
ForeignScan *
GetForeignPlan (PlannerInfo *root,
RelOptInfo *baserel,
***************
*** 808,817 **** GetForeignServerByName(const char *name, bool missing_ok);
<para>
The FDW callback functions <function>GetForeignRelSize</>,
! <function>GetForeignPaths</>, <function>GetForeignPlan</>, and
! <function>PlanForeignModify</> must fit into the workings of the
! <productname>PostgreSQL</> planner. Here are some notes about what
! they must do.
</para>
<para>
--- 839,848 ----
<para>
The FDW callback functions <function>GetForeignRelSize</>,
! <function>GetForeignPaths</>, <function>ReparameterizeForeignPath</>,
! <function>GetForeignPlan</>, and <function>PlanForeignModify</> must fit
! into the workings of the <productname>PostgreSQL</> planner. Here are
! some notes about what they must do.
</para>
<para>
***************
*** 841,854 **** GetForeignServerByName(const char *name, bool missing_ok);
to initialize it to NULL when the <literal>baserel</> node is created.
It is useful for passing information forward from
<function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
! <function>GetForeignPaths</> to <function>GetForeignPlan</>, thereby
! avoiding recalculation.
</para>
<para>
! <function>GetForeignPaths</> can identify the meaning of different
! access paths by storing private information in the
! <structfield>fdw_private</> field of <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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
--- 872,888 ----
to initialize it to NULL when the <literal>baserel</> node is created.
It is useful for passing information forward from
<function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
! <function>GetForeignPaths</> to <function>ReparameterizeForeignPath</>
! and/or
! <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
! to <function>GetForeignPlan</>, thereby avoiding recalculation.
</para>
<para>
! <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
! can identify the meaning of different access paths by storing private
! information in the <structfield>fdw_private</> field of
! <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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
***************
*** 891,900 **** GetForeignServerByName(const char *name, bool missing_ok);
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
! clause should happen during <function>GetForeignPaths</>, since it would
! affect the cost estimate for the path. The path's
! <structfield>fdw_private</> field would probably include a pointer to
! the identified clause's <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from <literal>scan_clauses</>,
but add the <replaceable>sub_expression</> to <structfield>fdw_exprs</>
to ensure that it gets massaged into executable form. It would probably
--- 925,935 ----
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
! clause should happen during <function>GetForeignPaths</> or
! <function>ReparameterizeForeignPath</>, since it would affect the cost
! estimate for the path. The path's <structfield>fdw_private</> field
! would probably include a pointer to the identified clause's
! <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from <literal>scan_clauses</>,
but add the <replaceable>sub_expression</> to <structfield>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 ] <replaceable class="PARAMETER">name</replaceab
--- 42,51 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ ADD <replaceable class="PARAMETER">table_constraint</replaceable>
+ DROP CONSTRAINT [ IF EXISTS ] <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 149,154 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 153,202 ----
</varlistentry>
<varlistentry>
+ <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds a new constraint to a table using the same syntax as
+ <xref linkend="SQL-CREATEFOREIGNTABLE">.
+ 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.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DROP CONSTRAINT [ IF EXISTS ]</literal></term>
+ <listitem>
+ <para>
+ This form drops the specified constraint on a table.
+ If <literal>IF EXISTS</literal> is specified and the constraint
+ does not exist, no error is thrown. In this case a notice is issued instead.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OWNER</literal></term>
<listitem>
<para>
***************
*** 270,275 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 318,341 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">table_constraint</replaceable></term>
+ <listitem>
+ <para>
+ New table constraint for the table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">constraint_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing constraint to drop.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>CASCADE</literal></term>
<listitem>
<para>
***************
*** 290,295 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 356,371 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">new_owner</replaceable></term>
<listitem>
<para>
***************
*** 319,328 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint is
! added, or a column type is changed with <literal>SET DATA TYPE</>. It is
! the user's responsibility to ensure that the table definition matches the
! remote side.
</para>
<para>
--- 395,404 ----
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint or
! <literal>CHECK</> constraint is added, or a column type is changed with
! <literal>SET DATA TYPE</>. It is the user's responsibility to ensure that
! the table definition matches the remote side.
</para>
<para>
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 967,972 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 967,979 ----
</para>
<para>
+ If a table has any descendant tables that are foreign, a recursive
+ <literal>SET STORAGE</literal> operation will be rejected since it
+ is not permitted to add an <literal>oid</literal> system column to
+ foreign tables.
+ </para>
+
+ <para>
The <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>,
and <literal>TABLESPACE</> actions never recurse to descendant tables;
that is, they always act as though <literal>ONLY</> were specified.
***************
*** 975,980 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 982,1000 ----
</para>
<para>
+ When adding a <literal>CHECK</> constraint with the <literal>NOT VALID
+ </literal> option recursively, an inherited constraint on a descendant
+ table that is foreign will be marked valid without checking
+ consistency with the foreign server.
+ </para>
+
+ <para>
+ A recursive <literal>SET STORAGE</literal> operation will make the
+ storage mode for a descendant table's column unchanged if the table is
+ foreign.
+ </para>
+
+ <para>
Changing any part of a system catalog table is not permitted.
</para>
*** a/doc/src/sgml/ref/analyze.sgml
--- b/doc/src/sgml/ref/analyze.sgml
***************
*** 200,205 **** ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
--- 200,212 ----
</para>
<para>
+ 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
+ <command>ANALYZE</command>, the command prints a warning and does nothing.
+ </para>
+
+ <para>
If the table being analyzed is completely empty, <command>ANALYZE</command>
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 ****
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
--- 19,29 ----
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
! | <replaceable>table_constraint</replaceable> }
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 30,36 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 32,44 ----
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ NOT NULL |
NULL |
+ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) |
DEFAULT <replaceable>default_expr</replaceable> }
+
+ <phrase>and <replaceable class="PARAMETER">table_constraint</replaceable> is:</phrase>
+
+ [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
+ { CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) }
</synopsis>
</refsynopsisdiv>
***************
*** 138,143 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 146,172 ----
</varlistentry>
<varlistentry>
+ <term><literal>CHECK ( <replaceable class="PARAMETER">expression</replaceable> )</literal></term>
+ <listitem>
+ <para>
+ The <literal>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.
+ </para>
+
+ <para>
+ Currently, <literal>CHECK</literal> expressions cannot contain
+ subqueries nor refer to variables other than columns of the
+ current row. The system column <literal>tableoid</literal>
+ may be referenced, but not any other system column.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>DEFAULT
<replaceable>default_expr</replaceable></literal></term>
<listitem>
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 188,205 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for the
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
***************
*** 187,192 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 228,251 ----
</refsect1>
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ Constraints on foreign tables are not enforced on insert or update.
+ Those definitions simply declare the constraints hold for all rows
+ in the foreign tables. It is the user's responsibility to ensure
+ that those definitions match the remote side. Such constraints are
+ used for some kind of query optimization such as constraint exclusion
+ for partitioned tables (see <xref linkend="ddl-partitioning">).
+ </para>
+
+ <para>
+ Since it is not permitted to add an <literal>oid</> system column to
+ foreign tables, the command will be rejected if any parent tables
+ have <literal>oid</> system columns.
+ </para>
+ </refsect1>
<refsect1 id="SQL-CREATEFOREIGNTABLE-examples">
<title>Examples</title>
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 2217,2222 **** AddRelationNewConstraints(Relation rel,
--- 2217,2228 ----
if (cdef->contype != CONSTR_CHECK)
continue;
+ if (cdef->is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("CHECK constraints on foreign tables cannot be marked NO INHERIT")));
+
if (cdef->raw_expr != NULL)
{
Assert(cdef->cooked_expr == NULL);
*** 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,125 ----
* 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)
--- 133,139 ----
elevel = DEBUG2;
/* Set up static variables */
+ vac_mode = vacmode;
vac_strategy = bstrategy;
/*
***************
*** 1452,1457 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1457,1463 ----
{
List *tableOIDs;
Relation *rels;
+ AcquireSampleRowsFunc *acquirefunc;
double *relblocks;
double totalblocks;
int numrows,
***************
*** 1486,1491 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1492,1499 ----
* 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++;
}
--- 1515,1554 ----
}
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,
--- 1566,1572 ----
{
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 &&
--- 1582,1593 ----
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
***************
*** 310,316 **** 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,
--- 310,317 ----
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,
***************
*** 466,475 **** 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,
--- 467,472 ----
***************
*** 556,561 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
--- 553,582 ----
stmt->relation->relpersistence,
&inheritOids, &old_constraints, &parentOidCount);
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * Don't allow a foreign table to inherit from parents that have OID
+ * system columns.
+ */
+ if (parentOidCount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot inherit from relation with OIDs")));
+
+ /*
+ * Reset the storage parameter for inherited attributes that have
+ * non-default values.
+ */
+ foreach(listptr, schema)
+ {
+ ColumnDef *colDef = lfirst(listptr);
+
+ if (colDef->storage != 0)
+ colDef->storage = 0;
+ }
+ }
+
/*
* Create a tuple descriptor from the relation schema. Note that this
* deals with column names, types, and NOT NULL constraints, but not
***************
*** 3064,3087 **** 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;
--- 3085,3112 ----
* 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;
***************
*** 3094,3100 **** 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;
--- 3119,3126 ----
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;
***************
*** 3112,3118 **** 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)
--- 3138,3144 ----
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)
***************
*** 3126,3132 **** 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)
--- 3152,3158 ----
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)
***************
*** 3194,3204 **** 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;
--- 3220,3236 ----
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);
pass = AT_PASS_MISC;
***************
*** 3232,3238 **** 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);
--- 3264,3269 ----
***************
*** 4173,4179 **** 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
--- 4204,4211 ----
*/
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
***************
*** 4201,4208 **** 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);
}
}
--- 4233,4244 ----
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);
}
}
***************
*** 4492,4498 **** 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);
--- 4528,4534 ----
/* 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);
***************
*** 4788,4793 **** ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4824,4834 ----
/* 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 */
***************
*** 5388,5394 **** 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
--- 5429,5435 ----
/* 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
***************
*** 5780,5786 **** 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
--- 5821,5834 ----
/* 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
***************
*** 5791,5799 **** 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 */
--- 5839,5855 ----
* 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 */
***************
*** 7273,7279 **** 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);
--- 7329,7335 ----
/* 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);
***************
*** 7608,7614 **** 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,
--- 7664,7673 ----
* 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/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 1945,1950 **** create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
--- 1945,1952 ----
RelOptInfo *rel = best_path->path.parent;
Index scan_relid = rel->relid;
RangeTblEntry *rte;
+ Bitmapset *attrs_used = NULL;
+ ListCell *lc;
int i;
/* it should be a base rel... */
***************
*** 1993,2008 **** create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
* bit of a kluge and might go away someday, so we intentionally leave it
* out of the API presented to FDWs.
*/
scan_plan->fsSystemCol = false;
for (i = rel->min_attr; i < 0; i++)
{
! if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
{
scan_plan->fsSystemCol = true;
break;
}
}
return scan_plan;
}
--- 1995,2029 ----
* bit of a kluge and might go away someday, so we intentionally leave it
* out of the API presented to FDWs.
*/
+
+ /*
+ * Add all the attributes needed for joins or final output. Note: we must
+ * look at reltargetlist, not the attr_needed data, because attr_needed
+ * isn't computed for inheritance child rels.
+ */
+ pull_varattnos((Node *) rel->reltargetlist, rel->relid, &attrs_used);
+
+ /* Add all the attributes used by restriction clauses. */
+ foreach(lc, rel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ pull_varattnos((Node *) rinfo->clause, rel->relid, &attrs_used);
+ }
+
+ /* Are any system columns requested? */
scan_plan->fsSystemCol = false;
for (i = rel->min_attr; i < 0; i++)
{
! if (bms_is_member(i - rel->min_attr, attrs_used))
{
scan_plan->fsSystemCol = true;
break;
}
}
+ bms_free(attrs_used);
+
return scan_plan;
}
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 1338,1348 **** 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);
--- 1338,1349 ----
/*
* 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 <math.h>
+ #include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
***************
*** 2062,2067 **** reparameterize_path(PlannerInfo *root, Path *path,
--- 2063,2093 ----
case T_SubqueryScan:
return create_subqueryscan_path(root, rel, path->pathkeys,
required_outer);
+ case T_ForeignScan:
+ {
+ ForeignPath *newpath = NULL;
+
+ /* Let the FDW reparameterize the path node if possible */
+ if (rel->fdwroutine->ReparameterizeForeignPath != 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);
+
+ newpath =
+ rel->fdwroutine->ReparameterizeForeignPath(root,
+ rel,
+ rte->relid,
+ (ForeignPath *) path,
+ required_outer);
+ }
+ return (Path *) newpath;
+ }
default:
break;
}
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 4213,4244 **** 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;
}
;
--- 4213,4244 ----
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,191 ----
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
***************
*** 748,763 **** 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;
--- 748,759 ----
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 '@');
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/plain] foreign_inherit-v13.patch (63.8K, ../../53B10914.2010504@lab.ntt.co.jp/2-foreign_inherit-v13.patch)
download | inline diff:
*** a/contrib/file_fdw/file_fdw.c
--- b/contrib/file_fdw/file_fdw.c
***************
*** 115,120 **** static void fileGetForeignRelSize(PlannerInfo *root,
--- 115,125 ----
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,
***************
*** 143,149 **** 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,
--- 148,154 ----
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,
***************
*** 161,166 **** file_fdw_handler(PG_FUNCTION_ARGS)
--- 166,172 ----
fdwroutine->GetForeignRelSize = fileGetForeignRelSize;
fdwroutine->GetForeignPaths = fileGetForeignPaths;
+ fdwroutine->ReparameterizeForeignPath = fileReparameterizeForeignPath;
fdwroutine->GetForeignPlan = fileGetForeignPlan;
fdwroutine->ExplainForeignScan = fileExplainForeignScan;
fdwroutine->BeginForeignScan = fileBeginForeignScan;
***************
*** 509,515 **** fileGetForeignPaths(PlannerInfo *root,
(Node *) columns));
/* Estimate costs */
! estimate_costs(root, baserel, fdw_private,
&startup_cost, &total_cost);
/*
--- 515,522 ----
(Node *) columns));
/* Estimate costs */
! estimate_costs(root, baserel,
! fdw_private, NIL,
&startup_cost, &total_cost);
/*
***************
*** 534,539 **** fileGetForeignPaths(PlannerInfo *root,
--- 541,581 ----
}
/*
+ * 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
*/
***************
*** 962,973 **** 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;
/*
--- 1004,1016 ----
*/
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;
/*
***************
*** 978,985 **** 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;
}
--- 1021,1031 ----
*/
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
***************
*** 239,244 **** static void postgresGetForeignRelSize(PlannerInfo *root,
--- 239,249 ----
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,
***************
*** 340,345 **** postgres_fdw_handler(PG_FUNCTION_ARGS)
--- 345,351 ----
/* Functions for scanning foreign tables */
routine->GetForeignRelSize = postgresGetForeignRelSize;
routine->GetForeignPaths = postgresGetForeignPaths;
+ routine->ReparameterizeForeignPath = postgresReparameterizeForeignPath;
routine->GetForeignPlan = postgresGetForeignPlan;
routine->BeginForeignScan = postgresBeginForeignScan;
routine->IterateForeignScan = postgresIterateForeignScan;
***************
*** 727,732 **** postgresGetForeignPaths(PlannerInfo *root,
--- 733,780 ----
}
/*
+ * 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
*/
***************
*** 1773,1783 **** 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;
--- 1821,1828 ----
}
else
{
! Selectivity join_sel;
! QualCost join_cost;
/* Use rows/width estimates made by set_baserel_size_estimates. */
rows = baserel->rows;
***************
*** 1790,1806 **** 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;
--- 1835,1863 ----
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/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
***************
*** 162,167 **** GetForeignPaths (PlannerInfo *root,
--- 162,198 ----
<para>
<programlisting>
+ ForeignPath *
+ ReparameterizeForeignPath (PlannerInfo *root,
+ RelOptInfo *baserel,
+ Oid foreigntableid,
+ ForeignPath *path,
+ Relids required_outer);
+ </programlisting>
+
+ Create an access path for a scan on a foreign table using join
+ clauses, which is called a <quote>parameterized path</>.
+ This is called during query planning.
+ The parameters are as for <function>GetForeignRelSize</>, plus
+ the <structname>ForeignPath</> (previously produced by
+ <function>GetForeignPaths</>), and the IDs of all other tables
+ that provide the join clauses.
+ </para>
+
+ <para>
+ 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.
+ </para>
+
+ <para>
+ See <xref linkend="fdw-planning"> for additional information.
+ </para>
+
+ <para>
+ <programlisting>
ForeignScan *
GetForeignPlan (PlannerInfo *root,
RelOptInfo *baserel,
***************
*** 808,817 **** GetForeignServerByName(const char *name, bool missing_ok);
<para>
The FDW callback functions <function>GetForeignRelSize</>,
! <function>GetForeignPaths</>, <function>GetForeignPlan</>, and
! <function>PlanForeignModify</> must fit into the workings of the
! <productname>PostgreSQL</> planner. Here are some notes about what
! they must do.
</para>
<para>
--- 839,848 ----
<para>
The FDW callback functions <function>GetForeignRelSize</>,
! <function>GetForeignPaths</>, <function>ReparameterizeForeignPath</>,
! <function>GetForeignPlan</>, and <function>PlanForeignModify</> must fit
! into the workings of the <productname>PostgreSQL</> planner. Here are
! some notes about what they must do.
</para>
<para>
***************
*** 841,854 **** GetForeignServerByName(const char *name, bool missing_ok);
to initialize it to NULL when the <literal>baserel</> node is created.
It is useful for passing information forward from
<function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
! <function>GetForeignPaths</> to <function>GetForeignPlan</>, thereby
! avoiding recalculation.
</para>
<para>
! <function>GetForeignPaths</> can identify the meaning of different
! access paths by storing private information in the
! <structfield>fdw_private</> field of <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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
--- 872,888 ----
to initialize it to NULL when the <literal>baserel</> node is created.
It is useful for passing information forward from
<function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
! <function>GetForeignPaths</> to <function>ReparameterizeForeignPath</>
! and/or
! <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
! to <function>GetForeignPlan</>, thereby avoiding recalculation.
</para>
<para>
! <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
! can identify the meaning of different access paths by storing private
! information in the <structfield>fdw_private</> field of
! <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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
***************
*** 891,900 **** GetForeignServerByName(const char *name, bool missing_ok);
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
! clause should happen during <function>GetForeignPaths</>, since it would
! affect the cost estimate for the path. The path's
! <structfield>fdw_private</> field would probably include a pointer to
! the identified clause's <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from <literal>scan_clauses</>,
but add the <replaceable>sub_expression</> to <structfield>fdw_exprs</>
to ensure that it gets massaged into executable form. It would probably
--- 925,935 ----
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
! clause should happen during <function>GetForeignPaths</> or
! <function>ReparameterizeForeignPath</>, since it would affect the cost
! estimate for the path. The path's <structfield>fdw_private</> field
! would probably include a pointer to the identified clause's
! <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from <literal>scan_clauses</>,
but add the <replaceable>sub_expression</> to <structfield>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 ] <replaceable class="PARAMETER">name</replaceab
--- 42,51 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ ADD <replaceable class="PARAMETER">table_constraint</replaceable>
+ DROP CONSTRAINT [ IF EXISTS ] <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 149,154 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 153,202 ----
</varlistentry>
<varlistentry>
+ <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds a new constraint to a table using the same syntax as
+ <xref linkend="SQL-CREATEFOREIGNTABLE">.
+ 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.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DROP CONSTRAINT [ IF EXISTS ]</literal></term>
+ <listitem>
+ <para>
+ This form drops the specified constraint on a table.
+ If <literal>IF EXISTS</literal> is specified and the constraint
+ does not exist, no error is thrown. In this case a notice is issued instead.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OWNER</literal></term>
<listitem>
<para>
***************
*** 270,275 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 318,341 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">table_constraint</replaceable></term>
+ <listitem>
+ <para>
+ New table constraint for the table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">constraint_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing constraint to drop.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>CASCADE</literal></term>
<listitem>
<para>
***************
*** 290,295 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 356,371 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">new_owner</replaceable></term>
<listitem>
<para>
***************
*** 319,328 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint is
! added, or a column type is changed with <literal>SET DATA TYPE</>. It is
! the user's responsibility to ensure that the table definition matches the
! remote side.
</para>
<para>
--- 395,404 ----
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint or
! <literal>CHECK</> constraint is added, or a column type is changed with
! <literal>SET DATA TYPE</>. It is the user's responsibility to ensure that
! the table definition matches the remote side.
</para>
<para>
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 967,972 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 967,979 ----
</para>
<para>
+ If a table has any descendant tables that are foreign, a recursive
+ <literal>SET STORAGE</literal> operation will be rejected since it
+ is not permitted to add an <literal>oid</literal> system column to
+ foreign tables.
+ </para>
+
+ <para>
The <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>,
and <literal>TABLESPACE</> actions never recurse to descendant tables;
that is, they always act as though <literal>ONLY</> were specified.
***************
*** 975,980 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 982,1000 ----
</para>
<para>
+ When adding a <literal>CHECK</> constraint with the <literal>NOT VALID
+ </literal> option recursively, an inherited constraint on a descendant
+ table that is foreign will be marked valid without checking
+ consistency with the foreign server.
+ </para>
+
+ <para>
+ A recursive <literal>SET STORAGE</literal> operation will make the
+ storage mode for a descendant table's column unchanged if the table is
+ foreign.
+ </para>
+
+ <para>
Changing any part of a system catalog table is not permitted.
</para>
*** a/doc/src/sgml/ref/analyze.sgml
--- b/doc/src/sgml/ref/analyze.sgml
***************
*** 200,205 **** ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
--- 200,212 ----
</para>
<para>
+ 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
+ <command>ANALYZE</command>, the command prints a warning and does nothing.
+ </para>
+
+ <para>
If the table being analyzed is completely empty, <command>ANALYZE</command>
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 ****
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
--- 19,29 ----
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
! | <replaceable>table_constraint</replaceable> }
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 30,36 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 32,44 ----
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ NOT NULL |
NULL |
+ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) |
DEFAULT <replaceable>default_expr</replaceable> }
+
+ <phrase>and <replaceable class="PARAMETER">table_constraint</replaceable> is:</phrase>
+
+ [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
+ { CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) }
</synopsis>
</refsynopsisdiv>
***************
*** 138,143 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 146,172 ----
</varlistentry>
<varlistentry>
+ <term><literal>CHECK ( <replaceable class="PARAMETER">expression</replaceable> )</literal></term>
+ <listitem>
+ <para>
+ The <literal>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.
+ </para>
+
+ <para>
+ Currently, <literal>CHECK</literal> expressions cannot contain
+ subqueries nor refer to variables other than columns of the
+ current row. The system column <literal>tableoid</literal>
+ may be referenced, but not any other system column.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>DEFAULT
<replaceable>default_expr</replaceable></literal></term>
<listitem>
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 188,205 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for the
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
***************
*** 187,192 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 228,251 ----
</refsect1>
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ Constraints on foreign tables are not enforced on insert or update.
+ Those definitions simply declare the constraints hold for all rows
+ in the foreign tables. It is the user's responsibility to ensure
+ that those definitions match the remote side. Such constraints are
+ used for some kind of query optimization such as constraint exclusion
+ for partitioned tables (see <xref linkend="ddl-partitioning">).
+ </para>
+
+ <para>
+ Since it is not permitted to add an <literal>oid</> system column to
+ foreign tables, the command will be rejected if any parent tables
+ have <literal>oid</> system columns.
+ </para>
+ </refsect1>
<refsect1 id="SQL-CREATEFOREIGNTABLE-examples">
<title>Examples</title>
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 2217,2222 **** AddRelationNewConstraints(Relation rel,
--- 2217,2228 ----
if (cdef->contype != CONSTR_CHECK)
continue;
+ if (cdef->is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("CHECK constraints on foreign tables cannot be marked NO INHERIT")));
+
if (cdef->raw_expr != NULL)
{
Assert(cdef->cooked_expr == NULL);
*** 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,125 ----
* 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)
--- 133,139 ----
elevel = DEBUG2;
/* Set up static variables */
+ vac_mode = vacmode;
vac_strategy = bstrategy;
/*
***************
*** 1452,1457 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1457,1463 ----
{
List *tableOIDs;
Relation *rels;
+ AcquireSampleRowsFunc *acquirefunc;
double *relblocks;
double totalblocks;
int numrows,
***************
*** 1486,1491 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1492,1499 ----
* 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++;
}
--- 1515,1554 ----
}
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,
--- 1566,1572 ----
{
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 &&
--- 1582,1593 ----
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
***************
*** 310,316 **** 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,
--- 310,317 ----
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,
***************
*** 466,475 **** 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,
--- 467,472 ----
***************
*** 556,561 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
--- 553,582 ----
stmt->relation->relpersistence,
&inheritOids, &old_constraints, &parentOidCount);
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * Don't allow a foreign table to inherit from parents that have OID
+ * system columns.
+ */
+ if (parentOidCount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot inherit from relation with OIDs")));
+
+ /*
+ * Reset the storage parameter for inherited attributes that have
+ * non-default values.
+ */
+ foreach(listptr, schema)
+ {
+ ColumnDef *colDef = lfirst(listptr);
+
+ if (colDef->storage != 0)
+ colDef->storage = 0;
+ }
+ }
+
/*
* Create a tuple descriptor from the relation schema. Note that this
* deals with column names, types, and NOT NULL constraints, but not
***************
*** 3064,3087 **** 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;
--- 3085,3112 ----
* 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;
***************
*** 3094,3100 **** 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;
--- 3119,3126 ----
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;
***************
*** 3112,3118 **** 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)
--- 3138,3144 ----
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)
***************
*** 3126,3132 **** 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)
--- 3152,3158 ----
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)
***************
*** 3194,3204 **** 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;
--- 3220,3236 ----
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);
pass = AT_PASS_MISC;
***************
*** 3232,3238 **** 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);
--- 3264,3269 ----
***************
*** 4173,4179 **** 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
--- 4204,4211 ----
*/
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
***************
*** 4201,4208 **** 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);
}
}
--- 4233,4244 ----
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);
}
}
***************
*** 4492,4498 **** 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);
--- 4528,4534 ----
/* 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);
***************
*** 4788,4793 **** ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4824,4834 ----
/* 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 */
***************
*** 5388,5394 **** 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
--- 5429,5435 ----
/* 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
***************
*** 5780,5786 **** 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
--- 5821,5834 ----
/* 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
***************
*** 5791,5799 **** 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 */
--- 5839,5855 ----
* 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 */
***************
*** 7273,7279 **** 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);
--- 7329,7335 ----
/* 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);
***************
*** 7608,7614 **** 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,
--- 7664,7673 ----
* 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/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 1945,1950 **** create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
--- 1945,1952 ----
RelOptInfo *rel = best_path->path.parent;
Index scan_relid = rel->relid;
RangeTblEntry *rte;
+ Bitmapset *attrs_used = NULL;
+ ListCell *lc;
int i;
/* it should be a base rel... */
***************
*** 1993,2008 **** create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
* bit of a kluge and might go away someday, so we intentionally leave it
* out of the API presented to FDWs.
*/
scan_plan->fsSystemCol = false;
for (i = rel->min_attr; i < 0; i++)
{
! if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
{
scan_plan->fsSystemCol = true;
break;
}
}
return scan_plan;
}
--- 1995,2029 ----
* bit of a kluge and might go away someday, so we intentionally leave it
* out of the API presented to FDWs.
*/
+
+ /*
+ * Add all the attributes needed for joins or final output. Note: we must
+ * look at reltargetlist, not the attr_needed data, because attr_needed
+ * isn't computed for inheritance child rels.
+ */
+ pull_varattnos((Node *) rel->reltargetlist, rel->relid, &attrs_used);
+
+ /* Add all the attributes used by restriction clauses. */
+ foreach(lc, rel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ pull_varattnos((Node *) rinfo->clause, rel->relid, &attrs_used);
+ }
+
+ /* Are any system columns requested? */
scan_plan->fsSystemCol = false;
for (i = rel->min_attr; i < 0; i++)
{
! if (bms_is_member(i - rel->min_attr, attrs_used))
{
scan_plan->fsSystemCol = true;
break;
}
}
+ bms_free(attrs_used);
+
return scan_plan;
}
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 1338,1348 **** 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);
--- 1338,1349 ----
/*
* 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 <math.h>
+ #include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
***************
*** 2062,2067 **** reparameterize_path(PlannerInfo *root, Path *path,
--- 2063,2093 ----
case T_SubqueryScan:
return create_subqueryscan_path(root, rel, path->pathkeys,
required_outer);
+ case T_ForeignScan:
+ {
+ ForeignPath *newpath = NULL;
+
+ /* Let the FDW reparameterize the path node if possible */
+ if (rel->fdwroutine->ReparameterizeForeignPath != 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);
+
+ newpath =
+ rel->fdwroutine->ReparameterizeForeignPath(root,
+ rel,
+ rte->relid,
+ (ForeignPath *) path,
+ required_outer);
+ }
+ return (Path *) newpath;
+ }
default:
break;
}
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 4213,4244 **** 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;
}
;
--- 4213,4244 ----
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,191 ----
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
***************
*** 748,763 **** 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;
--- 748,759 ----
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 '@');
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-06-30 08:47 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: Ashutosh Bapat @ 2014-06-30 08:47 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
I checked that it's reporting the right tableoid now.
BTW, why aren't you using the tlist passed to this function? I guess
create_scan_plan() passes tlist after processing it, so that should be used
rather than rel->reltargetlist.
On Mon, Jun 30, 2014 at 12:22 PM, Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp
> wrote:
> (2014/06/24 16:30), Etsuro Fujita wrote:
>
>> (2014/06/23 18:35), Ashutosh Bapat wrote:
>>
>
> Selecting tableoid on parent causes an error, "ERROR: cannot extract
>>> system attribute from virtual tuple". The foreign table has an OID which
>>> can be reported as tableoid for the rows coming from that foreign table.
>>> Do we want to do that?
>>>
>>
>> No. I think it's a bug. I'll fix it.
>>
>
> Done. I think this is because create_foreignscan_plan() makes reference
> to attr_needed, which isn't computed for inheritance children. To aboid
> this, I've modified create_foreignscan_plan() to see reltargetlist and
> baserestrictinfo, instead of attr_needed. Please find attached an updated
> version of the patch.
>
>
> Sorry for the delay.
>
> Best regards,
> Etsuro Fujita
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-06-30 10:47 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-06-30 10:47 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
(2014/06/30 17:47), Ashutosh Bapat wrote:
> I checked that it's reporting the right tableoid now.
Thank you for the check.
> BTW, why aren't you using the tlist passed to this function? I guess
> create_scan_plan() passes tlist after processing it, so that should be
> used rather than rel->reltargetlist.
I think that that would be maybe OK, but I think that it would not be
efficient to use the list to compute attrs_used, because the tlist would
have more information than rel->reltargetlist in cases where the tlist
is build through build_physical_tlist().
Thanks,
Best regards,
Etsuro Fujita
>
> On Mon, Jun 30, 2014 at 12:22 PM, Etsuro Fujita
> <fujita.etsuro@lab.ntt.co.jp <mailto:fujita.etsuro@lab.ntt.co.jp>> wrote:
>
> (2014/06/24 16:30), Etsuro Fujita wrote:
>
> (2014/06/23 18:35), Ashutosh Bapat wrote:
>
>
> Selecting tableoid on parent causes an error, "ERROR:
> cannot extract
> system attribute from virtual tuple". The foreign table has
> an OID which
> can be reported as tableoid for the rows coming from that
> foreign table.
> Do we want to do that?
>
>
> No. I think it's a bug. I'll fix it.
>
>
> Done. I think this is because create_foreignscan_plan() makes
> reference to attr_needed, which isn't computed for inheritance
> children. To aboid this, I've modified create_foreignscan_plan() to
> see reltargetlist and baserestrictinfo, instead of attr_needed.
> Please find attached an updated version of the patch.
>
>
> Sorry for the delay.
>
> Best regards,
> Etsuro Fujita
>
>
>
>
> --
> Best Wishes,
> Ashutosh Bapat
> EnterpriseDB Corporation
> The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-06-30 11:17 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 2 replies; 179+ messages in thread
From: Ashutosh Bapat @ 2014-06-30 11:17 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On Mon, Jun 30, 2014 at 4:17 PM, Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
wrote:
> (2014/06/30 17:47), Ashutosh Bapat wrote:
>
>> I checked that it's reporting the right tableoid now.
>>
>
> Thank you for the check.
>
>
> BTW, why aren't you using the tlist passed to this function? I guess
>> create_scan_plan() passes tlist after processing it, so that should be
>> used rather than rel->reltargetlist.
>>
>
> I think that that would be maybe OK, but I think that it would not be
> efficient to use the list to compute attrs_used, because the tlist would
> have more information than rel->reltargetlist in cases where the tlist is
> build through build_physical_tlist().
>
>
In that case, we can call build_relation_tlist() for foreign tables.
>
> Thanks,
>
> Best regards,
> Etsuro Fujita
>
>
>> On Mon, Jun 30, 2014 at 12:22 PM, Etsuro Fujita
>> <fujita.etsuro@lab.ntt.co.jp <mailto:fujita.etsuro@lab.ntt.co.jp>> wrote:
>>
>> (2014/06/24 16:30), Etsuro Fujita wrote:
>>
>> (2014/06/23 18:35), Ashutosh Bapat wrote:
>>
>>
>> Selecting tableoid on parent causes an error, "ERROR:
>> cannot extract
>> system attribute from virtual tuple". The foreign table has
>> an OID which
>> can be reported as tableoid for the rows coming from that
>> foreign table.
>> Do we want to do that?
>>
>>
>> No. I think it's a bug. I'll fix it.
>>
>>
>> Done. I think this is because create_foreignscan_plan() makes
>> reference to attr_needed, which isn't computed for inheritance
>> children. To aboid this, I've modified create_foreignscan_plan() to
>> see reltargetlist and baserestrictinfo, instead of attr_needed.
>> Please find attached an updated version of the patch.
>>
>>
>> Sorry for the delay.
>>
>> Best regards,
>> Etsuro Fujita
>>
>>
>>
>>
>> --
>> Best Wishes,
>> Ashutosh Bapat
>> EnterpriseDB Corporation
>> The Postgres Database Company
>>
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-06-30 13:48 Tom Lane <tgl@sss.pgh.pa.us>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 2 replies; 179+ messages in thread
From: Tom Lane @ 2014-06-30 13:48 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp> writes:
> Done. I think this is because create_foreignscan_plan() makes reference
> to attr_needed, which isn't computed for inheritance children.
I wonder whether it isn't time to change that. It was coded like that
originally only because calculating the values would've been a waste of
cycles at the time. But this is at least the third place where it'd be
useful to have attr_needed for child rels.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-07-01 02:09 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
1 sibling, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-07-01 02:09 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
(2014/06/30 20:17), Ashutosh Bapat wrote:
> On Mon, Jun 30, 2014 at 4:17 PM, Etsuro Fujita
> <fujita.etsuro@lab.ntt.co.jp <mailto:fujita.etsuro@lab.ntt.co.jp>> wrote:
> (2014/06/30 17:47), Ashutosh Bapat wrote:
> BTW, why aren't you using the tlist passed to this function? I guess
> create_scan_plan() passes tlist after processing it, so that
> should be
> used rather than rel->reltargetlist.
> I think that that would be maybe OK, but I think that it would not
> be efficient to use the list to compute attrs_used, because the
> tlist would have more information than rel->reltargetlist in cases
> where the tlist is build through build_physical_tlist().
> In that case, we can call build_relation_tlist() for foreign tables.
Do you mean build_physical_tlist()?
Yeah, we can call build_physical_tlist() (and do that in some cases),
but if we call the function, it would generate a tlist that contains all
Vars in the relation, not only those Vars actually needed by the query
(ie, Vars in reltargetlist), and thus it would take more cycles to
compute attr_used from the tlist than from reltargetlist. That' what I
wanted to say.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-07-01 02:10 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-07-01 02:10 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
(2014/06/30 22:48), Tom Lane wrote:
> Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp> writes:
>> Done. I think this is because create_foreignscan_plan() makes reference
>> to attr_needed, which isn't computed for inheritance children.
>
> I wonder whether it isn't time to change that. It was coded like that
> originally only because calculating the values would've been a waste of
> cycles at the time. But this is at least the third place where it'd be
> useful to have attr_needed for child rels.
+1 for calculating attr_needed for child rels. (I was wondering too.)
I'll create a separate patch for it.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-07-01 02:36 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
1 sibling, 1 reply; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2014-07-01 02:36 UTC (permalink / raw)
To: ashutosh.bapat@enterprisedb.com; +Cc: fujita.etsuro@lab.ntt.co.jp; shigeru.hanada@gmail.com; pgsql-hackers
Hello,
> > BTW, why aren't you using the tlist passed to this function? I guess
> >> create_scan_plan() passes tlist after processing it, so that should be
> >> used rather than rel->reltargetlist.
> >>
> >
> > I think that that would be maybe OK, but I think that it would not be
> > efficient to use the list to compute attrs_used, because the tlist would
> > have more information than rel->reltargetlist in cases where the tlist is
> > build through build_physical_tlist().
> >
> >
> In that case, we can call build_relation_tlist() for foreign tables.
# is it build_base_rel_tilst() ?
It needs only one effective line added, which would be seemingly
far simple ingoring potential extra calculation for non-child
relations (I suppose getting rid of it needs a foreach on
rel->append_rel_list..) and too-longer physical tlist. On the
other hand build_relation_tlist() and this patch seem to be in
the same order of computational complexity for the length of the
tlist.
I prefer to use build_base_rel_tlist() for the clarity of
code. But I don't know how high the chance to big physical tlist
and non-child relations become to be an annoyance. Is there any
suggestions?
By the way, I tried xmin and xmax for the file_fdw tables.
postgres=# select tableoid, xmin, xmax, * from passwd1;
tableoid | xmin | xmax | uname | pass | uid | gid | ..
16396 | 244 | 4294967295 | root | x | 0 | 0 | root...
16396 | 252 | 4294967295 | bin | x | 1 | 1 | bin...
16396 | 284 | 4294967295 | daemon | x | 2 | 2 | daemon...
The xmin and xmax apparently doesn't look sane. After some
investigation, I found that they came from the following place in
heap_form_tuple(), (call stach is show below)
| HeapTupleHeaderSetDatumLength(td, len);
| HeapTupleHeaderSetTypeId(td, tupleDescriptor->tdtypeid);
| HeapTupleHeaderSetTypMod(td, tupleDescriptor->tdtypmod);
HeapTupleHeader is a union of HeapTupleFields and
DatumTupleFields and these macors seem to treat it as the
latter. Then later this tuple seems to be read as the former so
xmin and xmax should have that values.
This seems to be a bug. But I have no idea how to deal with it
for now. I'll take more look into this.
The call stack onto the above heap_form_tuple is as follows.
#0 heap_form_tuple (tupleDescriptor=0x7fc46d2953a8, values=0x10dd1a0, isnull=0x10dd1f8 "") at heaptuple.c:731
#1 ExecCopySlotTuple (slot=0x10dd0e0) at execTuples.c:574
#2 ExecMaterializeSlot (slot=0x10dd0e0) at execTuples.c:760
#3 ForeignNext (node=0x10dc7b0) at nodeForeignscan.c:61
#4 ExecScanFetch (node=0x10dc7b0, accessMtd=0x66599c <ForeignNext>, recheckMtd=0x665a43 <ForeignRecheck>) at execScan.c:82
#5 ExecScan (node=0x10dc7b0, accessMtd=0x66599c <ForeignNext>, recheckMtd=0x665a43 <ForeignRecheck>) at execScan.c:167
#6 ExecForeignScan (node=0x10dc7b0) at nodeForeignscan.c:91
#7 ExecProcNode (node=0x10dc7b0) at execProcnode.c:442
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-07-01 06:04 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 0 replies; 179+ messages in thread
From: Ashutosh Bapat @ 2014-07-01 06:04 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
If we are going to change that portion of the code, we may as well go a bit
forward and allow any expressions to be fetched from a foreign server
(obviously, if that server is capable of doing so). It will help, when we
come to aggregate push-down or whole query push-down (whenever that
happens). So, instead of attr_needed, which restricts only the attributes
to be fetched, why not to targetlist itself?
On Mon, Jun 30, 2014 at 7:18 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp> writes:
> > Done. I think this is because create_foreignscan_plan() makes reference
> > to attr_needed, which isn't computed for inheritance children.
>
> I wonder whether it isn't time to change that. It was coded like that
> originally only because calculating the values would've been a waste of
> cycles at the time. But this is at least the third place where it'd be
> useful to have attr_needed for child rels.
>
> regards, tom lane
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-07-01 06:13 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Ashutosh Bapat @ 2014-07-01 06:13 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On Tue, Jul 1, 2014 at 7:39 AM, Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
wrote:
> (2014/06/30 20:17), Ashutosh Bapat wrote:
>
>> On Mon, Jun 30, 2014 at 4:17 PM, Etsuro Fujita
>> <fujita.etsuro@lab.ntt.co.jp <mailto:fujita.etsuro@lab.ntt.co.jp>> wrote:
>>
>
> (2014/06/30 17:47), Ashutosh Bapat wrote:
>>
>
> BTW, why aren't you using the tlist passed to this function? I
>> guess
>> create_scan_plan() passes tlist after processing it, so that
>> should be
>> used rather than rel->reltargetlist.
>>
>
> I think that that would be maybe OK, but I think that it would not
>> be efficient to use the list to compute attrs_used, because the
>> tlist would have more information than rel->reltargetlist in cases
>> where the tlist is build through build_physical_tlist().
>>
>
> In that case, we can call build_relation_tlist() for foreign tables.
>>
>
> Do you mean build_physical_tlist()?
>
>
Sorry, I meant build_path_tlist() or disuse_physical_tlist() whichever is
appropriate.
We may want to modify use_physical_tlist(), to return false, in case of
foreign tables. BTW, it does return false for inheritance trees.
486 /*
487 * Can't do it with inheritance cases either (mainly because Append
488 * doesn't project).
489 */
490 if (rel->reloptkind != RELOPT_BASEREL)
491 return false;
Yeah, we can call build_physical_tlist() (and do that in some cases), but
> if we call the function, it would generate a tlist that contains all Vars
> in the relation, not only those Vars actually needed by the query (ie, Vars
> in reltargetlist), and thus it would take more cycles to compute attr_used
> from the tlist than from reltargetlist. That' what I wanted to say.
>
>
> Thanks,
>
> Best regards,
> Etsuro Fujita
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-07-01 06:27 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
0 siblings, 0 replies; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2014-07-01 06:27 UTC (permalink / raw)
To: ashutosh.bapat@enterprisedb.com; +Cc: fujita.etsuro@lab.ntt.co.jp; shigeru.hanada@gmail.com; pgsql-hackers
Hello,
Sorry, this was no relation with this patch.
ForeignNext materializes the slot, which would be any of physical
and virtual tuple, when system column was requested. If it was a
virtual one, file_fdw makes this, heap_form_tuple generates the
tuple as DatumTuple. The result is a jumble of virtual and
physical. But the returning slot has tts_tuple so the caller
interprets it as a physical one.
Anyway the requests for xmin/xmax could not be prevented
beforehand in current framework, I did rewrite the physical tuple
header so as to really be a physical one.
This would be another patch, so I will put this into next CF if
this don't get any immediate objection.
> By the way, I tried xmin and xmax for the file_fdw tables.
>
> postgres=# select tableoid, xmin, xmax, * from passwd1;
> tableoid | xmin | xmax | uname | pass | uid | gid | ..
> 16396 | 244 | 4294967295 | root | x | 0 | 0 | root...
> 16396 | 252 | 4294967295 | bin | x | 1 | 1 | bin...
> 16396 | 284 | 4294967295 | daemon | x | 2 | 2 | daemon...
>
> The xmin and xmax apparently doesn't look sane. After some
> investigation, I found that they came from the following place in
> heap_form_tuple(), (call stach is show below)
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-patch] nodeForeignScan_tuphead_v1.patch (1.1K, ../../20140701.152712.153613883.horiguchi.kyotaro@lab.ntt.co.jp/2-nodeForeignScan_tuphead_v1.patch)
download | inline diff:
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 9cc5345..728db14 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -22,6 +22,8 @@
*/
#include "postgres.h"
+#include "access/transam.h"
+#include "access/htup_details.h"
#include "executor/executor.h"
#include "executor/nodeForeignscan.h"
#include "foreign/fdwapi.h"
@@ -58,8 +60,21 @@ ForeignNext(ForeignScanState *node)
*/
if (plan->fsSystemCol && !TupIsNull(slot))
{
+ bool was_virtual_tuple = (slot->tts_tuple == NULL);
HeapTuple tup = ExecMaterializeSlot(slot);
+ if (was_virtual_tuple)
+ {
+ /*
+ * ExecMaterializeSlot fills the tuple header as a
+ * DatumTupleFields if the slot was a virtual tuple, but a
+ * physical one is needed here. So rewrite the tuple header as
+ * HeapTupleFirelds.
+ */
+ HeapTupleHeaderSetXmin(tup->t_data, FrozenTransactionId);
+ HeapTupleHeaderSetXmax(tup->t_data, InvalidTransactionId);
+ HeapTupleHeaderSetCmin(tup->t_data, FirstCommandId);
+ }
tup->t_tableOid = RelationGetRelid(node->ss.ss_currentRelation);
}
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-07-01 06:55 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-07-01 06:55 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
(2014/07/01 15:13), Ashutosh Bapat wrote:
> On Tue, Jul 1, 2014 at 7:39 AM, Etsuro Fujita
> <fujita.etsuro@lab.ntt.co.jp <mailto:fujita.etsuro@lab.ntt.co.jp>> wrote:
> We may want to modify use_physical_tlist(), to return false, in case of
> foreign tables. BTW, it does return false for inheritance trees.
Yeah, but please consider cases where foreign tables are not inheritance
child rels (and any system columns are requested).
> 486 /*
> 487 * Can't do it with inheritance cases either (mainly because
> Append
> 488 * doesn't project).
> 489 */
> 490 if (rel->reloptkind != RELOPT_BASEREL)
> 491 return false;
>
> Yeah, we can call build_physical_tlist() (and do that in some
> cases), but if we call the function, it would generate a tlist that
> contains all Vars in the relation, not only those Vars actually
> needed by the query (ie, Vars in reltargetlist), and thus it would
> take more cycles to compute attr_used from the tlist than from
> reltargetlist. That' what I wanted to say.
Maybe I'm missing something, but what's the point of using the tlist,
not reltargetlist?
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-07-01 07:04 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Ashutosh Bapat @ 2014-07-01 07:04 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On Tue, Jul 1, 2014 at 12:25 PM, Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
wrote:
> (2014/07/01 15:13), Ashutosh Bapat wrote:
>
>> On Tue, Jul 1, 2014 at 7:39 AM, Etsuro Fujita
>> <fujita.etsuro@lab.ntt.co.jp <mailto:fujita.etsuro@lab.ntt.co.jp>> wrote:
>>
>
> We may want to modify use_physical_tlist(), to return false, in case of
>> foreign tables. BTW, it does return false for inheritance trees.
>>
>
> Yeah, but please consider cases where foreign tables are not inheritance
> child rels (and any system columns are requested).
>
>
> 486 /*
>> 487 * Can't do it with inheritance cases either (mainly because
>> Append
>> 488 * doesn't project).
>> 489 */
>> 490 if (rel->reloptkind != RELOPT_BASEREL)
>> 491 return false;
>>
>> Yeah, we can call build_physical_tlist() (and do that in some
>> cases), but if we call the function, it would generate a tlist that
>> contains all Vars in the relation, not only those Vars actually
>> needed by the query (ie, Vars in reltargetlist), and thus it would
>> take more cycles to compute attr_used from the tlist than from
>> reltargetlist. That' what I wanted to say.
>>
>
> Maybe I'm missing something, but what's the point of using the tlist, not
> reltargetlist?
>
>
Compliance with other create_*scan_plan() functions. The tlist passed to
those functions is sometimes preprocessed in create_scan_plan() and some of
the function it calls. If we use reltargetlist directly, we loose that
preprocessing. I have not see any of create_*scan_plan() fetch the
targetlist directly from RelOptInfo. It is always the one supplied by
build_path_tlist() or disuse_physical_tlist() (which in turn calls
build_path_tlist()) or build_physical_tlist().
>
> Thanks,
>
> Best regards,
> Etsuro Fujita
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-07-01 07:30 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-07-01 07:30 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
(2014/07/01 16:04), Ashutosh Bapat wrote:
> On Tue, Jul 1, 2014 at 12:25 PM, Etsuro Fujita
> <fujita.etsuro@lab.ntt.co.jp <mailto:fujita.etsuro@lab.ntt.co.jp>> wrote:
> Maybe I'm missing something, but what's the point of using the
> tlist, not reltargetlist?
> Compliance with other create_*scan_plan() functions. The tlist passed to
> those functions is sometimes preprocessed in create_scan_plan() and some
> of the function it calls. If we use reltargetlist directly, we loose
> that preprocessing. I have not see any of create_*scan_plan() fetch the
> targetlist directly from RelOptInfo. It is always the one supplied by
> build_path_tlist() or disuse_physical_tlist() (which in turn calls
> build_path_tlist()) or build_physical_tlist().
I've got the point.
As I said upthread, I'll work on calculating attr_needed for child rels,
and I hope that that will eliminate your concern.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-07-01 08:08 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 0 replies; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2014-07-01 08:08 UTC (permalink / raw)
To: fujita.etsuro@lab.ntt.co.jp; +Cc: ashutosh.bapat@enterprisedb.com; shigeru.hanada@gmail.com; pgsql-hackers
Hi,
At Tue, 01 Jul 2014 16:30:41 +0900, Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp> wrote in <53B263A1.3060107@lab.ntt.co.jp>
> I've got the point.
>
> As I said upthread, I'll work on calculating attr_needed for child
> rels, and I hope that that will eliminate your concern.
Inheritance tree is expanded far after where attr_needed for the
parent built, in set_base_rel_sizes() in make_one_rel(). I'm
afraid that building all attr_needed for whole inheritance tree
altogether is a bit suffering.
I have wanted the point of inheritance expansion earlier for
another patch. Do you think that rearranging there? Or generate
them individually in crete_foreign_plan()?
Anyway, I'm lookin forward to your next patch. So no answer
needed.
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-07-02 02:23 Noah Misch <noah@leadboat.com>
parent: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
1 sibling, 2 replies; 179+ messages in thread
From: Noah Misch @ 2014-07-02 02:23 UTC (permalink / raw)
To: shigeru.hanada@gmail.com, fujita.etsuro@lab.ntt.co.jp, Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: pgsql-hackers
On Fri, Jun 20, 2014 at 05:04:06PM +0900, Kyotaro HORIGUCHI wrote:
> Attached is the rebased patch of v11 up to the current master.
I've been studying this patch.
SELECT FOR UPDATE on the inheritance parent fails with a can't-happen error
condition, even when SELECT FOR UPDATE on the child foreign table alone would
have succeeded.
The patch adds zero tests. Add principal tests to postgres_fdw.sql. Also,
create a child foreign table in foreign_data.sql; this will make dump/reload
tests of the regression database exercise an inheritance tree that includes a
foreign table.
The inheritance section of ddl.sgml should mention child foreign tables, at
least briefly. Consider mentioning it in the partitioning section, too.
Your chosen ANALYZE behavior is fair, but the messaging from a database-wide
ANALYZE VERBOSE needs work:
INFO: analyzing "test_foreign_inherit.parent"
INFO: "parent": scanned 1 of 1 pages, containing 1 live rows and 0 dead rows; 1 rows in sample, 1 estimated total rows
INFO: analyzing "test_foreign_inherit.parent" inheritance tree
WARNING: relcache reference leak: relation "child" not closed
WARNING: relcache reference leak: relation "tchild" not closed
WARNING: relcache reference leak: relation "parent" not closed
Please arrange to omit the 'analyzing "tablename" inheritance tree' message,
since this ANALYZE actually skipped that task. (The warnings obviously need a
fix, too.) I do find it awkward that adding a foreign table to an inheritance
tree will make autovacuum stop collecting statistics on that inheritance tree,
but I can't think of a better policy.
The rest of these review comments are strictly observations; they're not
requests for you to change the patch, but they may deserve more discussion.
We use the term "child table" in many messages. Should that change to
something more inclusive, now that the child may be a foreign table? Perhaps
one of "child relation", plain "child", or "child foreign table"/"child table"
depending on the actual object? "child relation" is robust technically, but
it might add more confusion than it removes. Varying the message depending on
the actual object feels like a waste. Opinions?
LOCK TABLE on the inheritance parent locks child foreign tables, but LOCK
TABLE fails when given a foreign table directly. That's odd, but I see no
cause to change it.
A partition root only accepts an UPDATE command if every child is updatable.
Similarly, "UPDATE ... WHERE CURRENT OF cursor_name" fails if any child does
not support it. That seems fine. Incidentally, does anyone have a FDW that
supports WHERE CURRENT OF?
The longstanding behavior of CREATE TABLE INHERITS is to reorder local columns
to match the order found in parents. That is, both of these tables actually
have columns in the order (a,b):
create table parent (a int, b int);
create table child (b int, a int) inherits (parent);
Ordinary dump/reload always uses CREATE TABLE INHERITS, thereby changing
column order in this way. (pg_dump --binary-upgrade uses ALTER TABLE INHERIT
and some catalog hacks to avoid doing so.) I've never liked that dump/reload
can change column order, but it's tolerable if you follow the best practice of
always writing out column lists. The stakes rise for foreign tables, because
column order is inherently significant to file_fdw and probably to certain
other non-RDBMS FDWs. If pg_dump changes column order in your file_fdw
foreign table, the table breaks. I would heartily support making pg_dump
preserve column order for all inheritance children. That doesn't rise to the
level of being a blocker for this patch, though.
I am attaching rough-hewn tests I used during this review.
Thanks,
nm
--
Noah Misch
EnterpriseDB http://www.enterprisedb.com
drop schema test_foreign_inherit cascade;
create schema test_foreign_inherit;
set search_path = test_foreign_inherit;
create extension postgres_fdw;
CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (dbname 'test');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
create extension file_fdw;
CREATE SERVER f FOREIGN DATA WRAPPER file_fdw;
create table parent (
a int,
b text,
c date,
d path
);
insert into parent values (4, 'foo', current_date, '0,0,1,2');
-- regular child
create table tchild () inherits (parent);
insert into tchild values (5, 'oof', current_date - 2, '-1,-1,0,0');
-- cursor update works
begin;
declare c cursor for select 1 from parent where b = 'oof';
fetch from c;
explain analyze update parent set b = null where current of c;
select * from parent;
rollback;
-- "foreign" side
create table _child (
c date,
b text,
extra numeric,
d path,
a int
);
-- Foreign child. No column reorder.
create foreign table child (
c date,
b text,
extra numeric,
d path,
a int
) SERVER LOOPBACK OPTIONS (table_name '_child');
alter table child inherit parent;
insert into child (a, b, c, d, extra)
values (7, 'bar', current_date - 1, '5,5,9,9', 1.1);
\copy (select * from child) to /tmp/child.pgcopy
-- update works
select * from parent;
update parent set b = 'baz' where b = 'bar';
-- cursor update fails, because postgres_fdw doesn't handle WHERE CURRENT OF
begin;
declare c cursor for select 1 from child where b = 'baz';
fetch from c;
update child set b = 'bak' where current of c;
rollback;
-- cursor update fails, even though target isn't in the foreign table
begin;
declare c cursor for select 1 from parent where b = 'oof';
fetch from c;
update parent set b = null where current of c;
rollback;
-- Add a file_fdw child. No reorder.
create foreign table fchild0 (
c date,
b text,
extra numeric,
d path,
a int
) SERVER f OPTIONS (filename '/tmp/child.pgcopy');
alter table fchild0 inherit parent;
-- no more update, since file_fdw doesn't cooperate
select * from parent;
update parent set b = 'baz' where b = 'foo';
select * from child for update of child;
select * from fchild0 for update of fchild0;
select * from parent for update;
-- No LOCK TABLE for foreign tables, ...
begin; lock table child in exclusive mode; rollback;
-- ... but locking indirectly via a parent works.
begin;
select relation::regclass, mode from pg_locks
where mode = 'ExclusiveLock' and relation is not null order by 1;
lock table parent in exclusive mode;
select relation::regclass, mode from pg_locks
where mode = 'ExclusiveLock' and relation is not null order by 1;
rollback;
alter server loopback options (use_remote_estimate 'true');
select * from parent;
analyze verbose;
analyze verbose parent;
-- message correctly observes that foreign table is now allowed
create view v as select 1;
alter table v inherit parent;
-- for testing dump/reload of column-level options
create foreign table nonlocal () inherits (parent)
server loopback options (table_name '_child');
alter foreign table child alter c options (column_name 'c');
alter foreign table nonlocal alter c options (column_name 'c');
-- column reorder breaks the foreign table
create foreign table fchild1 (
c date,
b text,
extra numeric,
d path,
a int
) inherits (parent) SERVER f OPTIONS (filename '/tmp/child.pgcopy');
select * from fchild1;
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/plain] inherit-foreign.sql (3.3K, ../../20140702022327.GC1586927@tornado.leadboat.com/2-inherit-foreign.sql)
download | inline:
drop schema test_foreign_inherit cascade;
create schema test_foreign_inherit;
set search_path = test_foreign_inherit;
create extension postgres_fdw;
CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (dbname 'test');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
create extension file_fdw;
CREATE SERVER f FOREIGN DATA WRAPPER file_fdw;
create table parent (
a int,
b text,
c date,
d path
);
insert into parent values (4, 'foo', current_date, '0,0,1,2');
-- regular child
create table tchild () inherits (parent);
insert into tchild values (5, 'oof', current_date - 2, '-1,-1,0,0');
-- cursor update works
begin;
declare c cursor for select 1 from parent where b = 'oof';
fetch from c;
explain analyze update parent set b = null where current of c;
select * from parent;
rollback;
-- "foreign" side
create table _child (
c date,
b text,
extra numeric,
d path,
a int
);
-- Foreign child. No column reorder.
create foreign table child (
c date,
b text,
extra numeric,
d path,
a int
) SERVER LOOPBACK OPTIONS (table_name '_child');
alter table child inherit parent;
insert into child (a, b, c, d, extra)
values (7, 'bar', current_date - 1, '5,5,9,9', 1.1);
\copy (select * from child) to /tmp/child.pgcopy
-- update works
select * from parent;
update parent set b = 'baz' where b = 'bar';
-- cursor update fails, because postgres_fdw doesn't handle WHERE CURRENT OF
begin;
declare c cursor for select 1 from child where b = 'baz';
fetch from c;
update child set b = 'bak' where current of c;
rollback;
-- cursor update fails, even though target isn't in the foreign table
begin;
declare c cursor for select 1 from parent where b = 'oof';
fetch from c;
update parent set b = null where current of c;
rollback;
-- Add a file_fdw child. No reorder.
create foreign table fchild0 (
c date,
b text,
extra numeric,
d path,
a int
) SERVER f OPTIONS (filename '/tmp/child.pgcopy');
alter table fchild0 inherit parent;
-- no more update, since file_fdw doesn't cooperate
select * from parent;
update parent set b = 'baz' where b = 'foo';
select * from child for update of child;
select * from fchild0 for update of fchild0;
select * from parent for update;
-- No LOCK TABLE for foreign tables, ...
begin; lock table child in exclusive mode; rollback;
-- ... but locking indirectly via a parent works.
begin;
select relation::regclass, mode from pg_locks
where mode = 'ExclusiveLock' and relation is not null order by 1;
lock table parent in exclusive mode;
select relation::regclass, mode from pg_locks
where mode = 'ExclusiveLock' and relation is not null order by 1;
rollback;
alter server loopback options (use_remote_estimate 'true');
select * from parent;
analyze verbose;
analyze verbose parent;
-- message correctly observes that foreign table is now allowed
create view v as select 1;
alter table v inherit parent;
-- for testing dump/reload of column-level options
create foreign table nonlocal () inherits (parent)
server loopback options (table_name '_child');
alter foreign table child alter c options (column_name 'c');
alter foreign table nonlocal alter c options (column_name 'c');
-- column reorder breaks the foreign table
create foreign table fchild1 (
c date,
b text,
extra numeric,
d path,
a int
) inherits (parent) SERVER f OPTIONS (filename '/tmp/child.pgcopy');
select * from fchild1;
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-07-10 09:12 Shigeru Hanada <shigeru.hanada@gmail.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: Shigeru Hanada @ 2014-07-10 09:12 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; pgsql-hackers
Hi Fujita-san,
Sorry for leaving this thread for long time.
2014-06-24 16:30 GMT+09:00 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>:
> (2014/06/23 18:35), Ashutosh Bapat wrote:
>>
>> Hi,
>> Selecting tableoid on parent causes an error, "ERROR: cannot extract
>> system attribute from virtual tuple". The foreign table has an OID which
>> can be reported as tableoid for the rows coming from that foreign table.
>> Do we want to do that?
>
>
> No. I think it's a bug. I'll fix it.
IIUC, you mean that tableoid can't be retrieved when a foreign table
is accessed via parent table, but it sounds strange to me, because one
of main purposes of tableoid is determine actual source table in
appended results.
Am I missing the point?
--
Shigeru HANADA
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-07-11 06:50 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Shigeru Hanada <shigeru.hanada@gmail.com>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-07-11 06:50 UTC (permalink / raw)
To: Shigeru Hanada <shigeru.hanada@gmail.com>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; pgsql-hackers
(2014/07/10 18:12), Shigeru Hanada wrote:
> 2014-06-24 16:30 GMT+09:00 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>:
>> (2014/06/23 18:35), Ashutosh Bapat wrote:
>>> Selecting tableoid on parent causes an error, "ERROR: cannot extract
>>> system attribute from virtual tuple". The foreign table has an OID which
>>> can be reported as tableoid for the rows coming from that foreign table.
>>> Do we want to do that?
>> No. I think it's a bug. I'll fix it.
> IIUC, you mean that tableoid can't be retrieved when a foreign table
> is accessed via parent table,
No. What I want to say is that tableoid *can* be retrieved when a
foreign table is accessed via the parent table.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-07-11 07:18 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 0 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-07-11 07:18 UTC (permalink / raw)
To: Shigeru Hanada <shigeru.hanada@gmail.com>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; pgsql-hackers
(2014/07/11 15:50), Etsuro Fujita wrote:
> (2014/07/10 18:12), Shigeru Hanada wrote:
>> IIUC, you mean that tableoid can't be retrieved when a foreign table
>> is accessed via parent table,
>
> No. What I want to say is that tableoid *can* be retrieved when a
> foreign table is accessed via the parent table.
In fact, you can do that with v13 [1], but I plan to change the way of
fixing (see [2]).
Thanks,
[1] http://www.postgresql.org/message-id/53B10914.2010504@lab.ntt.co.jp
[2] http://www.postgresql.org/message-id/53B2188B.4090302@lab.ntt.co.jp
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-08-06 11:43 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-08-06 11:43 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
(2014/07/01 11:10), Etsuro Fujita wrote:
> (2014/06/30 22:48), Tom Lane wrote:
>> Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp> writes:
>>> Done. I think this is because create_foreignscan_plan() makes reference
>>> to attr_needed, which isn't computed for inheritance children.
>>
>> I wonder whether it isn't time to change that. It was coded like that
>> originally only because calculating the values would've been a waste of
>> cycles at the time. But this is at least the third place where it'd be
>> useful to have attr_needed for child rels.
>
> +1 for calculating attr_needed for child rels. (I was wondering too.)
>
> I'll create a separate patch for it.
Attached is a WIP patch for that. The following functions have been
changed to refer to attr_needed:
* check_index_only()
* remove_unused_subquery_outputs()
* check_selective_binary_conversion()
I'll add this to the upcoming commitfest. If anyone has any time to
glance at it before then, that would be a great help.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] attr_needed-v1.patch (9.2K, ../../53E214C8.3070704@lab.ntt.co.jp/2-attr_needed-v1.patch)
download | inline diff:
*** a/contrib/file_fdw/file_fdw.c
--- b/contrib/file_fdw/file_fdw.c
***************
*** 799,806 **** check_selective_binary_conversion(RelOptInfo *baserel,
}
/* Collect all the attributes needed for joins or final output. */
! pull_varattnos((Node *) baserel->reltargetlist, baserel->relid,
! &attrs_used);
/* Add all the attributes used by restriction clauses. */
foreach(lc, baserel->baserestrictinfo)
--- 799,811 ----
}
/* Collect all the attributes needed for joins or final output. */
! for (i = baserel->min_attr; i <= baserel->max_attr; i++)
! {
! if (!bms_is_empty(baserel->attr_needed[i - baserel->min_attr]))
! attrs_used =
! bms_add_member(attrs_used,
! i - FirstLowInvalidHeapAttributeNumber);
! }
/* Add all the attributes used by restriction clauses. */
foreach(lc, baserel->baserestrictinfo)
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
***************
*** 577,588 **** set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
childrel->has_eclass_joins = rel->has_eclass_joins;
/*
! * Note: we could compute appropriate attr_needed data for the child's
! * variables, by transforming the parent's attr_needed through the
! * translated_vars mapping. However, currently there's no need
! * because attr_needed is only examined for base relations not
! * otherrels. So we just leave the child's attr_needed empty.
*/
/*
* Compute the child's size.
--- 577,585 ----
childrel->has_eclass_joins = rel->has_eclass_joins;
/*
! * Compute the child's attr_needed.
*/
+ adjust_appendrel_attr_needed(rel, childrel, childRTE, appinfo);
/*
* Compute the child's size.
***************
*** 2173,2178 **** remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel)
--- 2170,2176 ----
{
Bitmapset *attrs_used = NULL;
ListCell *lc;
+ int i;
/*
* Do nothing if subquery has UNION/INTERSECT/EXCEPT: in principle we
***************
*** 2193,2204 **** remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel)
* Collect a bitmap of all the output column numbers used by the upper
* query.
*
! * Add all the attributes needed for joins or final output. Note: we must
! * look at reltargetlist, not the attr_needed data, because attr_needed
! * isn't computed for inheritance child rels, cf set_append_rel_size().
! * (XXX might be worth changing that sometime.)
*/
! pull_varattnos((Node *) rel->reltargetlist, rel->relid, &attrs_used);
/* Add all the attributes used by un-pushed-down restriction clauses. */
foreach(lc, rel->baserestrictinfo)
--- 2191,2205 ----
* Collect a bitmap of all the output column numbers used by the upper
* query.
*
! * Add all the attributes needed for joins or final output.
*/
! for (i = rel->min_attr; i <= rel->max_attr; i++)
! {
! if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
! attrs_used =
! bms_add_member(attrs_used,
! i - FirstLowInvalidHeapAttributeNumber);
! }
/* Add all the attributes used by un-pushed-down restriction clauses. */
foreach(lc, rel->baserestrictinfo)
*** a/src/backend/optimizer/path/indxpath.c
--- b/src/backend/optimizer/path/indxpath.c
***************
*** 1768,1779 **** check_index_only(RelOptInfo *rel, IndexOptInfo *index)
* way out.
*/
! /*
! * Add all the attributes needed for joins or final output. Note: we must
! * look at reltargetlist, not the attr_needed data, because attr_needed
! * isn't computed for inheritance child rels.
! */
! pull_varattnos((Node *) rel->reltargetlist, rel->relid, &attrs_used);
/* Add all the attributes used by restriction clauses. */
foreach(lc, rel->baserestrictinfo)
--- 1768,1781 ----
* way out.
*/
! /* Collect all the attributes needed for joins or final output. */
! for (i = rel->min_attr; i <= rel->max_attr; i++)
! {
! if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
! attrs_used =
! bms_add_member(attrs_used,
! i - FirstLowInvalidHeapAttributeNumber);
! }
/* Add all the attributes used by restriction clauses. */
foreach(lc, rel->baserestrictinfo)
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 109,114 **** static Bitmapset *translate_col_privs(const Bitmapset *parent_privs,
--- 109,122 ----
List *translated_vars);
static Node *adjust_appendrel_attrs_mutator(Node *node,
adjust_appendrel_attrs_context *context);
+ static void adjust_appendrel_attr_needed_inh(RelOptInfo *oldrel,
+ RelOptInfo *newrel,
+ RangeTblEntry *newrte,
+ AppendRelInfo *appinfo);
+ static void adjust_appendrel_attr_needed_setop(RelOptInfo *oldrel,
+ RelOptInfo *newrel,
+ RangeTblEntry *newrte,
+ AppendRelInfo *appinfo);
static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
static List *adjust_inherited_tlist(List *tlist,
AppendRelInfo *context);
***************
*** 1867,1872 **** adjust_appendrel_attrs_mutator(Node *node,
--- 1875,2002 ----
}
/*
+ * adjust_appendrel_attr_needed
+ * Compute the child's attr_needed by transforming the parent's attr_needed
+ * through the translated_vars mapping of the specified AppendRelInfo.
+ */
+ void
+ adjust_appendrel_attr_needed(RelOptInfo *oldrel,
+ RelOptInfo *newrel,
+ RangeTblEntry *newrte,
+ AppendRelInfo *appinfo)
+ {
+ if (OidIsValid(appinfo->parent_reloid))
+ adjust_appendrel_attr_needed_inh(oldrel, newrel, newrte, appinfo);
+ else
+ adjust_appendrel_attr_needed_setop(oldrel, newrel, newrte, appinfo);
+ }
+
+ static void
+ adjust_appendrel_attr_needed_inh(RelOptInfo *oldrel,
+ RelOptInfo *newrel,
+ RangeTblEntry *newrte,
+ AppendRelInfo *appinfo)
+ {
+ int i;
+ ListCell *lc;
+
+ Assert(newrte->rtekind == RTE_RELATION);
+
+ /*
+ * System attributes and whole-row Vars have the same numbers in all tables.
+ */
+ for (i = newrel->min_attr; i <= InvalidAttrNumber; i++)
+ newrel->attr_needed[i - newrel->min_attr] =
+ adjust_relid_set(oldrel->attr_needed[i - newrel->min_attr],
+ appinfo->parent_relid, appinfo->child_relid);
+
+ /*
+ * Compute user attributes' attr_needed through the translated_vars mapping.
+ */
+ i = InvalidAttrNumber;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Var *var = (Var *) lfirst(lc);
+ int oldattoff;
+
+ i++;
+ if (var == NULL) /* ignore dropped columns */
+ continue;
+ Assert(IsA(var, Var));
+
+ if (!bms_is_empty(oldrel->attr_needed[i - newrel->min_attr]))
+ newrel->attr_needed[var->varattno - oldrel->min_attr] =
+ adjust_relid_set(oldrel->attr_needed[i - newrel->min_attr],
+ appinfo->parent_relid, appinfo->child_relid);
+ }
+ }
+
+ static void
+ adjust_appendrel_attr_needed_setop(RelOptInfo *oldrel,
+ RelOptInfo *newrel,
+ RangeTblEntry *newrte,
+ AppendRelInfo *appinfo)
+ {
+ int i;
+ ListCell *lc;
+ bool whole_row;
+
+ if (newrte->rtekind != RTE_RELATION)
+ {
+ for (i = oldrel->min_attr; i <= oldrel->max_attr; i++)
+ newrel->attr_needed[i] = adjust_relid_set(oldrel->attr_needed[i],
+ appinfo->parent_relid,
+ appinfo->child_relid);
+ return;
+ }
+
+ /*
+ * Compute the child's attr_needed by transforming the parent's attr_needed
+ * through the translated_vars mapping of the specified AppendRelInfo.
+ */
+
+ /* Check if parent has whole-row reference */
+ whole_row = !bms_is_empty(oldrel->attr_needed[InvalidAttrNumber]);
+
+ i = InvalidAttrNumber;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *node = (Node *) lfirst(lc);
+ bool result;
+
+ i++;
+ if (whole_row || !bms_is_empty(oldrel->attr_needed[i]))
+ {
+ Bitmapset *attrs_used = NULL;
+ Bitmapset *tmpset = NULL;
+ int j;
+
+ pull_varattnos(node, newrel->relid, &attrs_used);
+ tmpset = bms_copy(attrs_used);
+ while ((j = bms_first_member(tmpset)) >= 0)
+ {
+ if (whole_row)
+ newrel->attr_needed[j - 1] =
+ bms_add_members(newrel->attr_needed[j - 1],
+ oldrel->attr_needed[InvalidAttrNumber]);
+ if (!bms_is_empty(oldrel->attr_needed[i]))
+ newrel->attr_needed[j - 1] =
+ bms_add_members(newrel->attr_needed[j - 1],
+ oldrel->attr_needed[i]);
+ }
+ bms_free(tmpset);
+ bms_free(attrs_used);
+ }
+ }
+
+ /* Adjust attr_needed's relid sets */
+ for (i = newrel->min_attr; i <= newrel->max_attr; i++)
+ newrel->attr_needed[i - newrel->min_attr] =
+ adjust_relid_set(newrel->attr_needed[i - newrel->min_attr],
+ appinfo->parent_relid, appinfo->child_relid);
+ }
+
+ /*
* Substitute newrelid for oldrelid in a Relid set
*/
static Relids
*** a/src/include/optimizer/prep.h
--- b/src/include/optimizer/prep.h
***************
*** 58,61 **** extern void expand_inherited_tables(PlannerInfo *root);
--- 58,66 ----
extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node,
AppendRelInfo *appinfo);
+ extern void adjust_appendrel_attr_needed(RelOptInfo *oldrel,
+ RelOptInfo *newrel,
+ RangeTblEntry *newrte,
+ AppendRelInfo *appinfo);
+
#endif /* PREP_H */
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-08-08 09:51 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 0 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-08-08 09:51 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
(2014/08/06 20:43), Etsuro Fujita wrote:
>> (2014/06/30 22:48), Tom Lane wrote:
>>> I wonder whether it isn't time to change that. It was coded like that
>>> originally only because calculating the values would've been a waste of
>>> cycles at the time. But this is at least the third place where it'd be
>>> useful to have attr_needed for child rels.
> Attached is a WIP patch for that.
I've revised the patch.
Changes:
* Make the code more readable
* Revise the comments
* Cleanup
Please find attached an updated version of the patch.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] attr_needed-v2.patch (10.0K, ../../53E49D84.8010603@lab.ntt.co.jp/2-attr_needed-v2.patch)
download | inline diff:
*** a/contrib/file_fdw/file_fdw.c
--- b/contrib/file_fdw/file_fdw.c
***************
*** 799,806 **** check_selective_binary_conversion(RelOptInfo *baserel,
}
/* Collect all the attributes needed for joins or final output. */
! pull_varattnos((Node *) baserel->reltargetlist, baserel->relid,
! &attrs_used);
/* Add all the attributes used by restriction clauses. */
foreach(lc, baserel->baserestrictinfo)
--- 799,811 ----
}
/* Collect all the attributes needed for joins or final output. */
! for (i = baserel->min_attr; i <= baserel->max_attr; i++)
! {
! if (!bms_is_empty(baserel->attr_needed[i - baserel->min_attr]))
! attrs_used =
! bms_add_member(attrs_used,
! i - FirstLowInvalidHeapAttributeNumber);
! }
/* Add all the attributes used by restriction clauses. */
foreach(lc, baserel->baserestrictinfo)
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
***************
*** 577,588 **** set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
childrel->has_eclass_joins = rel->has_eclass_joins;
/*
! * Note: we could compute appropriate attr_needed data for the child's
! * variables, by transforming the parent's attr_needed through the
! * translated_vars mapping. However, currently there's no need
! * because attr_needed is only examined for base relations not
! * otherrels. So we just leave the child's attr_needed empty.
*/
/*
* Compute the child's size.
--- 577,585 ----
childrel->has_eclass_joins = rel->has_eclass_joins;
/*
! * Compute the child's attr_needed.
*/
+ adjust_appendrel_attr_needed(rel, childrel, childRTE, appinfo);
/*
* Compute the child's size.
***************
*** 2173,2178 **** remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel)
--- 2170,2176 ----
{
Bitmapset *attrs_used = NULL;
ListCell *lc;
+ int i;
/*
* Do nothing if subquery has UNION/INTERSECT/EXCEPT: in principle we
***************
*** 2193,2204 **** remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel)
* Collect a bitmap of all the output column numbers used by the upper
* query.
*
! * Add all the attributes needed for joins or final output. Note: we must
! * look at reltargetlist, not the attr_needed data, because attr_needed
! * isn't computed for inheritance child rels, cf set_append_rel_size().
! * (XXX might be worth changing that sometime.)
*/
! pull_varattnos((Node *) rel->reltargetlist, rel->relid, &attrs_used);
/* Add all the attributes used by un-pushed-down restriction clauses. */
foreach(lc, rel->baserestrictinfo)
--- 2191,2205 ----
* Collect a bitmap of all the output column numbers used by the upper
* query.
*
! * Add all the attributes needed for joins or final output.
*/
! for (i = rel->min_attr; i <= rel->max_attr; i++)
! {
! if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
! attrs_used =
! bms_add_member(attrs_used,
! i - FirstLowInvalidHeapAttributeNumber);
! }
/* Add all the attributes used by un-pushed-down restriction clauses. */
foreach(lc, rel->baserestrictinfo)
*** a/src/backend/optimizer/path/indxpath.c
--- b/src/backend/optimizer/path/indxpath.c
***************
*** 1768,1779 **** check_index_only(RelOptInfo *rel, IndexOptInfo *index)
* way out.
*/
! /*
! * Add all the attributes needed for joins or final output. Note: we must
! * look at reltargetlist, not the attr_needed data, because attr_needed
! * isn't computed for inheritance child rels.
! */
! pull_varattnos((Node *) rel->reltargetlist, rel->relid, &attrs_used);
/* Add all the attributes used by restriction clauses. */
foreach(lc, rel->baserestrictinfo)
--- 1768,1781 ----
* way out.
*/
! /* Add all the attributes needed for joins or final output. */
! for (i = rel->min_attr; i <= rel->max_attr; i++)
! {
! if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
! attrs_used =
! bms_add_member(attrs_used,
! i - FirstLowInvalidHeapAttributeNumber);
! }
/* Add all the attributes used by restriction clauses. */
foreach(lc, rel->baserestrictinfo)
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 109,114 **** static Bitmapset *translate_col_privs(const Bitmapset *parent_privs,
--- 109,122 ----
List *translated_vars);
static Node *adjust_appendrel_attrs_mutator(Node *node,
adjust_appendrel_attrs_context *context);
+ static void adjust_appendrel_attr_needed_inh(RelOptInfo *parent_rel,
+ RelOptInfo *child_rel,
+ RangeTblEntry *child_rte,
+ AppendRelInfo *appinfo);
+ static void adjust_appendrel_attr_needed_setop(RelOptInfo *parent_rel,
+ RelOptInfo *child_rel,
+ RangeTblEntry *child_rte,
+ AppendRelInfo *appinfo);
static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
static List *adjust_inherited_tlist(List *tlist,
AppendRelInfo *context);
***************
*** 1867,1872 **** adjust_appendrel_attrs_mutator(Node *node,
--- 1875,2031 ----
}
/*
+ * adjust_appendrel_attr_needed
+ * Compute the child's attr_needed by transforming the parent's attr_needed
+ * through the translated_vars mapping of the specified AppendRelInfo.
+ */
+ void
+ adjust_appendrel_attr_needed(RelOptInfo *parent_rel,
+ RelOptInfo *child_rel,
+ RangeTblEntry *child_rte,
+ AppendRelInfo *appinfo)
+ {
+ if (OidIsValid(appinfo->parent_reloid))
+ adjust_appendrel_attr_needed_inh(parent_rel, child_rel,
+ child_rte, appinfo);
+ else
+ adjust_appendrel_attr_needed_setop(parent_rel, child_rel,
+ child_rte, appinfo);
+ }
+
+ /*
+ * adjust_appendrel_attr_needed for the inheritance case
+ */
+ static void
+ adjust_appendrel_attr_needed_inh(RelOptInfo *parent_rel,
+ RelOptInfo *child_rel,
+ RangeTblEntry *child_rte,
+ AppendRelInfo *appinfo)
+ {
+ int i;
+ ListCell *lc;
+
+ Assert(child_rte->rtekind == RTE_RELATION);
+ Assert(appinfo->parent_relid == parent_rel->relid);
+ Assert(appinfo->child_relid == child_rel->relid);
+
+ /*
+ * System attributes and whole-row Vars have the same numbers in all tables.
+ */
+ for (i = FirstLowInvalidHeapAttributeNumber + 1; i <= InvalidAttrNumber; i++)
+ {
+ int cndx = i - FirstLowInvalidHeapAttributeNumber - 1;
+
+ child_rel->attr_needed[cndx] =
+ adjust_relid_set(parent_rel->attr_needed[cndx],
+ appinfo->parent_relid, appinfo->child_relid);
+ }
+
+ /*
+ * Compute user attributes' attr_needed through the translated_vars mapping.
+ */
+ i = InvalidAttrNumber;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Var *var = (Var *) lfirst(lc);
+ int pndx;
+ int cndx;
+
+ i++;
+ if (var == NULL) /* ignore dropped columns */
+ continue;
+ Assert(IsA(var, Var));
+
+ pndx = i - FirstLowInvalidHeapAttributeNumber - 1;
+ cndx = var->varattno - FirstLowInvalidHeapAttributeNumber - 1;
+ child_rel->attr_needed[cndx] =
+ adjust_relid_set(parent_rel->attr_needed[pndx],
+ appinfo->parent_relid, appinfo->child_relid);
+ }
+ }
+
+ /*
+ * adjust_appendrel_attr_needed for the UNION ALL case
+ */
+ static void
+ adjust_appendrel_attr_needed_setop(RelOptInfo *parent_rel,
+ RelOptInfo *child_rel,
+ RangeTblEntry *child_rte,
+ AppendRelInfo *appinfo)
+ {
+ int i;
+ ListCell *lc;
+ int pndx;
+ bool whole_row;
+
+ Assert(appinfo->parent_relid == parent_rel->relid);
+ Assert(appinfo->child_relid == child_rel->relid);
+
+ if (child_rte->rtekind != RTE_RELATION)
+ {
+ for (i = child_rel->min_attr; i <= child_rel->max_attr; i++)
+ child_rel->attr_needed[i] =
+ adjust_relid_set(parent_rel->attr_needed[i],
+ appinfo->parent_relid, appinfo->child_relid);
+ return;
+ }
+
+ /*
+ * Compute the child's attr_needed through the translated_vars mapping.
+ *
+ * Note: pull_up_subqueries might have changed the mapping so that their
+ * expansions contain arbitrary expressions. So we must cope with such.
+ */
+
+ /* Check if parent has whole-row reference */
+ whole_row = !bms_is_empty(parent_rel->attr_needed[InvalidAttrNumber]);
+
+ pndx = InvalidAttrNumber;
+ foreach(lc, appinfo->translated_vars)
+ {
+ Node *node = (Node *) lfirst(lc);
+ bool result;
+
+ pndx++;
+ result = !bms_is_empty(parent_rel->attr_needed[pndx]);
+ if (result || whole_row)
+ {
+ Bitmapset *attrs_used = NULL;
+ Bitmapset *tmpset = NULL;
+ int j;
+
+ pull_varattnos(node, child_rel->relid, &attrs_used);
+ tmpset = bms_copy(attrs_used);
+ while ((j = bms_first_member(tmpset)) >= 0)
+ {
+ int cndx = j - 1;
+
+ if (result)
+ child_rel->attr_needed[cndx] =
+ bms_add_members(child_rel->attr_needed[cndx],
+ parent_rel->attr_needed[pndx]);
+ if (whole_row)
+ child_rel->attr_needed[cndx] =
+ bms_add_members(child_rel->attr_needed[cndx],
+ parent_rel->attr_needed[InvalidAttrNumber]);
+ }
+ bms_free(tmpset);
+ bms_free(attrs_used);
+ }
+ }
+
+ /* Fix attr_needed's relid sets */
+ for (i = FirstLowInvalidHeapAttributeNumber + 1; i <= child_rel->max_attr; i++)
+ {
+ int cndx = i - FirstLowInvalidHeapAttributeNumber - 1;
+
+ child_rel->attr_needed[cndx] =
+ adjust_relid_set(child_rel->attr_needed[cndx],
+ appinfo->parent_relid, appinfo->child_relid);
+ }
+ }
+
+ /*
* Substitute newrelid for oldrelid in a Relid set
*/
static Relids
*** a/src/include/optimizer/prep.h
--- b/src/include/optimizer/prep.h
***************
*** 58,61 **** extern void expand_inherited_tables(PlannerInfo *root);
--- 58,66 ----
extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node,
AppendRelInfo *appinfo);
+ extern void adjust_appendrel_attr_needed(RelOptInfo *parent_rel,
+ RelOptInfo *child_rel,
+ RangeTblEntry *child_rte,
+ AppendRelInfo *appinfo);
+
#endif /* PREP_H */
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-08-20 11:11 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Noah Misch <noah@leadboat.com>
1 sibling, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-08-20 11:11 UTC (permalink / raw)
To: Noah Misch <noah@leadboat.com>; shigeru.hanada@gmail.com, Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: pgsql-hackers
Hi Noah,
Thank you for the review!
(2014/07/02 11:23), Noah Misch wrote:
> On Fri, Jun 20, 2014 at 05:04:06PM +0900, Kyotaro HORIGUCHI wrote:
>> Attached is the rebased patch of v11 up to the current master.
>
> I've been studying this patch.
>
> SELECT FOR UPDATE on the inheritance parent fails with a can't-happen error
> condition, even when SELECT FOR UPDATE on the child foreign table alone would
> have succeeded.
To fix this, I've modified the planner and executor so that the planner
adds wholerow as well as ctid and tableoid as resjunk output columns to
the plan for an inheritance tree that contains foreign tables, and that
while the executor uses the ctid and tableoid in the EPQ processing for
child regular tables as before, the executor uses the wholerow and
tableoid for child foreign tables. Patch attached. This is based on
the patch [1].
> The patch adds zero tests. Add principal tests to postgres_fdw.sql. Also,
> create a child foreign table in foreign_data.sql; this will make dump/reload
> tests of the regression database exercise an inheritance tree that includes a
> foreign table.
Done. I used your tests as reference. Thanks!
> The inheritance section of ddl.sgml should mention child foreign tables, at
> least briefly. Consider mentioning it in the partitioning section, too.
Done.
> Your chosen ANALYZE behavior is fair, but the messaging from a database-wide
> ANALYZE VERBOSE needs work:
>
> INFO: analyzing "test_foreign_inherit.parent"
> INFO: "parent": scanned 1 of 1 pages, containing 1 live rows and 0 dead rows; 1 rows in sample, 1 estimated total rows
> INFO: analyzing "test_foreign_inherit.parent" inheritance tree
> WARNING: relcache reference leak: relation "child" not closed
> WARNING: relcache reference leak: relation "tchild" not closed
> WARNING: relcache reference leak: relation "parent" not closed
>
> Please arrange to omit the 'analyzing "tablename" inheritance tree' message,
> since this ANALYZE actually skipped that task. (The warnings obviously need a
> fix, too.) I do find it awkward that adding a foreign table to an inheritance
> tree will make autovacuum stop collecting statistics on that inheritance tree,
> but I can't think of a better policy.
I think it would be better that this is handled in the same way as an
inheritance tree that turns out to be a singe table that doesn't have
any descendants in acquire_inherited_sample_rows(). That would still
output the message as shown below, but I think that that would be more
consistent with the existing code. The patch works so. (The warnings
are also fixed.)
INFO: analyzing "public.parent"
INFO: "parent": scanned 0 of 0 pages, containing 0 live rows and 0 dead
rows; 0 rows in sample, 0 estimated total rows
INFO: analyzing "public.parent" inheritance tree
INFO: analyzing "pg_catalog.pg_authid"
INFO: "pg_authid": scanned 1 of 1 pages, containing 1 live rows and 0
dead rows; 1 rows in sample, 1 estimated total rows
> The rest of these review comments are strictly observations; they're not
> requests for you to change the patch, but they may deserve more discussion.
I'd like to give my opinions on those things later on.
Sorry for the long delay.
[1] http://www.postgresql.org/message-id/53F4707C.8030904@lab.ntt.co.jp
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] foreign_inherit-v14.patch (109.4K, ../../53F48245.6020204@lab.ntt.co.jp/2-foreign_inherit-v14.patch)
download | inline diff:
*** a/contrib/file_fdw/file_fdw.c
--- b/contrib/file_fdw/file_fdw.c
***************
*** 115,120 **** static void fileGetForeignRelSize(PlannerInfo *root,
--- 115,125 ----
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,
***************
*** 143,149 **** 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,
--- 148,154 ----
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,
***************
*** 161,166 **** file_fdw_handler(PG_FUNCTION_ARGS)
--- 166,172 ----
fdwroutine->GetForeignRelSize = fileGetForeignRelSize;
fdwroutine->GetForeignPaths = fileGetForeignPaths;
+ fdwroutine->ReparameterizeForeignPath = fileReparameterizeForeignPath;
fdwroutine->GetForeignPlan = fileGetForeignPlan;
fdwroutine->ExplainForeignScan = fileExplainForeignScan;
fdwroutine->BeginForeignScan = fileBeginForeignScan;
***************
*** 509,515 **** fileGetForeignPaths(PlannerInfo *root,
(Node *) columns));
/* Estimate costs */
! estimate_costs(root, baserel, fdw_private,
&startup_cost, &total_cost);
/*
--- 515,522 ----
(Node *) columns));
/* Estimate costs */
! estimate_costs(root, baserel,
! fdw_private, NIL,
&startup_cost, &total_cost);
/*
***************
*** 534,539 **** fileGetForeignPaths(PlannerInfo *root,
--- 541,581 ----
}
/*
+ * 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);
+
+ /* Recreate 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
*/
***************
*** 966,977 **** 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;
/*
--- 1008,1020 ----
*/
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;
/*
***************
*** 982,989 **** 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;
}
--- 1025,1035 ----
*/
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/file_fdw/input/file_fdw.source
--- b/contrib/file_fdw/input/file_fdw.source
***************
*** 134,139 **** DELETE FROM agg_csv WHERE a = 100;
--- 134,177 ----
-- but this should be ignored
SELECT * FROM agg_csv FOR UPDATE;
+ -- table inheritance tests
+ CREATE TABLE agg (a int2, b float4);
+ ALTER FOREIGN TABLE agg_text INHERIT agg;
+ ALTER FOREIGN TABLE agg_csv INHERIT agg;
+ INSERT INTO agg
+ SELECT x, 5432.0 FROM generate_series(0,1000) x;
+ SELECT * FROM agg WHERE b < 10.0 ORDER BY a, b;
+ SELECT * FROM ONLY agg WHERE b < 10.0 ORDER BY a, b;
+ SELECT * FROM agg_text;
+ SELECT * FROM agg_csv;
+ -- updates aren't supported
+ UPDATE agg SET a = 1;
+ DELETE FROM agg WHERE a = 100;
+ -- but this should be ignored
+ SELECT * FROM agg WHERE b < 10.0 ORDER BY a, b FOR UPDATE;
+ -- path reparameterization should work
+ CREATE TABLE int_tbl (f1) AS SELECT x FROM generate_series(0,1000) x;
+ ANALYZE int_tbl;
+ ANALYZE agg;
+ ANALYZE agg_text;
+ ANALYZE agg_csv;
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE)
+ SELECT * FROM agg JOIN (SELECT f1 FROM int_tbl LIMIT 1) ss ON a = f1;
+ \t off
+ SELECT * FROM agg JOIN (SELECT f1 FROM int_tbl LIMIT 1) ss ON a = f1;
+ CREATE INDEX agg_idx ON agg (a);
+ ANALYZE agg;
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE)
+ SELECT * FROM agg JOIN (SELECT f1 FROM int_tbl LIMIT 1) ss ON a = f1;
+ \t off
+ SELECT * FROM agg JOIN (SELECT f1 FROM int_tbl LIMIT 1) ss ON a = f1;
+ ALTER FOREIGN TABLE agg_csv NO INHERIT agg;
+ ALTER FOREIGN TABLE agg_text NO INHERIT agg;
+ DROP TABLE int_tbl;
+ DROP TABLE agg;
+
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/file_fdw/output/file_fdw.source
--- b/contrib/file_fdw/output/file_fdw.source
***************
*** 219,224 **** SELECT * FROM agg_csv FOR UPDATE;
--- 219,348 ----
42 | 324.78
(3 rows)
+ -- table inheritance tests
+ CREATE TABLE agg (a int2, b float4);
+ ALTER FOREIGN TABLE agg_text INHERIT agg;
+ ALTER FOREIGN TABLE agg_csv INHERIT agg;
+ INSERT INTO agg
+ SELECT x, 5432.0 FROM generate_series(0,1000) x;
+ SELECT * FROM agg WHERE b < 10.0 ORDER BY a, b;
+ a | b
+ ----+---------
+ 0 | 0.09561
+ 0 | 0.09561
+ 56 | 7.8
+ (3 rows)
+
+ SELECT * FROM ONLY agg WHERE b < 10.0 ORDER BY a, b;
+ a | b
+ ---+---
+ (0 rows)
+
+ SELECT * FROM agg_text;
+ a | b
+ -----+---------
+ 56 | 7.8
+ 100 | 99.097
+ 0 | 0.09561
+ 42 | 324.78
+ (4 rows)
+
+ SELECT * FROM agg_csv;
+ a | b
+ -----+---------
+ 100 | 99.097
+ 0 | 0.09561
+ 42 | 324.78
+ (3 rows)
+
+ -- updates aren't supported
+ UPDATE agg SET a = 1;
+ ERROR: cannot update foreign table "agg_text"
+ DELETE FROM agg WHERE a = 100;
+ ERROR: cannot delete from foreign table "agg_text"
+ -- but this should be ignored
+ SELECT * FROM agg WHERE b < 10.0 ORDER BY a, b FOR UPDATE;
+ a | b
+ ----+---------
+ 0 | 0.09561
+ 0 | 0.09561
+ 56 | 7.8
+ (3 rows)
+
+ -- path reparameterization should work
+ CREATE TABLE int_tbl (f1) AS SELECT x FROM generate_series(0,1000) x;
+ ANALYZE int_tbl;
+ ANALYZE agg;
+ ANALYZE agg_text;
+ ANALYZE agg_csv;
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE)
+ SELECT * FROM agg JOIN (SELECT f1 FROM int_tbl LIMIT 1) ss ON a = f1;
+ Hash Join
+ Output: agg.a, agg.b, int_tbl.f1
+ Hash Cond: (agg.a = int_tbl.f1)
+ -> Append
+ -> Seq Scan on public.agg
+ Output: agg.a, agg.b
+ -> Foreign Scan on public.agg_text
+ Output: agg_text.a, agg_text.b
+ Foreign File: @abs_srcdir@/data/agg.data
+ -> Foreign Scan on public.agg_csv
+ Output: agg_csv.a, agg_csv.b
+ Foreign File: @abs_srcdir@/data/agg.csv
+ -> Hash
+ Output: int_tbl.f1
+ -> Limit
+ Output: int_tbl.f1
+ -> Seq Scan on public.int_tbl
+ Output: int_tbl.f1
+
+ \t off
+ SELECT * FROM agg JOIN (SELECT f1 FROM int_tbl LIMIT 1) ss ON a = f1;
+ a | b | f1
+ ---+---------+----
+ 0 | 5432 | 0
+ 0 | 0.09561 | 0
+ 0 | 0.09561 | 0
+ (3 rows)
+
+ CREATE INDEX agg_idx ON agg (a);
+ ANALYZE agg;
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE)
+ SELECT * FROM agg JOIN (SELECT f1 FROM int_tbl LIMIT 1) ss ON a = f1;
+ Nested Loop
+ Output: agg.a, agg.b, int_tbl.f1
+ -> Limit
+ Output: int_tbl.f1
+ -> Seq Scan on public.int_tbl
+ Output: int_tbl.f1
+ -> Append
+ -> Index Scan using agg_idx on public.agg
+ Output: agg.a, agg.b
+ Index Cond: (agg.a = int_tbl.f1)
+ -> Foreign Scan on public.agg_text
+ Output: agg_text.a, agg_text.b
+ Filter: (int_tbl.f1 = agg_text.a)
+ Foreign File: @abs_srcdir@/data/agg.data
+ -> Foreign Scan on public.agg_csv
+ Output: agg_csv.a, agg_csv.b
+ Filter: (int_tbl.f1 = agg_csv.a)
+ Foreign File: @abs_srcdir@/data/agg.csv
+
+ \t off
+ SELECT * FROM agg JOIN (SELECT f1 FROM int_tbl LIMIT 1) ss ON a = f1;
+ a | b | f1
+ ---+---------+----
+ 0 | 5432 | 0
+ 0 | 0.09561 | 0
+ 0 | 0.09561 | 0
+ (3 rows)
+
+ ALTER FOREIGN TABLE agg_csv NO INHERIT agg;
+ ALTER FOREIGN TABLE agg_text NO INHERIT agg;
+ DROP TABLE int_tbl;
+ DROP TABLE agg;
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 2835,2840 **** NOTICE: NEW: (13,"test triggered !")
--- 2835,3373 ----
(1 row)
-- ===================================================================
+ -- test inheritance features
+ -- ===================================================================
+ CREATE TABLE a (aa TEXT);
+ CREATE TABLE loct (aa TEXT, bb TEXT);
+ CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a)
+ SERVER loopback OPTIONS (table_name 'loct');
+ INSERT INTO a(aa) VALUES('aaa');
+ INSERT INTO a(aa) VALUES('aaaa');
+ INSERT INTO a(aa) VALUES('aaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaaa');
+ INSERT INTO b(aa) VALUES('bbb');
+ INSERT INTO b(aa) VALUES('bbbb');
+ INSERT INTO b(aa) VALUES('bbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbbb');
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | aaa
+ a | aaaa
+ a | aaaaa
+ a | aaaaaa
+ a | aaaaaaa
+ a | aaaaaaaa
+ b | bbb
+ b | bbbb
+ b | bbbbb
+ b | bbbbbb
+ b | bbbbbbb
+ b | bbbbbbbb
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----------+----
+ b | bbb |
+ b | bbbb |
+ b | bbbbb |
+ b | bbbbbb |
+ b | bbbbbbb |
+ b | bbbbbbbb |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | aaa
+ a | aaaa
+ a | aaaaa
+ a | aaaaaa
+ a | aaaaaaa
+ a | aaaaaaaa
+ (6 rows)
+
+ UPDATE a SET aa='zzzzzz' WHERE aa LIKE 'aaa%';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ b | bbb
+ b | bbbb
+ b | bbbbb
+ b | bbbbbb
+ b | bbbbbbb
+ b | bbbbbbbb
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----------+----
+ b | bbb |
+ b | bbbb |
+ b | bbbbb |
+ b | bbbbbb |
+ b | bbbbbbb |
+ b | bbbbbbbb |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ (6 rows)
+
+ UPDATE b SET aa='new';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+-----+----
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ (6 rows)
+
+ UPDATE a SET aa='new';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+-----
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+-----+----
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+-----
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ (6 rows)
+
+ DELETE FROM a;
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----
+ (0 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----+----
+ (0 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----
+ (0 rows)
+
+ DROP TABLE a CASCADE;
+ NOTICE: drop cascades to foreign table b
+ DROP TABLE loct;
+ -- Check SELECT FOR UPDATE/SHARE with an inherited source table
+ create table loct1 (f1 int, f2 int, f3 int);
+ create table loct2 (f1 int, f2 int, f3 int);
+ create table foo (f1 int, f2 int);
+ create foreign table foo2 (f3 int) inherits (foo)
+ server loopback options (table_name 'loct1');
+ create table bar (f1 int, f2 int);
+ create foreign table bar2 (f3 int) inherits (bar)
+ server loopback options (table_name 'loct2');
+ insert into foo values(1,1);
+ insert into foo values(3,3);
+ insert into foo2 values(2,2,2);
+ insert into foo2 values(3,3,3);
+ insert into bar values(1,1);
+ insert into bar values(2,2);
+ insert into bar values(3,3);
+ insert into bar values(4,4);
+ insert into bar2 values(1,1,1);
+ insert into bar2 values(2,2,2);
+ insert into bar2 values(3,3,3);
+ insert into bar2 values(4,4,4);
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for update;
+ QUERY PLAN
+ ----------------------------------------------------------------------------------------------
+ LockRows
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ -> Hash Join
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Append
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.ctid, bar2.tableoid, bar2.*
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (22 rows)
+
+ select * from bar where f1 in (select f1 from foo) for update;
+ f1 | f2
+ ----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ (6 rows)
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for share;
+ QUERY PLAN
+ ----------------------------------------------------------------------------------------------
+ LockRows
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ -> Hash Join
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Append
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.ctid, bar2.tableoid, bar2.*
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (22 rows)
+
+ select * from bar where f1 in (select f1 from foo) for share;
+ f1 | f2
+ ----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ (6 rows)
+
+ -- Check UPDATE with inherited target and an inherited source table
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ QUERY PLAN
+ ---------------------------------------------------------------------------------------------
+ Update on public.bar
+ -> Hash Join
+ Output: bar.f1, (bar.f2 + 100), bar.ctid, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ -> Hash Join
+ Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar2.f1 = foo.f1)
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (34 rows)
+
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+ relname | f1 | f2
+ ---------+----+-----
+ bar | 1 | 101
+ bar | 2 | 102
+ bar | 3 | 103
+ bar | 4 | 4
+ bar2 | 1 | 101
+ bar2 | 2 | 102
+ bar2 | 3 | 103
+ bar2 | 4 | 4
+ (8 rows)
+
+ -- Check UPDATE with inherited target and an appendrel subquery
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ QUERY PLAN
+ --------------------------------------------------------------------------------------
+ Update on public.bar
+ -> Hash Join
+ Output: bar.f1, (bar.f2 + 100), bar.ctid, (ROW(foo.f1))
+ Hash Cond: (foo.f1 = bar.f1)
+ -> Append
+ -> Seq Scan on public.foo
+ Output: ROW(foo.f1), foo.f1
+ -> Foreign Scan on public.foo2
+ Output: ROW(foo2.f1), foo2.f1
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Seq Scan on public.foo foo_1
+ Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3)
+ -> Foreign Scan on public.foo2 foo2_1
+ Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3)
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Hash
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Merge Join
+ Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, (ROW(foo.f1))
+ Merge Cond: (bar2.f1 = foo.f1)
+ -> Sort
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Sort Key: bar2.f1
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Sort
+ Output: (ROW(foo.f1)), foo.f1
+ Sort Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: ROW(foo.f1), foo.f1
+ -> Foreign Scan on public.foo2
+ Output: ROW(foo2.f1), foo2.f1
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Seq Scan on public.foo foo_1
+ Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3)
+ -> Foreign Scan on public.foo2 foo2_1
+ Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3)
+ Remote SQL: SELECT f1 FROM public.loct1
+ (42 rows)
+
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+ relname | f1 | f2
+ ---------+----+-----
+ bar | 1 | 201
+ bar | 2 | 202
+ bar | 3 | 203
+ bar | 4 | 104
+ bar2 | 1 | 201
+ bar2 | 2 | 202
+ bar2 | 3 | 203
+ bar2 | 4 | 104
+ (8 rows)
+
+ drop table foo cascade;
+ NOTICE: drop cascades to foreign table foo2
+ drop table bar cascade;
+ NOTICE: drop cascades to foreign table bar2
+ drop table loct1;
+ drop table loct2;
+ -- Test WHERE CURRENT OF
+ create table ltbl (a int, b text);
+ create table ptbl (a int, b text);
+ create table locc () inherits (ptbl);
+ insert into locc values(1, 'foo');
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ (1 row)
+
+ create foreign table remc () inherits (ptbl)
+ server loopback options (table_name 'ltbl');
+ insert into remc values(2, 'bar');
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'foo';
+ fetch from c;
+ ?column?
+ ----------
+ 1
+ (1 row)
+
+ update ptbl set b = null where current of c;
+ ERROR: WHERE CURRENT OF is not supported for this table type
+ rollback;
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ drop table ptbl cascade;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to table locc
+ drop cascades to foreign table remc
+ drop table ltbl;
+ -- Test path reparameterization
+ create table loctbl (id int, x int);
+ create table parent (id int, x int);
+ create table locchild () inherits (parent);
+ create foreign table remchild () inherits (parent)
+ server loopback options (table_name 'loctbl');
+ insert into parent select x, x from generate_series(0,1000) x;
+ insert into locchild select x, x from generate_series(0,1000) x;
+ insert into remchild select x, x from generate_series(0,1000) x;
+ create table inttbl (f1) as select x from generate_series(0,1000) x;
+ analyze inttbl;
+ analyze parent;
+ analyze locchild;
+ analyze remchild;
+ explain (costs off)
+ select * from parent join (select f1 from inttbl limit 1) ss on id = f1;
+ QUERY PLAN
+ --------------------------------------
+ Hash Join
+ Hash Cond: (parent.id = inttbl.f1)
+ -> Append
+ -> Seq Scan on parent
+ -> Seq Scan on locchild
+ -> Foreign Scan on remchild
+ -> Hash
+ -> Limit
+ -> Seq Scan on inttbl
+ (9 rows)
+
+ select * from parent join (select f1 from inttbl limit 1) ss on id = f1;
+ id | x | f1
+ ----+---+----
+ 0 | 0 | 0
+ 0 | 0 | 0
+ 0 | 0 | 0
+ (3 rows)
+
+ create index parent_idx on parent (id);
+ create index locchild_idx on locchild (id);
+ analyze parent;
+ analyze locchild;
+ explain (costs off)
+ select * from parent join (select f1 from inttbl limit 1) ss on id = f1;
+ QUERY PLAN
+ -------------------------------------------------------
+ Nested Loop
+ -> Limit
+ -> Seq Scan on inttbl
+ -> Append
+ -> Index Scan using parent_idx on parent
+ Index Cond: (id = inttbl.f1)
+ -> Index Scan using locchild_idx on locchild
+ Index Cond: (id = inttbl.f1)
+ -> Foreign Scan on remchild
+ (9 rows)
+
+ select * from parent join (select f1 from inttbl limit 1) ss on id = f1;
+ id | x | f1
+ ----+---+----
+ 0 | 0 | 0
+ 0 | 0 | 0
+ 0 | 0 | 0
+ (3 rows)
+
+ drop table parent cascade;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to table locchild
+ drop cascades to foreign table remchild
+ drop table loctbl;
+ drop table inttbl;
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
CREATE SCHEMA import_source;
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 238,243 **** static void postgresGetForeignRelSize(PlannerInfo *root,
--- 238,248 ----
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,
***************
*** 341,346 **** postgres_fdw_handler(PG_FUNCTION_ARGS)
--- 346,352 ----
/* Functions for scanning foreign tables */
routine->GetForeignRelSize = postgresGetForeignRelSize;
routine->GetForeignPaths = postgresGetForeignPaths;
+ routine->ReparameterizeForeignPath = postgresReparameterizeForeignPath;
routine->GetForeignPlan = postgresGetForeignPlan;
routine->BeginForeignScan = postgresBeginForeignScan;
routine->IterateForeignScan = postgresIterateForeignScan;
***************
*** 731,736 **** postgresGetForeignPaths(PlannerInfo *root,
--- 737,784 ----
}
/*
+ * 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;
+
+ /* Recreate 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
*/
***************
*** 823,828 **** postgresGetForeignPlan(PlannerInfo *root,
--- 871,887 ----
{
RowMarkClause *rc = get_parse_rowmark(root->parse, baserel->relid);
+ if (rc == NULL)
+ {
+ PlanRowMark *prm = get_plan_rowmark(root->rowMarks, baserel->relid);
+
+ if (prm)
+ {
+ if (prm->rti != prm->prti)
+ rc = get_parse_rowmark(root->parse, prm->prti);
+ }
+ }
+
if (rc)
{
/*
***************
*** 1777,1787 **** 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;
--- 1836,1843 ----
}
else
{
! Selectivity join_sel;
! QualCost join_cost;
/* Use rows/width estimates made by set_baserel_size_estimates. */
rows = baserel->rows;
***************
*** 1794,1810 **** 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;
--- 1850,1878 ----
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/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 611,616 **** UPDATE rem1 SET f2 = 'testo';
--- 611,786 ----
INSERT INTO rem1(f2) VALUES ('test') RETURNING ctid;
-- ===================================================================
+ -- test inheritance features
+ -- ===================================================================
+
+ CREATE TABLE a (aa TEXT);
+ CREATE TABLE loct (aa TEXT, bb TEXT);
+ CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a)
+ SERVER loopback OPTIONS (table_name 'loct');
+
+ INSERT INTO a(aa) VALUES('aaa');
+ INSERT INTO a(aa) VALUES('aaaa');
+ INSERT INTO a(aa) VALUES('aaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaaa');
+
+ INSERT INTO b(aa) VALUES('bbb');
+ INSERT INTO b(aa) VALUES('bbbb');
+ INSERT INTO b(aa) VALUES('bbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbbb');
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE a SET aa='zzzzzz' WHERE aa LIKE 'aaa%';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE b SET aa='new';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE a SET aa='new';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ DELETE FROM a;
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ DROP TABLE a CASCADE;
+ DROP TABLE loct;
+
+ -- Check SELECT FOR UPDATE/SHARE with an inherited source table
+ create table loct1 (f1 int, f2 int, f3 int);
+ create table loct2 (f1 int, f2 int, f3 int);
+
+ create table foo (f1 int, f2 int);
+ create foreign table foo2 (f3 int) inherits (foo)
+ server loopback options (table_name 'loct1');
+ create table bar (f1 int, f2 int);
+ create foreign table bar2 (f3 int) inherits (bar)
+ server loopback options (table_name 'loct2');
+
+ insert into foo values(1,1);
+ insert into foo values(3,3);
+ insert into foo2 values(2,2,2);
+ insert into foo2 values(3,3,3);
+ insert into bar values(1,1);
+ insert into bar values(2,2);
+ insert into bar values(3,3);
+ insert into bar values(4,4);
+ insert into bar2 values(1,1,1);
+ insert into bar2 values(2,2,2);
+ insert into bar2 values(3,3,3);
+ insert into bar2 values(4,4,4);
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for update;
+ select * from bar where f1 in (select f1 from foo) for update;
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for share;
+ select * from bar where f1 in (select f1 from foo) for share;
+
+ -- Check UPDATE with inherited target and an inherited source table
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+
+ -- Check UPDATE with inherited target and an appendrel subquery
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+
+ drop table foo cascade;
+ drop table bar cascade;
+ drop table loct1;
+ drop table loct2;
+
+ -- Test WHERE CURRENT OF
+ create table ltbl (a int, b text);
+
+ create table ptbl (a int, b text);
+ create table locc () inherits (ptbl);
+ insert into locc values(1, 'foo');
+
+ select * from ptbl;
+
+ create foreign table remc () inherits (ptbl)
+ server loopback options (table_name 'ltbl');
+ insert into remc values(2, 'bar');
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'foo';
+ fetch from c;
+ update ptbl set b = null where current of c;
+ rollback;
+
+ select * from ptbl;
+
+ drop table ptbl cascade;
+ drop table ltbl;
+
+ -- Test path reparameterization
+ create table loctbl (id int, x int);
+
+ create table parent (id int, x int);
+ create table locchild () inherits (parent);
+ create foreign table remchild () inherits (parent)
+ server loopback options (table_name 'loctbl');
+ insert into parent select x, x from generate_series(0,1000) x;
+ insert into locchild select x, x from generate_series(0,1000) x;
+ insert into remchild select x, x from generate_series(0,1000) x;
+
+ create table inttbl (f1) as select x from generate_series(0,1000) x;
+
+ analyze inttbl;
+ analyze parent;
+ analyze locchild;
+ analyze remchild;
+
+ explain (costs off)
+ select * from parent join (select f1 from inttbl limit 1) ss on id = f1;
+ select * from parent join (select f1 from inttbl limit 1) ss on id = f1;
+
+ create index parent_idx on parent (id);
+ create index locchild_idx on locchild (id);
+ analyze parent;
+ analyze locchild;
+
+ explain (costs off)
+ select * from parent join (select f1 from inttbl limit 1) ss on id = f1;
+ select * from parent join (select f1 from inttbl limit 1) ss on id = f1;
+
+ drop table parent cascade;
+ drop table loctbl;
+ drop table inttbl;
+
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
*** a/doc/src/sgml/ddl.sgml
--- b/doc/src/sgml/ddl.sgml
***************
*** 2224,2229 **** VALUES ('Albany', NULL, NULL, 'NY');
--- 2224,2245 ----
further privileges to be granted.
</para>
+ <para>
+ Note that a foreign table can also inherit from more than one parent
+ table (see <xref linkend="ddl-foreign-data">).
+ Like normal tables, table inheritance is typically established when
+ the foreign table is created, using the <literal>INHERITS</> clause of
+ the <xref linkend="sql-createforeigntable"> statement. Alternatively,
+ a foreign table which is already defined in a compatible way can have
+ a new parent relationship added, using the <literal>INHERIT</literal>
+ variant of <xref linkend="sql-alterforeigntable">.
+ <command>CREATE FOREIGN TABLE</command> and
+ <command>ALTER FOREIGN TABLE</command> follow the same rules for
+ duplicate column merging and rejection that apply during
+ <command>CREATE TABLE</command> and <command>ALTER TABLE</command>,
+ respectively.
+ </para>
+
<sect2 id="ddl-inherit-caveats">
<title>Caveats</title>
***************
*** 2371,2377 **** VALUES ('Albany', NULL, NULL, 'NY');
table of a single parent table. The parent table itself is normally
empty; it exists just to represent the entire data set. You should be
familiar with inheritance (see <xref linkend="ddl-inherit">) before
! attempting to set up partitioning.
</para>
<para>
--- 2387,2396 ----
table of a single parent table. The parent table itself is normally
empty; it exists just to represent the entire data set. You should be
familiar with inheritance (see <xref linkend="ddl-inherit">) before
! attempting to set up partitioning. (The setup and management of
! partitioned tables illustrated in this section assume that each
! partition is a normal table. However, you can do that in a similar way
! for cases where some or all partitions are foreign tables.)
</para>
<para>
***************
*** 2434,2441 **** VALUES ('Albany', NULL, NULL, 'NY');
</para>
<para>
! We will refer to the child tables as partitions, though they
! are in every way normal <productname>PostgreSQL</> tables.
</para>
</listitem>
--- 2453,2460 ----
</para>
<para>
! We will refer to the child tables as partitions, though we assume
! that they are normal <productname>PostgreSQL</> tables.
</para>
</listitem>
*** a/doc/src/sgml/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
***************
*** 162,167 **** GetForeignPaths (PlannerInfo *root,
--- 162,198 ----
<para>
<programlisting>
+ ForeignPath *
+ ReparameterizeForeignPath (PlannerInfo *root,
+ RelOptInfo *baserel,
+ Oid foreigntableid,
+ ForeignPath *path,
+ Relids required_outer);
+ </programlisting>
+
+ Create an access path for a scan on a foreign table using join
+ clauses, which is called a <quote>parameterized path</>.
+ This is called during query planning.
+ The parameters are as for <function>GetForeignRelSize</>, plus
+ the <structname>ForeignPath</> (previously produced by
+ <function>GetForeignPaths</>), and the IDs of all other tables
+ that provide the join clauses.
+ </para>
+
+ <para>
+ 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.
+ </para>
+
+ <para>
+ See <xref linkend="fdw-planning"> for additional information.
+ </para>
+
+ <para>
+ <programlisting>
ForeignScan *
GetForeignPlan (PlannerInfo *root,
RelOptInfo *baserel,
***************
*** 868,877 **** GetForeignServerByName(const char *name, bool missing_ok);
<para>
The FDW callback functions <function>GetForeignRelSize</>,
! <function>GetForeignPaths</>, <function>GetForeignPlan</>, and
! <function>PlanForeignModify</> must fit into the workings of the
! <productname>PostgreSQL</> planner. Here are some notes about what
! they must do.
</para>
<para>
--- 899,908 ----
<para>
The FDW callback functions <function>GetForeignRelSize</>,
! <function>GetForeignPaths</>, <function>ReparameterizeForeignPath</>,
! <function>GetForeignPlan</>, and <function>PlanForeignModify</> must fit
! into the workings of the <productname>PostgreSQL</> planner. Here are
! some notes about what they must do.
</para>
<para>
***************
*** 901,914 **** GetForeignServerByName(const char *name, bool missing_ok);
to initialize it to NULL when the <literal>baserel</> node is created.
It is useful for passing information forward from
<function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
! <function>GetForeignPaths</> to <function>GetForeignPlan</>, thereby
! avoiding recalculation.
</para>
<para>
! <function>GetForeignPaths</> can identify the meaning of different
! access paths by storing private information in the
! <structfield>fdw_private</> field of <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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
--- 932,948 ----
to initialize it to NULL when the <literal>baserel</> node is created.
It is useful for passing information forward from
<function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
! <function>GetForeignPaths</> to <function>ReparameterizeForeignPath</>
! and/or
! <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
! to <function>GetForeignPlan</>, thereby avoiding recalculation.
</para>
<para>
! <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
! can identify the meaning of different access paths by storing private
! information in the <structfield>fdw_private</> field of
! <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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
***************
*** 951,960 **** GetForeignServerByName(const char *name, bool missing_ok);
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
! clause should happen during <function>GetForeignPaths</>, since it would
! affect the cost estimate for the path. The path's
! <structfield>fdw_private</> field would probably include a pointer to
! the identified clause's <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from <literal>scan_clauses</>,
but add the <replaceable>sub_expression</> to <structfield>fdw_exprs</>
to ensure that it gets massaged into executable form. It would probably
--- 985,995 ----
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
! clause should happen during <function>GetForeignPaths</> or
! <function>ReparameterizeForeignPath</>, since it would affect the cost
! estimate for the path. The path's <structfield>fdw_private</> field
! would probably include a pointer to the identified clause's
! <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from <literal>scan_clauses</>,
but add the <replaceable>sub_expression</> to <structfield>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,51 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 42,55 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ ADD <replaceable class="PARAMETER">table_constraint</replaceable>
+ DROP CONSTRAINT [ IF EXISTS ] <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
DISABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
ENABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
ENABLE REPLICA TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
ENABLE ALWAYS TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 164,169 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 168,217 ----
</varlistentry>
<varlistentry>
+ <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds a new constraint to a table using the same syntax as
+ <xref linkend="SQL-CREATEFOREIGNTABLE">.
+ 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.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DROP CONSTRAINT [ IF EXISTS ]</literal></term>
+ <listitem>
+ <para>
+ This form drops the specified constraint on a table.
+ If <literal>IF EXISTS</literal> is specified and the constraint
+ does not exist, no error is thrown. In this case a notice is issued instead.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OWNER</literal></term>
<listitem>
<para>
***************
*** 285,290 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 333,356 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">table_constraint</replaceable></term>
+ <listitem>
+ <para>
+ New table constraint for the table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">constraint_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing constraint to drop.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>CASCADE</literal></term>
<listitem>
<para>
***************
*** 336,341 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 402,417 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">new_owner</replaceable></term>
<listitem>
<para>
***************
*** 365,374 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint is
! added, or a column type is changed with <literal>SET DATA TYPE</>. It is
! the user's responsibility to ensure that the table definition matches the
! remote side.
</para>
<para>
--- 441,450 ----
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint or
! <literal>CHECK</> constraint is added, or a column type is changed with
! <literal>SET DATA TYPE</>. It is the user's responsibility to ensure that
! the table definition matches the remote side.
</para>
<para>
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 967,972 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 967,979 ----
</para>
<para>
+ If a table has any descendant tables that are foreign, a recursive
+ <literal>SET STORAGE</literal> operation will be rejected since it
+ is not permitted to add an <literal>oid</literal> system column to
+ foreign tables.
+ </para>
+
+ <para>
The <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>,
and <literal>TABLESPACE</> actions never recurse to descendant tables;
that is, they always act as though <literal>ONLY</> were specified.
***************
*** 975,980 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 982,1000 ----
</para>
<para>
+ When adding a <literal>CHECK</> constraint with the <literal>NOT VALID
+ </literal> option recursively, an inherited constraint on a descendant
+ table that is foreign will be marked valid without checking
+ consistency with the foreign server.
+ </para>
+
+ <para>
+ A recursive <literal>SET STORAGE</literal> operation will make the
+ storage mode for a descendant table's column unchanged if the table is
+ foreign.
+ </para>
+
+ <para>
Changing any part of a system catalog table is not permitted.
</para>
*** a/doc/src/sgml/ref/analyze.sgml
--- b/doc/src/sgml/ref/analyze.sgml
***************
*** 200,205 **** ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
--- 200,212 ----
</para>
<para>
+ 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
+ <command>ANALYZE</command>, the command prints a warning and does nothing.
+ </para>
+
+ <para>
If the table being analyzed is completely empty, <command>ANALYZE</command>
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 ****
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
--- 19,29 ----
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
! | <replaceable>table_constraint</replaceable> }
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 30,36 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 32,44 ----
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ NOT NULL |
NULL |
+ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) |
DEFAULT <replaceable>default_expr</replaceable> }
+
+ <phrase>and <replaceable class="PARAMETER">table_constraint</replaceable> is:</phrase>
+
+ [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
+ { CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) }
</synopsis>
</refsynopsisdiv>
***************
*** 138,143 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 146,172 ----
</varlistentry>
<varlistentry>
+ <term><literal>CHECK ( <replaceable class="PARAMETER">expression</replaceable> )</literal></term>
+ <listitem>
+ <para>
+ The <literal>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.
+ </para>
+
+ <para>
+ Currently, <literal>CHECK</literal> expressions cannot contain
+ subqueries nor refer to variables other than columns of the
+ current row. The system column <literal>tableoid</literal>
+ may be referenced, but not any other system column.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>DEFAULT
<replaceable>default_expr</replaceable></literal></term>
<listitem>
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 188,205 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for the
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
***************
*** 187,192 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 228,251 ----
</refsect1>
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ Constraints on foreign tables are not enforced on insert or update.
+ Those definitions simply declare the constraints hold for all rows
+ in the foreign tables. It is the user's responsibility to ensure
+ that those definitions match the remote side. Such constraints are
+ used for some kind of query optimization such as constraint exclusion
+ for partitioned tables (see <xref linkend="ddl-partitioning">).
+ </para>
+
+ <para>
+ Since it is not permitted to add an <literal>oid</> system column to
+ foreign tables, the command will be rejected if any parent tables
+ have <literal>oid</> system columns.
+ </para>
+ </refsect1>
<refsect1 id="SQL-CREATEFOREIGNTABLE-examples">
<title>Examples</title>
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 2217,2222 **** AddRelationNewConstraints(Relation rel,
--- 2217,2228 ----
if (cdef->contype != CONSTR_CHECK)
continue;
+ if (cdef->is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("CHECK constraints on foreign tables cannot be marked NO INHERIT")));
+
if (cdef->raw_expr != NULL)
{
Assert(cdef->cooked_expr == NULL);
*** 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;
***************
*** 102,107 **** static int acquire_sample_rows(Relation onerel, int elevel,
--- 103,109 ----
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
static int compare_rows(const void *a, const void *b);
+ static bool has_foreign(Oid parentrelId, List *tableOIDs);
static int acquire_inherited_sample_rows(Relation onerel, int elevel,
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
***************
*** 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;
--- 117,126 ----
* 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)
--- 134,140 ----
elevel = DEBUG2;
/* Set up static variables */
+ vac_mode = vacmode;
vac_strategy = bstrategy;
/*
***************
*** 1439,1444 **** compare_rows(const void *a, const void *b)
--- 1445,1484 ----
/*
+ * Check wether to contain at least one foreign table
+ */
+ static bool
+ has_foreign(Oid parentrelId, List *tableOIDs)
+ {
+ bool result = false;
+ ListCell *lc;
+
+ /* 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, NoLock);
+ }
+ return result;
+ }
+
+ /*
* acquire_inherited_sample_rows -- acquire sample rows from inheritance tree
*
* This has the same API as acquire_sample_rows, except that rows are
***************
*** 1452,1457 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1492,1498 ----
{
List *tableOIDs;
Relation *rels;
+ AcquireSampleRowsFunc *acquirefunc;
double *relblocks;
double totalblocks;
int numrows,
***************
*** 1482,1491 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1523,1544 ----
}
/*
+ * If we are not in VAC_MODE_SINGLE case and the inheritance set contains
+ * at least one foreign table, then fail.
+ */
+ if (vac_mode != VAC_MODE_SINGLE)
+ {
+ if (has_foreign(RelationGetRelid(onerel), tableOIDs))
+ return 0;
+ }
+
+ /*
* Count the blocks in all the relations. The result could overflow
* 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++;
}
--- 1560,1595 ----
}
rels[nrels] = childrel;
!
! if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
! {
! FdwRoutine *fdwroutine;
! BlockNumber relpages = 0;
! bool ok = false;
!
! /* 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,
--- 1607,1613 ----
{
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 &&
--- 1623,1634 ----
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
***************
*** 309,315 **** 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,
--- 309,316 ----
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,
***************
*** 465,474 **** 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,
--- 466,471 ----
***************
*** 558,563 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
--- 555,584 ----
stmt->relation->relpersistence,
&inheritOids, &old_constraints, &parentOidCount);
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * Don't allow a foreign table to inherit from parents that have OID
+ * system columns.
+ */
+ if (parentOidCount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot inherit from relation with OIDs")));
+
+ /*
+ * Reset the storage parameter for inherited attributes that have
+ * non-default values.
+ */
+ foreach(listptr, schema)
+ {
+ ColumnDef *colDef = lfirst(listptr);
+
+ if (colDef->storage != 0)
+ colDef->storage = 0;
+ }
+ }
+
/*
* Create a tuple descriptor from the relation schema. Note that this
* deals with column names, types, and NOT NULL constraints, but not
***************
*** 3066,3089 **** 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;
--- 3087,3114 ----
* 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;
***************
*** 3096,3102 **** 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;
--- 3121,3128 ----
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;
***************
*** 3114,3120 **** 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)
--- 3140,3146 ----
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)
***************
*** 3128,3134 **** 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)
--- 3154,3160 ----
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)
***************
*** 3196,3206 **** 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;
--- 3222,3238 ----
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);
pass = AT_PASS_MISC;
***************
*** 3234,3240 **** 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);
--- 3266,3271 ----
***************
*** 4175,4181 **** 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
--- 4206,4213 ----
*/
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
***************
*** 4203,4210 **** 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);
}
}
--- 4235,4246 ----
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);
}
}
***************
*** 4494,4500 **** 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);
--- 4530,4536 ----
/* 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);
***************
*** 4790,4795 **** ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4826,4836 ----
/* 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 */
***************
*** 5390,5396 **** 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
--- 5431,5437 ----
/* 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
***************
*** 5782,5788 **** 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
--- 5823,5836 ----
/* 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
***************
*** 5793,5801 **** 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 */
--- 5841,5857 ----
* 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 */
***************
*** 7282,7288 **** 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);
--- 7338,7344 ----
/* 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);
***************
*** 7617,7623 **** 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,
--- 7673,7682 ----
* 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;
***************
*** 149,154 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
--- 150,169 ----
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.
*/
***************
*** 251,257 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
PushActiveSnapshot(GetTransactionSnapshot());
}
! analyze_rel(relid, vacstmt, vac_strategy);
if (use_own_xacts)
{
--- 266,272 ----
PushActiveSnapshot(GetTransactionSnapshot());
}
! analyze_rel(relid, vacstmt, vac_mode, vac_strategy);
if (use_own_xacts)
{
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 2275,2280 **** EvalPlanQualFetchRowMarks(EPQState *epqstate)
--- 2275,2300 ----
Assert(erm->markType == ROW_MARK_COPY);
+ /* if child rel, must check whether it produced this row */
+ if (erm->rti != erm->prti)
+ {
+ Oid tableoid;
+
+ datum = ExecGetJunkAttribute(epqstate->origslot,
+ aerm->toidAttNo,
+ &isNull);
+ /* non-locked rels could be on the inside of outer joins */
+ if (isNull)
+ continue;
+ tableoid = DatumGetObjectId(datum);
+
+ if (tableoid != RelationGetRelid(erm->relation))
+ {
+ /* this child is inactive right now */
+ continue;
+ }
+ }
+
/* fetch the whole-row Var for the relation */
datum = ExecGetJunkAttribute(epqstate->origslot,
aerm->wholeAttNo,
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 1994,1999 **** _copyRangeTblEntry(const RangeTblEntry *from)
--- 1994,2000 ----
COPY_NODE_FIELD(eref);
COPY_SCALAR_FIELD(lateral);
COPY_SCALAR_FIELD(inh);
+ COPY_SCALAR_FIELD(hasForeign);
COPY_SCALAR_FIELD(inFromCl);
COPY_SCALAR_FIELD(requiredPerms);
COPY_SCALAR_FIELD(checkAsUser);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 2315,2320 **** _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
--- 2315,2321 ----
COMPARE_NODE_FIELD(eref);
COMPARE_SCALAR_FIELD(lateral);
COMPARE_SCALAR_FIELD(inh);
+ COMPARE_SCALAR_FIELD(hasForeign);
COMPARE_SCALAR_FIELD(inFromCl);
COMPARE_SCALAR_FIELD(requiredPerms);
COMPARE_SCALAR_FIELD(checkAsUser);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 2419,2424 **** _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
--- 2419,2425 ----
WRITE_BOOL_FIELD(lateral);
WRITE_BOOL_FIELD(inh);
+ WRITE_BOOL_FIELD(hasForeign);
WRITE_BOOL_FIELD(inFromCl);
WRITE_UINT_FIELD(requiredPerms);
WRITE_OID_FIELD(checkAsUser);
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 1248,1253 **** _readRangeTblEntry(void)
--- 1248,1254 ----
READ_BOOL_FIELD(lateral);
READ_BOOL_FIELD(inh);
+ READ_BOOL_FIELD(hasForeign);
READ_BOOL_FIELD(inFromCl);
READ_UINT_FIELD(requiredPerms);
READ_OID_FIELD(checkAsUser);
*** a/src/backend/optimizer/prep/preptlist.c
--- b/src/backend/optimizer/prep/preptlist.c
***************
*** 111,116 **** preprocess_targetlist(PlannerInfo *root, List *tlist)
--- 111,118 ----
/* if parent of inheritance tree, need the tableoid too */
if (rc->isParent)
{
+ RangeTblEntry *rte = rt_fetch(rc->rti, parse->rtable);
+
var = makeVar(rc->rti,
TableOidAttributeNumber,
OIDOID,
***************
*** 123,128 **** preprocess_targetlist(PlannerInfo *root, List *tlist)
--- 125,146 ----
pstrdup(resname),
true);
tlist = lappend(tlist, tle);
+
+ /* if including foreign tables, fetch the whole row too */
+ if (rte->hasForeign)
+ {
+ /* Not a table, so we need the whole row as a junk var */
+ var = makeWholeRowVar(rt_fetch(rc->rti, range_table),
+ rc->rti,
+ 0,
+ false);
+ snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId);
+ tle = makeTargetEntry((Expr *) var,
+ list_length(tlist) + 1,
+ pstrdup(resname),
+ true);
+ tlist = lappend(tlist, tle);
+ }
}
}
else
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 1243,1248 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1243,1249 ----
LOCKMODE lockmode;
List *inhOIDs;
List *appinfos;
+ bool hasForeign;
ListCell *l;
/* Does RT entry allow inheritance? */
***************
*** 1315,1320 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1316,1322 ----
/* Scan the inheritance set and expand it */
appinfos = NIL;
+ hasForeign = false;
foreach(l, inhOIDs)
{
Oid childOID = lfirst_oid(l);
***************
*** 1344,1354 **** 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);
--- 1346,1357 ----
/*
* 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);
***************
*** 1394,1400 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
newrc->rti = childRTindex;
newrc->prti = rti;
newrc->rowmarkId = oldrc->rowmarkId;
! newrc->markType = oldrc->markType;
newrc->noWait = oldrc->noWait;
newrc->isParent = false;
--- 1397,1409 ----
newrc->rti = childRTindex;
newrc->prti = rti;
newrc->rowmarkId = oldrc->rowmarkId;
! if (childrte->relkind == RELKIND_FOREIGN_TABLE)
! {
! hasForeign = true;
! newrc->markType = ROW_MARK_COPY;
! }
! else
! newrc->markType = oldrc->markType;
newrc->noWait = oldrc->noWait;
newrc->isParent = false;
***************
*** 1422,1427 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1431,1440 ----
/* Otherwise, OK to add to root->append_rel_list */
root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+
+ /* And mark the parent table as having children that are foreign, if so */
+ if (hasForeign)
+ rte->hasForeign = true;
}
/*
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
***************
*** 16,21 ****
--- 16,22 ----
#include <math.h>
+ #include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
***************
*** 1921,1926 **** reparameterize_path(PlannerInfo *root, Path *path,
--- 1922,1952 ----
case T_SubqueryScan:
return create_subqueryscan_path(root, rel, path->pathkeys,
required_outer);
+ case T_ForeignScan:
+ {
+ ForeignPath *newpath = NULL;
+
+ /* Let the FDW reparameterize the path node if possible */
+ if (rel->fdwroutine->ReparameterizeForeignPath != 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);
+
+ newpath =
+ rel->fdwroutine->ReparameterizeForeignPath(root,
+ rel,
+ rte->relid,
+ (ForeignPath *) path,
+ required_outer);
+ }
+ return (Path *) newpath;
+ }
default:
break;
}
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 4225,4256 **** 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;
}
;
--- 4225,4256 ----
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,191 ----
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,
***************
*** 120,125 **** typedef struct FdwRoutine
--- 126,132 ----
/* 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/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 810,815 **** typedef struct RangeTblEntry
--- 810,816 ----
Alias *eref; /* expanded reference names */
bool lateral; /* subquery, function, or values is LATERAL? */
bool inh; /* inheritance requested? */
+ bool hasForeign; /* does inheritance include foreign tables? */
bool inFromCl; /* present in FROM clause? */
AclMode requiredPerms; /* bitmask of required access permissions */
Oid checkAsUser; /* if valid, check access as this role */
*** a/src/test/regress/expected/foreign_data.out
--- b/src/test/regress/expected/foreign_data.out
***************
*** 748,763 **** 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;
--- 748,759 ----
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;
***************
*** 832,837 **** ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 RENAME c1 TO foreign_column_1;
--- 828,931 ----
NOTICE: relation "doesnt_exist_ft1" does not exist, skipping
ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 RENAME TO foreign_table_1;
NOTICE: relation "doesnt_exist_ft1" does not exist, skipping
+ -- Table inheritance
+ CREATE TABLE pt1 (ff1 integer);
+ CREATE VIEW view1 AS SELECT 1;
+ ALTER TABLE view1 INHERIT pt1; -- ERROR
+ ERROR: "view1" is not a table or foreign table
+ CREATE FOREIGN TABLE ct1 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ CREATE FOREIGN TABLE ct2 (ff1 integer)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ ALTER FOREIGN TABLE ct2 INHERIT pt1;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+---------+--------------+-------------
+ ff1 | integer | | plain | |
+ Child tables: ct1,
+ ct2
+
+ \d+ ct1
+ Foreign table "public.ct1"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+---------+--------------+-------------
+ ff1 | integer | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ \d+ ct2
+ Foreign table "public.ct2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+---------+--------------+-------------
+ ff1 | integer | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ ALTER FOREIGN TABLE ct1 NO INHERIT pt1;
+ ALTER FOREIGN TABLE ct2 NO INHERIT pt1;
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk1 CHECK (ff1 > 0) NO INHERIT;
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (ff1 > 10);
+ -- connoinherit should be true for NO INHERIT constraint
+ SELECT pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pgc.connoinherit FROM pg_class AS pc INNER JOIN pg_constraint AS pgc ON (pgc.conrelid = pc.oid) WHERE pc.relname = 'pt1' order by 1,2;
+ relname | conname | contype | conislocal | coninhcount | connoinherit
+ ---------+---------+---------+------------+-------------+--------------
+ pt1 | pt1chk1 | c | t | 0 | t
+ pt1 | pt1chk2 | c | t | 0 | f
+ (2 rows)
+
+ -- child does not inherit NO INHERIT constraints
+ CREATE FOREIGN TABLE ct3 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ CREATE FOREIGN TABLE ct4 (ff1 integer)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ -- child must have parent's INHERIT constraints
+ ALTER FOREIGN TABLE ct4 INHERIT pt1; -- ERROR
+ ERROR: child table is missing constraint "pt1chk2"
+ ALTER TABLE ct4 ADD CONSTRAINT pt1chk2 CHECK (ff1 > 10);
+ ALTER FOREIGN TABLE ct4 INHERIT pt1;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+---------+--------------+-------------
+ ff1 | integer | | plain | |
+ Check constraints:
+ "pt1chk1" CHECK (ff1 > 0) NO INHERIT
+ "pt1chk2" CHECK (ff1 > 10)
+ Child tables: ct3,
+ ct4
+
+ \d+ ct3
+ Foreign table "public.ct3"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+---------+--------------+-------------
+ ff1 | integer | | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (ff1 > 10)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ \d+ ct4
+ Foreign table "public.ct4"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+---------+--------------+-------------
+ ff1 | integer | | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (ff1 > 10)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ DROP VIEW view1;
+ DROP FOREIGN TABLE ct1;
+ DROP FOREIGN TABLE ct2;
+ DROP TABLE pt1 CASCADE;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to foreign table ct3
+ drop cascades to foreign table ct4
-- Information schema
SELECT * FROM information_schema.foreign_data_wrappers ORDER BY 1, 2;
foreign_data_wrapper_catalog | foreign_data_wrapper_name | authorization_identifier | library_name | foreign_data_wrapper_language
*** 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 '@');
***************
*** 326,331 **** ALTER FOREIGN TABLE ft1 DROP COLUMN IF EXISTS no_column;
--- 326,332 ----
ALTER FOREIGN TABLE ft1 DROP COLUMN c9;
ALTER FOREIGN TABLE ft1 SET SCHEMA foreign_schema;
ALTER FOREIGN TABLE ft1 SET TABLESPACE ts; -- ERROR
+
ALTER FOREIGN TABLE foreign_schema.ft1 RENAME c1 TO foreign_column_1;
ALTER FOREIGN TABLE foreign_schema.ft1 RENAME TO foreign_table_1;
\d foreign_schema.foreign_table_1
***************
*** 356,361 **** ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 SET SCHEMA foreign_schema;
--- 357,399 ----
ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 RENAME c1 TO foreign_column_1;
ALTER FOREIGN TABLE IF EXISTS doesnt_exist_ft1 RENAME TO foreign_table_1;
+ -- Table inheritance
+ CREATE TABLE pt1 (ff1 integer);
+ CREATE VIEW view1 AS SELECT 1;
+ ALTER TABLE view1 INHERIT pt1; -- ERROR
+ CREATE FOREIGN TABLE ct1 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ CREATE FOREIGN TABLE ct2 (ff1 integer)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ ALTER FOREIGN TABLE ct2 INHERIT pt1;
+ \d+ pt1
+ \d+ ct1
+ \d+ ct2
+
+ ALTER FOREIGN TABLE ct1 NO INHERIT pt1;
+ ALTER FOREIGN TABLE ct2 NO INHERIT pt1;
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk1 CHECK (ff1 > 0) NO INHERIT;
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (ff1 > 10);
+ -- connoinherit should be true for NO INHERIT constraint
+ SELECT pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pgc.connoinherit FROM pg_class AS pc INNER JOIN pg_constraint AS pgc ON (pgc.conrelid = pc.oid) WHERE pc.relname = 'pt1' order by 1,2;
+ -- child does not inherit NO INHERIT constraints
+ CREATE FOREIGN TABLE ct3 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ CREATE FOREIGN TABLE ct4 (ff1 integer)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ -- child must have parent's INHERIT constraints
+ ALTER FOREIGN TABLE ct4 INHERIT pt1; -- ERROR
+ ALTER TABLE ct4 ADD CONSTRAINT pt1chk2 CHECK (ff1 > 10);
+ ALTER FOREIGN TABLE ct4 INHERIT pt1;
+ \d+ pt1
+ \d+ ct3
+ \d+ ct4
+
+ DROP VIEW view1;
+ DROP FOREIGN TABLE ct1;
+ DROP FOREIGN TABLE ct2;
+ DROP TABLE pt1 CASCADE;
+
-- Information schema
SELECT * FROM information_schema.foreign_data_wrappers ORDER BY 1, 2;
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-08-21 09:30 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Noah Misch <noah@leadboat.com>
1 sibling, 0 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-08-21 09:30 UTC (permalink / raw)
To: Noah Misch <noah@leadboat.com>; shigeru.hanada@gmail.com, Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: pgsql-hackers
(2014/07/02 11:23), Noah Misch wrote:
> On Fri, Jun 20, 2014 at 05:04:06PM +0900, Kyotaro HORIGUCHI wrote:
>> Attached is the rebased patch of v11 up to the current master.
> The rest of these review comments are strictly observations; they're not
> requests for you to change the patch, but they may deserve more discussion.
>
> We use the term "child table" in many messages. Should that change to
> something more inclusive, now that the child may be a foreign table? Perhaps
> one of "child relation", plain "child", or "child foreign table"/"child table"
> depending on the actual object? "child relation" is robust technically, but
> it might add more confusion than it removes. Varying the message depending on
> the actual object feels like a waste. Opinions?
IMHO, I think that "child table" would not confusing users terribly.
> LOCK TABLE on the inheritance parent locks child foreign tables, but LOCK
> TABLE fails when given a foreign table directly. That's odd, but I see no
> cause to change it.
I agree wth that.
> The longstanding behavior of CREATE TABLE INHERITS is to reorder local columns
> to match the order found in parents. That is, both of these tables actually
> have columns in the order (a,b):
>
> create table parent (a int, b int);
> create table child (b int, a int) inherits (parent);
>
> Ordinary dump/reload always uses CREATE TABLE INHERITS, thereby changing
> column order in this way. (pg_dump --binary-upgrade uses ALTER TABLE INHERIT
> and some catalog hacks to avoid doing so.) I've never liked that dump/reload
> can change column order, but it's tolerable if you follow the best practice of
> always writing out column lists. The stakes rise for foreign tables, because
> column order is inherently significant to file_fdw and probably to certain
> other non-RDBMS FDWs. If pg_dump changes column order in your file_fdw
> foreign table, the table breaks. I would heartily support making pg_dump
> preserve column order for all inheritance children. That doesn't rise to the
> level of being a blocker for this patch, though.
I agree with that, too. I think it would be better to add docs for now.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-08-22 02:51 Noah Misch <noah@leadboat.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 2 replies; 179+ messages in thread
From: Noah Misch @ 2014-08-22 02:51 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: shigeru.hanada@gmail.com, Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; pgsql-hackers
On Wed, Aug 20, 2014 at 08:11:01PM +0900, Etsuro Fujita wrote:
> (2014/07/02 11:23), Noah Misch wrote:
> >Your chosen ANALYZE behavior is fair, but the messaging from a database-wide
> >ANALYZE VERBOSE needs work:
> >
> >INFO: analyzing "test_foreign_inherit.parent"
> >INFO: "parent": scanned 1 of 1 pages, containing 1 live rows and 0 dead rows; 1 rows in sample, 1 estimated total rows
> >INFO: analyzing "test_foreign_inherit.parent" inheritance tree
> >WARNING: relcache reference leak: relation "child" not closed
> >WARNING: relcache reference leak: relation "tchild" not closed
> >WARNING: relcache reference leak: relation "parent" not closed
> >
> >Please arrange to omit the 'analyzing "tablename" inheritance tree' message,
> >since this ANALYZE actually skipped that task. (The warnings obviously need a
> >fix, too.) I do find it awkward that adding a foreign table to an inheritance
> >tree will make autovacuum stop collecting statistics on that inheritance tree,
> >but I can't think of a better policy.
>
> I think it would be better that this is handled in the same way as
> an inheritance tree that turns out to be a singe table that doesn't
> have any descendants in acquire_inherited_sample_rows(). That would
> still output the message as shown below, but I think that that would
> be more consistent with the existing code. The patch works so.
> (The warnings are also fixed.)
>
> INFO: analyzing "public.parent"
> INFO: "parent": scanned 0 of 0 pages, containing 0 live rows and 0
> dead rows; 0 rows in sample, 0 estimated total rows
> INFO: analyzing "public.parent" inheritance tree
> INFO: analyzing "pg_catalog.pg_authid"
> INFO: "pg_authid": scanned 1 of 1 pages, containing 1 live rows and
> 0 dead rows; 1 rows in sample, 1 estimated total rows
Today's ANALYZE VERBOSE messaging for former inheritance parents (tables with
relhassubclass = true but no pg_inherits.inhparent links) is deceptive, and I
welcome a fix to omit the spurious message. As defects go, this is quite
minor. There's fundamentally no value in collecting inheritance tree
statistics for such a parent, and no PostgreSQL command will do so.
Your proposed behavior for inheritance parents having at least one foreign
table child is more likely to mislead DBAs in practice. An inheritance tree
genuinely exists, and a different ANALYZE command is quite capable of
collecting statistics on that inheritance tree. Messaging should reflect the
difference between ANALYZE invocations that do such work and ANALYZE
invocations that skip it. I'm anticipating a bug report along these lines:
I saw poor estimates involving a child foreign table, so I ran "ANALYZE
VERBOSE", which reported 'INFO: analyzing "public.parent" inheritance
tree'. Estimates remained poor, so I scratched my head for awhile before
noticing that pg_stats didn't report such statistics. I then ran "ANALYZE
VERBOSE parent", saw the same 'INFO: analyzing "public.parent" inheritance
tree', but this time saw relevant rows in pg_stats. The message doesn't
reflect the actual behavior.
I'll sympathize with that complaint. It's a minor point overall, but the code
impact is, I predict, small enough that we may as well get it right. A
credible alternative is to emit a second message indicating that we skipped
the inheritance tree statistics after all, and why we skipped them.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-08-22 03:58 Alvaro Herrera <alvherre@2ndquadrant.com>
parent: Noah Misch <noah@leadboat.com>
1 sibling, 1 reply; 179+ messages in thread
From: Alvaro Herrera @ 2014-08-22 03:58 UTC (permalink / raw)
To: Noah Misch <noah@leadboat.com>; +Cc: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; shigeru.hanada@gmail.com, Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; pgsql-hackers
Noah Misch wrote:
> I'm anticipating a bug report along these lines:
>
> I saw poor estimates involving a child foreign table, so I ran "ANALYZE
> VERBOSE", which reported 'INFO: analyzing "public.parent" inheritance
> tree'. Estimates remained poor, so I scratched my head for awhile before
> noticing that pg_stats didn't report such statistics. I then ran "ANALYZE
> VERBOSE parent", saw the same 'INFO: analyzing "public.parent" inheritance
> tree', but this time saw relevant rows in pg_stats. The message doesn't
> reflect the actual behavior.
>
> I'll sympathize with that complaint.
Agreed on that.
> A credible alternative is to emit a second message indicating that we
> skipped the inheritance tree statistics after all, and why we skipped
> them.
That'd be similar to Xorg emitting messages such as
[ 53.772] (II) intel(0): RandR 1.2 enabled, ignore the following RandR disabled message.
[ 53.800] (--) RandR disabled
I find this very poor.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-08-22 09:38 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Alvaro Herrera <alvherre@2ndquadrant.com>
0 siblings, 0 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-08-22 09:38 UTC (permalink / raw)
To: Alvaro Herrera <alvherre@2ndquadrant.com>; Noah Misch <noah@leadboat.com>; +Cc: shigeru.hanada@gmail.com, Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; pgsql-hackers
(2014/08/22 12:58), Alvaro Herrera wrote:
> Noah Misch wrote:
>
>> I'm anticipating a bug report along these lines:
>>
>> I saw poor estimates involving a child foreign table, so I ran "ANALYZE
>> VERBOSE", which reported 'INFO: analyzing "public.parent" inheritance
>> tree'. Estimates remained poor, so I scratched my head for awhile before
>> noticing that pg_stats didn't report such statistics. I then ran "ANALYZE
>> VERBOSE parent", saw the same 'INFO: analyzing "public.parent" inheritance
>> tree', but this time saw relevant rows in pg_stats. The message doesn't
>> reflect the actual behavior.
>>
>> I'll sympathize with that complaint.
>
> Agreed on that.
I've got the point. Will fix.
Thanks for the comment!
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-08-28 09:00 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Noah Misch <noah@leadboat.com>
1 sibling, 2 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-08-28 09:00 UTC (permalink / raw)
To: Noah Misch <noah@leadboat.com>; +Cc: shigeru.hanada@gmail.com, Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; pgsql-hackers
(2014/08/22 11:51), Noah Misch wrote:
> On Wed, Aug 20, 2014 at 08:11:01PM +0900, Etsuro Fujita wrote:
>> (2014/07/02 11:23), Noah Misch wrote:
>>> Your chosen ANALYZE behavior is fair, but the messaging from a database-wide
>>> ANALYZE VERBOSE needs work:
>>>
>>> INFO: analyzing "test_foreign_inherit.parent"
>>> INFO: "parent": scanned 1 of 1 pages, containing 1 live rows and 0 dead rows; 1 rows in sample, 1 estimated total rows
>>> INFO: analyzing "test_foreign_inherit.parent" inheritance tree
>>> Please arrange to omit the 'analyzing "tablename" inheritance tree' message,
>>> since this ANALYZE actually skipped that task.
>> I think it would be better that this is handled in the same way as
>> an inheritance tree that turns out to be a singe table that doesn't
>> have any descendants in acquire_inherited_sample_rows(). That would
>> still output the message as shown below, but I think that that would
>> be more consistent with the existing code. The patch works so.
> Today's ANALYZE VERBOSE messaging for former inheritance parents (tables with
> relhassubclass = true but no pg_inherits.inhparent links) is deceptive, and I
> welcome a fix to omit the spurious message. As defects go, this is quite
> minor. There's fundamentally no value in collecting inheritance tree
> statistics for such a parent, and no PostgreSQL command will do so.
>
> Your proposed behavior for inheritance parents having at least one foreign
> table child is more likely to mislead DBAs in practice. An inheritance tree
> genuinely exists, and a different ANALYZE command is quite capable of
> collecting statistics on that inheritance tree. Messaging should reflect the
> difference between ANALYZE invocations that do such work and ANALYZE
> invocations that skip it. I'm anticipating a bug report along these lines:
>
> I saw poor estimates involving a child foreign table, so I ran "ANALYZE
> VERBOSE", which reported 'INFO: analyzing "public.parent" inheritance
> tree'. Estimates remained poor, so I scratched my head for awhile before
> noticing that pg_stats didn't report such statistics. I then ran "ANALYZE
> VERBOSE parent", saw the same 'INFO: analyzing "public.parent" inheritance
> tree', but this time saw relevant rows in pg_stats. The message doesn't
> reflect the actual behavior.
>
> I'll sympathize with that complaint. It's a minor point overall, but the code
> impact is, I predict, small enough that we may as well get it right. A
> credible alternative is to emit a second message indicating that we skipped
> the inheritance tree statistics after all, and why we skipped them.
I'd like to address this by emitting the second message as shown below:
INFO: analyzing "public.parent"
INFO: "parent": scanned 0 of 0 pages, containing 0 live rows and 0 dead
rows; 0 rows in sample, 0 estimated total rows
INFO: analyzing "public.parent" inheritance tree
INFO: skipping analyze of "public.parent" inheritance tree --- this
inheritance tree contains foreign tables
Attached is the update version of the patch. Based on the result of
discussions about attr_needed upthread, I've changed the patch so that
create_foreignscan_plan makes reference to reltargetlist, not to
attr_needed. (So, the patch in [1] isn't required, anymore.)
Other changes:
* Revise code/comments/docs a bit
* Add more regression tests
A separate patch (analyze.patch) handles the former case in a similar way.
[1] http://www.postgresql.org/message-id/53F4707C.8030904@lab.ntt.co.jp
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] foreign_inherit-v15.patch (119.9K, ../../53FEEF94.6040101@lab.ntt.co.jp/2-foreign_inherit-v15.patch)
download | inline diff:
*** a/contrib/file_fdw/file_fdw.c
--- b/contrib/file_fdw/file_fdw.c
***************
*** 115,120 **** static void fileGetForeignRelSize(PlannerInfo *root,
--- 115,125 ----
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,
***************
*** 143,149 **** 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,
--- 148,154 ----
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,
***************
*** 161,166 **** file_fdw_handler(PG_FUNCTION_ARGS)
--- 166,172 ----
fdwroutine->GetForeignRelSize = fileGetForeignRelSize;
fdwroutine->GetForeignPaths = fileGetForeignPaths;
+ fdwroutine->ReparameterizeForeignPath = fileReparameterizeForeignPath;
fdwroutine->GetForeignPlan = fileGetForeignPlan;
fdwroutine->ExplainForeignScan = fileExplainForeignScan;
fdwroutine->BeginForeignScan = fileBeginForeignScan;
***************
*** 509,515 **** fileGetForeignPaths(PlannerInfo *root,
(Node *) columns));
/* Estimate costs */
! estimate_costs(root, baserel, fdw_private,
&startup_cost, &total_cost);
/*
--- 515,522 ----
(Node *) columns));
/* Estimate costs */
! estimate_costs(root, baserel,
! fdw_private, NIL,
&startup_cost, &total_cost);
/*
***************
*** 534,539 **** fileGetForeignPaths(PlannerInfo *root,
--- 541,581 ----
}
/*
+ * 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);
+
+ /* Recreate 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
*/
***************
*** 962,973 **** 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;
/*
--- 1004,1016 ----
*/
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;
/*
***************
*** 978,985 **** 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;
}
--- 1021,1031 ----
*/
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/file_fdw/input/file_fdw.source
--- b/contrib/file_fdw/input/file_fdw.source
***************
*** 134,139 **** DELETE FROM agg_csv WHERE a = 100;
--- 134,177 ----
-- but this should be ignored
SELECT * FROM agg_csv FOR UPDATE;
+ -- table inheritance tests
+ CREATE TABLE agg (a int2, b float4);
+ ALTER FOREIGN TABLE agg_text INHERIT agg;
+ ALTER FOREIGN TABLE agg_csv INHERIT agg;
+ INSERT INTO agg
+ SELECT x, 5432.0 FROM generate_series(0,1000) x;
+ SELECT * FROM agg WHERE b < 10.0 ORDER BY a, b;
+ SELECT * FROM ONLY agg WHERE b < 10.0 ORDER BY a, b;
+ SELECT * FROM agg_text;
+ SELECT * FROM agg_csv;
+ -- updates aren't supported
+ UPDATE agg SET a = 1;
+ DELETE FROM agg WHERE a = 100;
+ -- but this should be ignored
+ SELECT * FROM agg WHERE b < 10.0 ORDER BY a, b FOR UPDATE;
+ -- path reparameterization should work
+ CREATE TABLE int_tbl (f1) AS SELECT x FROM generate_series(0,1000) x;
+ ANALYZE int_tbl;
+ ANALYZE agg;
+ ANALYZE agg_text;
+ ANALYZE agg_csv;
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE)
+ SELECT * FROM agg JOIN (SELECT f1 FROM int_tbl LIMIT 1) ss ON a = f1;
+ \t off
+ SELECT * FROM agg JOIN (SELECT f1 FROM int_tbl LIMIT 1) ss ON a = f1;
+ CREATE INDEX agg_idx ON agg (a);
+ ANALYZE agg;
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE)
+ SELECT * FROM agg JOIN (SELECT f1 FROM int_tbl LIMIT 1) ss ON a = f1;
+ \t off
+ SELECT * FROM agg JOIN (SELECT f1 FROM int_tbl LIMIT 1) ss ON a = f1;
+ ALTER FOREIGN TABLE agg_csv NO INHERIT agg;
+ ALTER FOREIGN TABLE agg_text NO INHERIT agg;
+ DROP TABLE int_tbl;
+ DROP TABLE agg;
+
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/file_fdw/output/file_fdw.source
--- b/contrib/file_fdw/output/file_fdw.source
***************
*** 219,224 **** SELECT * FROM agg_csv FOR UPDATE;
--- 219,348 ----
42 | 324.78
(3 rows)
+ -- table inheritance tests
+ CREATE TABLE agg (a int2, b float4);
+ ALTER FOREIGN TABLE agg_text INHERIT agg;
+ ALTER FOREIGN TABLE agg_csv INHERIT agg;
+ INSERT INTO agg
+ SELECT x, 5432.0 FROM generate_series(0,1000) x;
+ SELECT * FROM agg WHERE b < 10.0 ORDER BY a, b;
+ a | b
+ ----+---------
+ 0 | 0.09561
+ 0 | 0.09561
+ 56 | 7.8
+ (3 rows)
+
+ SELECT * FROM ONLY agg WHERE b < 10.0 ORDER BY a, b;
+ a | b
+ ---+---
+ (0 rows)
+
+ SELECT * FROM agg_text;
+ a | b
+ -----+---------
+ 56 | 7.8
+ 100 | 99.097
+ 0 | 0.09561
+ 42 | 324.78
+ (4 rows)
+
+ SELECT * FROM agg_csv;
+ a | b
+ -----+---------
+ 100 | 99.097
+ 0 | 0.09561
+ 42 | 324.78
+ (3 rows)
+
+ -- updates aren't supported
+ UPDATE agg SET a = 1;
+ ERROR: cannot update foreign table "agg_text"
+ DELETE FROM agg WHERE a = 100;
+ ERROR: cannot delete from foreign table "agg_text"
+ -- but this should be ignored
+ SELECT * FROM agg WHERE b < 10.0 ORDER BY a, b FOR UPDATE;
+ a | b
+ ----+---------
+ 0 | 0.09561
+ 0 | 0.09561
+ 56 | 7.8
+ (3 rows)
+
+ -- path reparameterization should work
+ CREATE TABLE int_tbl (f1) AS SELECT x FROM generate_series(0,1000) x;
+ ANALYZE int_tbl;
+ ANALYZE agg;
+ ANALYZE agg_text;
+ ANALYZE agg_csv;
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE)
+ SELECT * FROM agg JOIN (SELECT f1 FROM int_tbl LIMIT 1) ss ON a = f1;
+ Hash Join
+ Output: agg.a, agg.b, int_tbl.f1
+ Hash Cond: (agg.a = int_tbl.f1)
+ -> Append
+ -> Seq Scan on public.agg
+ Output: agg.a, agg.b
+ -> Foreign Scan on public.agg_text
+ Output: agg_text.a, agg_text.b
+ Foreign File: @abs_srcdir@/data/agg.data
+ -> Foreign Scan on public.agg_csv
+ Output: agg_csv.a, agg_csv.b
+ Foreign File: @abs_srcdir@/data/agg.csv
+ -> Hash
+ Output: int_tbl.f1
+ -> Limit
+ Output: int_tbl.f1
+ -> Seq Scan on public.int_tbl
+ Output: int_tbl.f1
+
+ \t off
+ SELECT * FROM agg JOIN (SELECT f1 FROM int_tbl LIMIT 1) ss ON a = f1;
+ a | b | f1
+ ---+---------+----
+ 0 | 5432 | 0
+ 0 | 0.09561 | 0
+ 0 | 0.09561 | 0
+ (3 rows)
+
+ CREATE INDEX agg_idx ON agg (a);
+ ANALYZE agg;
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE)
+ SELECT * FROM agg JOIN (SELECT f1 FROM int_tbl LIMIT 1) ss ON a = f1;
+ Nested Loop
+ Output: agg.a, agg.b, int_tbl.f1
+ -> Limit
+ Output: int_tbl.f1
+ -> Seq Scan on public.int_tbl
+ Output: int_tbl.f1
+ -> Append
+ -> Index Scan using agg_idx on public.agg
+ Output: agg.a, agg.b
+ Index Cond: (agg.a = int_tbl.f1)
+ -> Foreign Scan on public.agg_text
+ Output: agg_text.a, agg_text.b
+ Filter: (int_tbl.f1 = agg_text.a)
+ Foreign File: @abs_srcdir@/data/agg.data
+ -> Foreign Scan on public.agg_csv
+ Output: agg_csv.a, agg_csv.b
+ Filter: (int_tbl.f1 = agg_csv.a)
+ Foreign File: @abs_srcdir@/data/agg.csv
+
+ \t off
+ SELECT * FROM agg JOIN (SELECT f1 FROM int_tbl LIMIT 1) ss ON a = f1;
+ a | b | f1
+ ---+---------+----
+ 0 | 5432 | 0
+ 0 | 0.09561 | 0
+ 0 | 0.09561 | 0
+ (3 rows)
+
+ ALTER FOREIGN TABLE agg_csv NO INHERIT agg;
+ ALTER FOREIGN TABLE agg_text NO INHERIT agg;
+ DROP TABLE int_tbl;
+ DROP TABLE agg;
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 2841,2846 **** NOTICE: NEW: (13,"test triggered !")
--- 2841,3398 ----
(1 row)
-- ===================================================================
+ -- test inheritance features
+ -- ===================================================================
+ CREATE TABLE a (aa TEXT);
+ CREATE TABLE loct (aa TEXT, bb TEXT);
+ CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a)
+ SERVER loopback OPTIONS (table_name 'loct');
+ INSERT INTO a(aa) VALUES('aaa');
+ INSERT INTO a(aa) VALUES('aaaa');
+ INSERT INTO a(aa) VALUES('aaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaaa');
+ INSERT INTO b(aa) VALUES('bbb');
+ INSERT INTO b(aa) VALUES('bbbb');
+ INSERT INTO b(aa) VALUES('bbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbbb');
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | aaa
+ a | aaaa
+ a | aaaaa
+ a | aaaaaa
+ a | aaaaaaa
+ a | aaaaaaaa
+ b | bbb
+ b | bbbb
+ b | bbbbb
+ b | bbbbbb
+ b | bbbbbbb
+ b | bbbbbbbb
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----------+----
+ b | bbb |
+ b | bbbb |
+ b | bbbbb |
+ b | bbbbbb |
+ b | bbbbbbb |
+ b | bbbbbbbb |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | aaa
+ a | aaaa
+ a | aaaaa
+ a | aaaaaa
+ a | aaaaaaa
+ a | aaaaaaaa
+ (6 rows)
+
+ UPDATE a SET aa='zzzzzz' WHERE aa LIKE 'aaa%';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ b | bbb
+ b | bbbb
+ b | bbbbb
+ b | bbbbbb
+ b | bbbbbbb
+ b | bbbbbbbb
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----------+----
+ b | bbb |
+ b | bbbb |
+ b | bbbbb |
+ b | bbbbbb |
+ b | bbbbbbb |
+ b | bbbbbbbb |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ (6 rows)
+
+ UPDATE b SET aa='new';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+-----+----
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ (6 rows)
+
+ UPDATE a SET aa='new';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+-----
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+-----+----
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+-----
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ (6 rows)
+
+ DELETE FROM a;
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----
+ (0 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----+----
+ (0 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----
+ (0 rows)
+
+ DROP TABLE a CASCADE;
+ NOTICE: drop cascades to foreign table b
+ DROP TABLE loct;
+ -- Check SELECT FOR UPDATE/SHARE with an inherited source table
+ create table loct1 (f1 int, f2 int, f3 int);
+ create table loct2 (f1 int, f2 int, f3 int);
+ create table foo (f1 int, f2 int);
+ create foreign table foo2 (f3 int) inherits (foo)
+ server loopback options (table_name 'loct1');
+ create table bar (f1 int, f2 int);
+ create foreign table bar2 (f3 int) inherits (bar)
+ server loopback options (table_name 'loct2');
+ insert into foo values(1,1);
+ insert into foo values(3,3);
+ insert into foo2 values(2,2,2);
+ insert into foo2 values(3,3,3);
+ insert into bar values(1,1);
+ insert into bar values(2,2);
+ insert into bar values(3,3);
+ insert into bar values(4,4);
+ insert into bar2 values(1,1,1);
+ insert into bar2 values(2,2,2);
+ insert into bar2 values(3,3,3);
+ insert into bar2 values(4,4,4);
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for update;
+ QUERY PLAN
+ ----------------------------------------------------------------------------------------------
+ LockRows
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ -> Hash Join
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Append
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.ctid, bar2.tableoid, bar2.*
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (22 rows)
+
+ select * from bar where f1 in (select f1 from foo) for update;
+ f1 | f2
+ ----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ (6 rows)
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for share;
+ QUERY PLAN
+ ----------------------------------------------------------------------------------------------
+ LockRows
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ -> Hash Join
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Append
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.ctid, bar2.tableoid, bar2.*
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (22 rows)
+
+ select * from bar where f1 in (select f1 from foo) for share;
+ f1 | f2
+ ----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ (6 rows)
+
+ -- Check UPDATE with inherited target and an inherited source table
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ QUERY PLAN
+ ---------------------------------------------------------------------------------------------
+ Update on public.bar
+ -> Hash Join
+ Output: bar.f1, (bar.f2 + 100), bar.ctid, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ -> Hash Join
+ Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar2.f1 = foo.f1)
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (34 rows)
+
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+ relname | f1 | f2
+ ---------+----+-----
+ bar | 1 | 101
+ bar | 2 | 102
+ bar | 3 | 103
+ bar | 4 | 4
+ bar2 | 1 | 101
+ bar2 | 2 | 102
+ bar2 | 3 | 103
+ bar2 | 4 | 4
+ (8 rows)
+
+ -- Check UPDATE with inherited target and an appendrel subquery
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ QUERY PLAN
+ --------------------------------------------------------------------------------------
+ Update on public.bar
+ -> Hash Join
+ Output: bar.f1, (bar.f2 + 100), bar.ctid, (ROW(foo.f1))
+ Hash Cond: (foo.f1 = bar.f1)
+ -> Append
+ -> Seq Scan on public.foo
+ Output: ROW(foo.f1), foo.f1
+ -> Foreign Scan on public.foo2
+ Output: ROW(foo2.f1), foo2.f1
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Seq Scan on public.foo foo_1
+ Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3)
+ -> Foreign Scan on public.foo2 foo2_1
+ Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3)
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Hash
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Merge Join
+ Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, (ROW(foo.f1))
+ Merge Cond: (bar2.f1 = foo.f1)
+ -> Sort
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Sort Key: bar2.f1
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Sort
+ Output: (ROW(foo.f1)), foo.f1
+ Sort Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: ROW(foo.f1), foo.f1
+ -> Foreign Scan on public.foo2
+ Output: ROW(foo2.f1), foo2.f1
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Seq Scan on public.foo foo_1
+ Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3)
+ -> Foreign Scan on public.foo2 foo2_1
+ Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3)
+ Remote SQL: SELECT f1 FROM public.loct1
+ (42 rows)
+
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+ relname | f1 | f2
+ ---------+----+-----
+ bar | 1 | 201
+ bar | 2 | 202
+ bar | 3 | 203
+ bar | 4 | 104
+ bar2 | 1 | 201
+ bar2 | 2 | 202
+ bar2 | 3 | 203
+ bar2 | 4 | 104
+ (8 rows)
+
+ drop table foo cascade;
+ NOTICE: drop cascades to foreign table foo2
+ drop table bar cascade;
+ NOTICE: drop cascades to foreign table bar2
+ drop table loct1;
+ drop table loct2;
+ -- Test that WHERE CURRENT OF is not supported
+ create table ltbl (a int, b text);
+ create table ptbl (a int, b text);
+ create table locc () inherits (ptbl);
+ insert into locc values(1, 'foo');
+ create foreign table remc () inherits (ptbl)
+ server loopback options (table_name 'ltbl');
+ insert into remc values(2, 'bar');
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'foo';
+ fetch from c;
+ ?column?
+ ----------
+ 1
+ (1 row)
+
+ update ptbl set b = null where current of c;
+ ERROR: WHERE CURRENT OF is not supported for this table type
+ rollback;
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'bar';
+ fetch from c;
+ ?column?
+ ----------
+ 1
+ (1 row)
+
+ update ptbl set b = null where current of c;
+ ERROR: WHERE CURRENT OF is not supported for this table type
+ rollback;
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ drop table ptbl cascade;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to table locc
+ drop cascades to foreign table remc
+ drop table ltbl;
+ -- Test path reparameterization
+ create table loctbl (id int, x int);
+ create table parent (id int, x int);
+ create table locchild () inherits (parent);
+ create foreign table remchild () inherits (parent)
+ server loopback options (table_name 'loctbl');
+ insert into parent select x, x from generate_series(0,1000) x;
+ insert into locchild select x, x from generate_series(0,1000) x;
+ insert into remchild select x, x from generate_series(0,1000) x;
+ create table inttbl (f1) as select x from generate_series(0,1000) x;
+ analyze inttbl;
+ analyze parent;
+ analyze locchild;
+ analyze remchild;
+ explain (costs off)
+ select * from parent join (select f1 from inttbl limit 1) ss on id = f1;
+ QUERY PLAN
+ --------------------------------------
+ Hash Join
+ Hash Cond: (parent.id = inttbl.f1)
+ -> Append
+ -> Seq Scan on parent
+ -> Seq Scan on locchild
+ -> Foreign Scan on remchild
+ -> Hash
+ -> Limit
+ -> Seq Scan on inttbl
+ (9 rows)
+
+ select * from parent join (select f1 from inttbl limit 1) ss on id = f1;
+ id | x | f1
+ ----+---+----
+ 0 | 0 | 0
+ 0 | 0 | 0
+ 0 | 0 | 0
+ (3 rows)
+
+ create index parent_idx on parent (id);
+ create index locchild_idx on locchild (id);
+ analyze parent;
+ analyze locchild;
+ explain (costs off)
+ select * from parent join (select f1 from inttbl limit 1) ss on id = f1;
+ QUERY PLAN
+ -------------------------------------------------------
+ Nested Loop
+ -> Limit
+ -> Seq Scan on inttbl
+ -> Append
+ -> Index Scan using parent_idx on parent
+ Index Cond: (id = inttbl.f1)
+ -> Index Scan using locchild_idx on locchild
+ Index Cond: (id = inttbl.f1)
+ -> Foreign Scan on remchild
+ (9 rows)
+
+ select * from parent join (select f1 from inttbl limit 1) ss on id = f1;
+ id | x | f1
+ ----+---+----
+ 0 | 0 | 0
+ 0 | 0 | 0
+ 0 | 0 | 0
+ (3 rows)
+
+ drop table parent cascade;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to table locchild
+ drop cascades to foreign table remchild
+ drop table loctbl;
+ drop table inttbl;
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
CREATE SCHEMA import_source;
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 238,243 **** static void postgresGetForeignRelSize(PlannerInfo *root,
--- 238,248 ----
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,
***************
*** 341,346 **** postgres_fdw_handler(PG_FUNCTION_ARGS)
--- 346,352 ----
/* Functions for scanning foreign tables */
routine->GetForeignRelSize = postgresGetForeignRelSize;
routine->GetForeignPaths = postgresGetForeignPaths;
+ routine->ReparameterizeForeignPath = postgresReparameterizeForeignPath;
routine->GetForeignPlan = postgresGetForeignPlan;
routine->BeginForeignScan = postgresBeginForeignScan;
routine->IterateForeignScan = postgresIterateForeignScan;
***************
*** 731,736 **** postgresGetForeignPaths(PlannerInfo *root,
--- 737,784 ----
}
/*
+ * 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;
+
+ /* Recreate 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
*/
***************
*** 823,828 **** postgresGetForeignPlan(PlannerInfo *root,
--- 871,887 ----
{
RowMarkClause *rc = get_parse_rowmark(root->parse, baserel->relid);
+ if (rc == NULL)
+ {
+ PlanRowMark *prm = get_plan_rowmark(root->rowMarks, baserel->relid);
+
+ if (prm)
+ {
+ if (prm->rti != prm->prti)
+ rc = get_parse_rowmark(root->parse, prm->prti);
+ }
+ }
+
if (rc)
{
/*
***************
*** 1777,1787 **** 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;
--- 1836,1843 ----
}
else
{
! Selectivity join_sel;
! QualCost join_cost;
/* Use rows/width estimates made by set_baserel_size_estimates. */
rows = baserel->rows;
***************
*** 1794,1810 **** 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;
--- 1850,1878 ----
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/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 617,622 **** UPDATE rem1 SET f2 = 'testo';
--- 617,800 ----
INSERT INTO rem1(f2) VALUES ('test') RETURNING ctid;
-- ===================================================================
+ -- test inheritance features
+ -- ===================================================================
+
+ CREATE TABLE a (aa TEXT);
+ CREATE TABLE loct (aa TEXT, bb TEXT);
+ CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a)
+ SERVER loopback OPTIONS (table_name 'loct');
+
+ INSERT INTO a(aa) VALUES('aaa');
+ INSERT INTO a(aa) VALUES('aaaa');
+ INSERT INTO a(aa) VALUES('aaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaaa');
+
+ INSERT INTO b(aa) VALUES('bbb');
+ INSERT INTO b(aa) VALUES('bbbb');
+ INSERT INTO b(aa) VALUES('bbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbbb');
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE a SET aa='zzzzzz' WHERE aa LIKE 'aaa%';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE b SET aa='new';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE a SET aa='new';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ DELETE FROM a;
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ DROP TABLE a CASCADE;
+ DROP TABLE loct;
+
+ -- Check SELECT FOR UPDATE/SHARE with an inherited source table
+ create table loct1 (f1 int, f2 int, f3 int);
+ create table loct2 (f1 int, f2 int, f3 int);
+
+ create table foo (f1 int, f2 int);
+ create foreign table foo2 (f3 int) inherits (foo)
+ server loopback options (table_name 'loct1');
+ create table bar (f1 int, f2 int);
+ create foreign table bar2 (f3 int) inherits (bar)
+ server loopback options (table_name 'loct2');
+
+ insert into foo values(1,1);
+ insert into foo values(3,3);
+ insert into foo2 values(2,2,2);
+ insert into foo2 values(3,3,3);
+ insert into bar values(1,1);
+ insert into bar values(2,2);
+ insert into bar values(3,3);
+ insert into bar values(4,4);
+ insert into bar2 values(1,1,1);
+ insert into bar2 values(2,2,2);
+ insert into bar2 values(3,3,3);
+ insert into bar2 values(4,4,4);
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for update;
+ select * from bar where f1 in (select f1 from foo) for update;
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for share;
+ select * from bar where f1 in (select f1 from foo) for share;
+
+ -- Check UPDATE with inherited target and an inherited source table
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+
+ -- Check UPDATE with inherited target and an appendrel subquery
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+
+ drop table foo cascade;
+ drop table bar cascade;
+ drop table loct1;
+ drop table loct2;
+
+ -- Test that WHERE CURRENT OF is not supported
+ create table ltbl (a int, b text);
+
+ create table ptbl (a int, b text);
+ create table locc () inherits (ptbl);
+ insert into locc values(1, 'foo');
+
+ create foreign table remc () inherits (ptbl)
+ server loopback options (table_name 'ltbl');
+ insert into remc values(2, 'bar');
+
+ select * from ptbl;
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'foo';
+ fetch from c;
+ update ptbl set b = null where current of c;
+ rollback;
+
+ select * from ptbl;
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'bar';
+ fetch from c;
+ update ptbl set b = null where current of c;
+ rollback;
+
+ select * from ptbl;
+
+ drop table ptbl cascade;
+ drop table ltbl;
+
+ -- Test path reparameterization
+ create table loctbl (id int, x int);
+
+ create table parent (id int, x int);
+ create table locchild () inherits (parent);
+ create foreign table remchild () inherits (parent)
+ server loopback options (table_name 'loctbl');
+ insert into parent select x, x from generate_series(0,1000) x;
+ insert into locchild select x, x from generate_series(0,1000) x;
+ insert into remchild select x, x from generate_series(0,1000) x;
+
+ create table inttbl (f1) as select x from generate_series(0,1000) x;
+
+ analyze inttbl;
+ analyze parent;
+ analyze locchild;
+ analyze remchild;
+
+ explain (costs off)
+ select * from parent join (select f1 from inttbl limit 1) ss on id = f1;
+ select * from parent join (select f1 from inttbl limit 1) ss on id = f1;
+
+ create index parent_idx on parent (id);
+ create index locchild_idx on locchild (id);
+ analyze parent;
+ analyze locchild;
+
+ explain (costs off)
+ select * from parent join (select f1 from inttbl limit 1) ss on id = f1;
+ select * from parent join (select f1 from inttbl limit 1) ss on id = f1;
+
+ drop table parent cascade;
+ drop table loctbl;
+ drop table inttbl;
+
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
*** a/doc/src/sgml/ddl.sgml
--- b/doc/src/sgml/ddl.sgml
***************
*** 2224,2229 **** VALUES ('Albany', NULL, NULL, 'NY');
--- 2224,2247 ----
further privileges to be granted.
</para>
+ <para>
+ Note that a foreign table can also inherit from more than one parent
+ table (see <xref linkend="ddl-foreign-data">).
+ Like normal tables, table inheritance is typically established when
+ the foreign table is created, using the <literal>INHERITS</> clause of
+ the <xref linkend="sql-createforeigntable"> statement. Alternatively,
+ a foreign table which is already defined in a compatible way can have
+ a new parent relationship added, using the <literal>INHERIT</literal>
+ variant of <xref linkend="sql-alterforeigntable">.
+ Similarly an inheritance link can be removed from a child using the
+ <literal>NO INHERIT</literal> variant of <command>ALTER FOREIGN TABLE</>.
+ <command>CREATE FOREIGN TABLE</command> and
+ <command>ALTER FOREIGN TABLE</command> follow the same rules for
+ duplicate column merging and rejection that apply during
+ <command>CREATE TABLE</command> and <command>ALTER TABLE</command>,
+ respectively.
+ </para>
+
<sect2 id="ddl-inherit-caveats">
<title>Caveats</title>
***************
*** 2371,2377 **** VALUES ('Albany', NULL, NULL, 'NY');
table of a single parent table. The parent table itself is normally
empty; it exists just to represent the entire data set. You should be
familiar with inheritance (see <xref linkend="ddl-inherit">) before
! attempting to set up partitioning.
</para>
<para>
--- 2389,2398 ----
table of a single parent table. The parent table itself is normally
empty; it exists just to represent the entire data set. You should be
familiar with inheritance (see <xref linkend="ddl-inherit">) before
! attempting to set up partitioning. (The setup and management of
! partitioned tables illustrated in this section assume that each
! partition is a normal table. However, you can do that in a similar way
! for cases where some or all partitions are foreign tables.)
</para>
<para>
***************
*** 2434,2441 **** VALUES ('Albany', NULL, NULL, 'NY');
</para>
<para>
! We will refer to the child tables as partitions, though they
! are in every way normal <productname>PostgreSQL</> tables.
</para>
</listitem>
--- 2455,2462 ----
</para>
<para>
! We will refer to the child tables as partitions, though we assume
! that they are normal <productname>PostgreSQL</> tables.
</para>
</listitem>
*** a/doc/src/sgml/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
***************
*** 162,167 **** GetForeignPaths (PlannerInfo *root,
--- 162,198 ----
<para>
<programlisting>
+ ForeignPath *
+ ReparameterizeForeignPath (PlannerInfo *root,
+ RelOptInfo *baserel,
+ Oid foreigntableid,
+ ForeignPath *path,
+ Relids required_outer);
+ </programlisting>
+
+ Create an access path for a scan on a foreign table using join
+ clauses, which is called a <quote>parameterized path</>.
+ This is called during query planning.
+ The parameters are as for <function>GetForeignRelSize</>, plus
+ the <structname>ForeignPath</> (previously produced by
+ <function>GetForeignPaths</>), and the IDs of all other tables
+ that provide the join clauses.
+ </para>
+
+ <para>
+ 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.
+ </para>
+
+ <para>
+ See <xref linkend="fdw-planning"> for additional information.
+ </para>
+
+ <para>
+ <programlisting>
ForeignScan *
GetForeignPlan (PlannerInfo *root,
RelOptInfo *baserel,
***************
*** 868,877 **** GetForeignServerByName(const char *name, bool missing_ok);
<para>
The FDW callback functions <function>GetForeignRelSize</>,
! <function>GetForeignPaths</>, <function>GetForeignPlan</>, and
! <function>PlanForeignModify</> must fit into the workings of the
! <productname>PostgreSQL</> planner. Here are some notes about what
! they must do.
</para>
<para>
--- 899,908 ----
<para>
The FDW callback functions <function>GetForeignRelSize</>,
! <function>GetForeignPaths</>, <function>ReparameterizeForeignPath</>,
! <function>GetForeignPlan</>, and <function>PlanForeignModify</> must fit
! into the workings of the <productname>PostgreSQL</> planner. Here are
! some notes about what they must do.
</para>
<para>
***************
*** 901,914 **** GetForeignServerByName(const char *name, bool missing_ok);
to initialize it to NULL when the <literal>baserel</> node is created.
It is useful for passing information forward from
<function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
! <function>GetForeignPaths</> to <function>GetForeignPlan</>, thereby
! avoiding recalculation.
</para>
<para>
! <function>GetForeignPaths</> can identify the meaning of different
! access paths by storing private information in the
! <structfield>fdw_private</> field of <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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
--- 932,948 ----
to initialize it to NULL when the <literal>baserel</> node is created.
It is useful for passing information forward from
<function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
! <function>GetForeignPaths</> to <function>ReparameterizeForeignPath</>
! and/or
! <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
! to <function>GetForeignPlan</>, thereby avoiding recalculation.
</para>
<para>
! <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
! can identify the meaning of different access paths by storing private
! information in the <structfield>fdw_private</> field of
! <structname>ForeignPath</> nodes.
<structfield>fdw_private</> is declared as a <type>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
***************
*** 951,960 **** GetForeignServerByName(const char *name, bool missing_ok);
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
! clause should happen during <function>GetForeignPaths</>, since it would
! affect the cost estimate for the path. The path's
! <structfield>fdw_private</> field would probably include a pointer to
! the identified clause's <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from <literal>scan_clauses</>,
but add the <replaceable>sub_expression</> to <structfield>fdw_exprs</>
to ensure that it gets massaged into executable form. It would probably
--- 985,995 ----
<replaceable>sub_expression</>, which it determines can be executed on
the remote server given the locally-evaluated value of the
<replaceable>sub_expression</>. The actual identification of such a
! clause should happen during <function>GetForeignPaths</> or
! <function>ReparameterizeForeignPath</>, since it would affect the cost
! estimate for the path. The path's <structfield>fdw_private</> field
! would probably include a pointer to the identified clause's
! <structname>RestrictInfo</> node. Then
<function>GetForeignPlan</> would remove that clause from <literal>scan_clauses</>,
but add the <replaceable>sub_expression</> to <structfield>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,51 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 42,55 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ ADD <replaceable class="PARAMETER">table_constraint</replaceable>
+ DROP CONSTRAINT [ IF EXISTS ] <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
DISABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
ENABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
ENABLE REPLICA TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
ENABLE ALWAYS TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 153,158 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 157,186 ----
</varlistentry>
<varlistentry>
+ <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds a new constraint to a table using the same syntax as
+ <xref linkend="SQL-CREATEFOREIGNTABLE">.
+ 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.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DROP CONSTRAINT [ IF EXISTS ]</literal></term>
+ <listitem>
+ <para>
+ This form drops the specified constraint on a table.
+ If <literal>IF EXISTS</literal> is specified and the constraint
+ does not exist, no error is thrown. In this case a notice is issued instead.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>DISABLE</literal>/<literal>ENABLE [ REPLICA | ALWAYS ] TRIGGER</literal></term>
<listitem>
<para>
***************
*** 164,169 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 192,217 ----
</varlistentry>
<varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OWNER</literal></term>
<listitem>
<para>
***************
*** 285,290 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 333,356 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">table_constraint</replaceable></term>
+ <listitem>
+ <para>
+ New table constraint for the table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">constraint_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing constraint to drop.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>CASCADE</literal></term>
<listitem>
<para>
***************
*** 336,341 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 402,417 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">new_owner</replaceable></term>
<listitem>
<para>
***************
*** 365,374 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint is
! added, or a column type is changed with <literal>SET DATA TYPE</>. It is
! the user's responsibility to ensure that the table definition matches the
! remote side.
</para>
<para>
--- 441,450 ----
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint or
! <literal>CHECK</> constraint is added, or a column type is changed with
! <literal>SET DATA TYPE</>. It is the user's responsibility to ensure that
! the table definition matches the remote side.
</para>
<para>
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 993,998 **** ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
--- 993,1005 ----
</para>
<para>
+ If a table has any descendant tables that are foreign, a recursive
+ <literal>SET STORAGE</literal> operation will be rejected since it
+ is not permitted to add an <literal>oid</literal> system column to
+ foreign tables.
+ </para>
+
+ <para>
The <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>,
and <literal>TABLESPACE</> actions never recurse to descendant tables;
that is, they always act as though <literal>ONLY</> were specified.
***************
*** 1001,1006 **** ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
--- 1008,1026 ----
</para>
<para>
+ When adding a <literal>CHECK</> constraint with the <literal>NOT VALID
+ </literal> option recursively, an inherited constraint on a descendant
+ table that is foreign will be marked valid without checking
+ consistency with the foreign server.
+ </para>
+
+ <para>
+ A recursive <literal>SET STORAGE</literal> operation will make the
+ storage mode for a descendant table's column unchanged if the table is
+ foreign.
+ </para>
+
+ <para>
Changing any part of a system catalog table is not permitted.
</para>
*** a/doc/src/sgml/ref/analyze.sgml
--- b/doc/src/sgml/ref/analyze.sgml
***************
*** 200,205 **** ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
--- 200,212 ----
</para>
<para>
+ 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
+ <command>ANALYZE</command>, the command prints a warning and does nothing.
+ </para>
+
+ <para>
If the table being analyzed is completely empty, <command>ANALYZE</command>
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 ****
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
--- 19,29 ----
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
! | <replaceable>table_constraint</replaceable> }
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 30,36 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 32,44 ----
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ NOT NULL |
NULL |
+ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) |
DEFAULT <replaceable>default_expr</replaceable> }
+
+ <phrase>and <replaceable class="PARAMETER">table_constraint</replaceable> is:</phrase>
+
+ [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
+ { CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) }
</synopsis>
</refsynopsisdiv>
***************
*** 138,143 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 146,172 ----
</varlistentry>
<varlistentry>
+ <term><literal>CHECK ( <replaceable class="PARAMETER">expression</replaceable> )</literal></term>
+ <listitem>
+ <para>
+ The <literal>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.
+ </para>
+
+ <para>
+ Currently, <literal>CHECK</literal> expressions cannot contain
+ subqueries nor refer to variables other than columns of the
+ current row. The system column <literal>tableoid</literal>
+ may be referenced, but not any other system column.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>DEFAULT
<replaceable>default_expr</replaceable></literal></term>
<listitem>
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 188,205 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for the
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
***************
*** 187,192 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 228,251 ----
</refsect1>
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ Constraints on foreign tables are not enforced on insert or update.
+ Those definitions simply declare the constraints hold for all rows
+ in the foreign tables. It is the user's responsibility to ensure
+ that those definitions match the remote side. Such constraints are
+ used for some kind of query optimization such as constraint exclusion
+ for partitioned tables (see <xref linkend="ddl-partitioning">).
+ </para>
+
+ <para>
+ Since it is not permitted to add an <literal>oid</> system column to
+ foreign tables, the command will be rejected if any parent tables
+ have <literal>oid</> system columns.
+ </para>
+ </refsect1>
<refsect1 id="SQL-CREATEFOREIGNTABLE-examples">
<title>Examples</title>
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 2219,2224 **** AddRelationNewConstraints(Relation rel,
--- 2219,2230 ----
if (cdef->contype != CONSTR_CHECK)
continue;
+ if (cdef->is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("CHECK constraints on foreign tables cannot be marked NO INHERIT")));
+
if (cdef->raw_expr != NULL)
{
Assert(cdef->cooked_expr == NULL);
*** 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;
***************
*** 102,107 **** static int acquire_sample_rows(Relation onerel, int elevel,
--- 103,109 ----
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
static int compare_rows(const void *a, const void *b);
+ static bool has_foreign(Oid parentrelId, List *tableOIDs);
static int acquire_inherited_sample_rows(Relation onerel, int elevel,
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
***************
*** 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;
--- 117,126 ----
* 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)
--- 134,140 ----
elevel = DEBUG2;
/* Set up static variables */
+ vac_mode = vacmode;
vac_strategy = bstrategy;
/*
***************
*** 294,302 **** analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
* do_analyze_rel() -- analyze one relation, recursively or not
*
* Note that "acquirefunc" is only relevant for the non-inherited case.
! * If we supported foreign tables in inheritance trees,
! * acquire_inherited_sample_rows would need to determine the appropriate
! * acquirefunc for each child table.
*/
static void
do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
--- 300,307 ----
* do_analyze_rel() -- analyze one relation, recursively or not
*
* Note that "acquirefunc" is only relevant for the non-inherited case.
! * For the inherited case, acquire_inherited_sample_rows determines the
! * appropriate acquirefunc for each child table.
*/
static void
do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
***************
*** 1439,1449 **** compare_rows(const void *a, const void *b)
/*
* acquire_inherited_sample_rows -- acquire sample rows from inheritance tree
*
* This has the same API as acquire_sample_rows, except that rows are
* collected from all inheritance children as well as the specified table.
! * We fail and return zero if there are no inheritance children.
*/
static int
acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1444,1489 ----
/*
+ * Check wether to contain at least one foreign table
+ */
+ static bool
+ has_foreign(Oid parentrelId, List *tableOIDs)
+ {
+ bool result = false;
+ ListCell *lc;
+
+ /* 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, NoLock);
+ }
+ return result;
+ }
+
+ /*
* acquire_inherited_sample_rows -- acquire sample rows from inheritance tree
*
* This has the same API as acquire_sample_rows, except that rows are
* collected from all inheritance children as well as the specified table.
! * We fail and return zero if there are no inheritance children or there are
! * inheritance children that foreign tables.
*/
static int
acquire_inherited_sample_rows(Relation onerel, int elevel,
***************
*** 1452,1457 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1492,1498 ----
{
List *tableOIDs;
Relation *rels;
+ AcquireSampleRowsFunc *acquirefunc;
double *relblocks;
double totalblocks;
int numrows,
***************
*** 1482,1491 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1523,1550 ----
}
/*
+ * If we are not in VAC_MODE_SINGLE case and the inheritance set contains
+ * at least one foreign table, then fail.
+ */
+ if (vac_mode != VAC_MODE_SINGLE)
+ {
+ if (has_foreign(RelationGetRelid(onerel), tableOIDs))
+ {
+ ereport(elevel,
+ (errmsg("skipping analyze of \"%s.%s\" inheritance tree --- this inheritance tree contains foreign tables",
+ get_namespace_name(RelationGetNamespace(onerel)),
+ RelationGetRelationName(onerel))));
+ return 0;
+ }
+ }
+
+ /*
* Count the blocks in all the relations. The result could overflow
* 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++;
}
--- 1566,1601 ----
}
rels[nrels] = childrel;
!
! if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
! {
! FdwRoutine *fdwroutine;
! BlockNumber relpages = 0;
! bool ok = false;
!
! /* 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,
--- 1613,1619 ----
{
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 &&
--- 1629,1640 ----
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
***************
*** 312,318 **** 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,
--- 312,319 ----
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,
***************
*** 472,481 **** 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,
--- 473,478 ----
***************
*** 565,570 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
--- 562,591 ----
stmt->relation->relpersistence,
&inheritOids, &old_constraints, &parentOidCount);
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * Don't allow a foreign table to inherit from parents that have OID
+ * system columns.
+ */
+ if (parentOidCount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot inherit from relation with OIDs")));
+
+ /*
+ * Reset the storage parameter for inherited attributes that have
+ * non-default values.
+ */
+ foreach(listptr, schema)
+ {
+ ColumnDef *colDef = lfirst(listptr);
+
+ if (colDef->storage != 0)
+ colDef->storage = 0;
+ }
+ }
+
/*
* Create a tuple descriptor from the relation schema. Note that this
* deals with column names, types, and NOT NULL constraints, but not
***************
*** 3078,3101 **** 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;
--- 3099,3126 ----
* 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;
***************
*** 3108,3114 **** 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;
--- 3133,3140 ----
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;
***************
*** 3126,3132 **** 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)
--- 3152,3158 ----
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)
***************
*** 3140,3146 **** 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)
--- 3166,3172 ----
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)
***************
*** 3230,3240 **** 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;
--- 3256,3272 ----
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);
pass = AT_PASS_MISC;
***************
*** 3268,3274 **** 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);
--- 3300,3305 ----
***************
*** 4254,4260 **** 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
--- 4285,4292 ----
*/
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
***************
*** 4282,4289 **** 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);
}
}
--- 4314,4325 ----
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);
}
}
***************
*** 4573,4579 **** 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);
--- 4609,4615 ----
/* 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);
***************
*** 4869,4874 **** ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4905,4915 ----
/* 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 */
***************
*** 5469,5475 **** 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
--- 5510,5516 ----
/* 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
***************
*** 5861,5867 **** 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
--- 5902,5915 ----
/* 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
***************
*** 5872,5880 **** 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 */
--- 5920,5936 ----
* 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 */
***************
*** 7361,7367 **** 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);
--- 7417,7423 ----
/* 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);
***************
*** 7696,7702 **** 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,
--- 7752,7761 ----
* 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;
***************
*** 149,154 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
--- 150,169 ----
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.
*/
***************
*** 251,257 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
PushActiveSnapshot(GetTransactionSnapshot());
}
! analyze_rel(relid, vacstmt, vac_strategy);
if (use_own_xacts)
{
--- 266,272 ----
PushActiveSnapshot(GetTransactionSnapshot());
}
! analyze_rel(relid, vacstmt, vac_mode, vac_strategy);
if (use_own_xacts)
{
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 2285,2290 **** EvalPlanQualFetchRowMarks(EPQState *epqstate)
--- 2285,2310 ----
Assert(erm->markType == ROW_MARK_COPY);
+ /* if child rel, must check whether it produced this row */
+ if (erm->rti != erm->prti)
+ {
+ Oid tableoid;
+
+ datum = ExecGetJunkAttribute(epqstate->origslot,
+ aerm->toidAttNo,
+ &isNull);
+ /* non-locked rels could be on the inside of outer joins */
+ if (isNull)
+ continue;
+ tableoid = DatumGetObjectId(datum);
+
+ if (tableoid != RelationGetRelid(erm->relation))
+ {
+ /* this child is inactive right now */
+ continue;
+ }
+ }
+
/* fetch the whole-row Var for the relation */
datum = ExecGetJunkAttribute(epqstate->origslot,
aerm->wholeAttNo,
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 1994,1999 **** _copyRangeTblEntry(const RangeTblEntry *from)
--- 1994,2000 ----
COPY_NODE_FIELD(eref);
COPY_SCALAR_FIELD(lateral);
COPY_SCALAR_FIELD(inh);
+ COPY_SCALAR_FIELD(hasForeign);
COPY_SCALAR_FIELD(inFromCl);
COPY_SCALAR_FIELD(requiredPerms);
COPY_SCALAR_FIELD(checkAsUser);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 2315,2320 **** _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
--- 2315,2321 ----
COMPARE_NODE_FIELD(eref);
COMPARE_SCALAR_FIELD(lateral);
COMPARE_SCALAR_FIELD(inh);
+ COMPARE_SCALAR_FIELD(hasForeign);
COMPARE_SCALAR_FIELD(inFromCl);
COMPARE_SCALAR_FIELD(requiredPerms);
COMPARE_SCALAR_FIELD(checkAsUser);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 2419,2424 **** _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
--- 2419,2425 ----
WRITE_BOOL_FIELD(lateral);
WRITE_BOOL_FIELD(inh);
+ WRITE_BOOL_FIELD(hasForeign);
WRITE_BOOL_FIELD(inFromCl);
WRITE_UINT_FIELD(requiredPerms);
WRITE_OID_FIELD(checkAsUser);
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 1248,1253 **** _readRangeTblEntry(void)
--- 1248,1254 ----
READ_BOOL_FIELD(lateral);
READ_BOOL_FIELD(inh);
+ READ_BOOL_FIELD(hasForeign);
READ_BOOL_FIELD(inFromCl);
READ_UINT_FIELD(requiredPerms);
READ_OID_FIELD(checkAsUser);
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 1945,1950 **** create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
--- 1945,1952 ----
RelOptInfo *rel = best_path->path.parent;
Index scan_relid = rel->relid;
RangeTblEntry *rte;
+ Bitmapset *attrs_used = NULL;
+ ListCell *lc;
int i;
/* it should be a base rel... */
***************
*** 1993,2008 **** create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
* bit of a kluge and might go away someday, so we intentionally leave it
* out of the API presented to FDWs.
*/
scan_plan->fsSystemCol = false;
for (i = rel->min_attr; i < 0; i++)
{
! if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
{
scan_plan->fsSystemCol = true;
break;
}
}
return scan_plan;
}
--- 1995,2029 ----
* bit of a kluge and might go away someday, so we intentionally leave it
* out of the API presented to FDWs.
*/
+
+ /*
+ * Add all the attributes needed for joins or final output. Note: we must
+ * look at reltargetlist, not the attr_needed data, because attr_needed
+ * isn't computed for inheritance child rels.
+ */
+ pull_varattnos((Node *) rel->reltargetlist, rel->relid, &attrs_used);
+
+ /* Add all the attributes used by restriction clauses. */
+ foreach(lc, rel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ pull_varattnos((Node *) rinfo->clause, rel->relid, &attrs_used);
+ }
+
+ /* Is any system column requested? */
scan_plan->fsSystemCol = false;
for (i = rel->min_attr; i < 0; i++)
{
! if (bms_is_member(i - rel->min_attr, attrs_used))
{
scan_plan->fsSystemCol = true;
break;
}
}
+ bms_free(attrs_used);
+
return scan_plan;
}
*** a/src/backend/optimizer/prep/preptlist.c
--- b/src/backend/optimizer/prep/preptlist.c
***************
*** 111,116 **** preprocess_targetlist(PlannerInfo *root, List *tlist)
--- 111,118 ----
/* if parent of inheritance tree, need the tableoid too */
if (rc->isParent)
{
+ RangeTblEntry *rte = rt_fetch(rc->rti, parse->rtable);
+
var = makeVar(rc->rti,
TableOidAttributeNumber,
OIDOID,
***************
*** 123,128 **** preprocess_targetlist(PlannerInfo *root, List *tlist)
--- 125,146 ----
pstrdup(resname),
true);
tlist = lappend(tlist, tle);
+
+ /* if including foreign tables, fetch the whole row too */
+ if (rte->hasForeign)
+ {
+ /* Not a table, so we need the whole row as a junk var */
+ var = makeWholeRowVar(rt_fetch(rc->rti, range_table),
+ rc->rti,
+ 0,
+ false);
+ snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId);
+ tle = makeTargetEntry((Expr *) var,
+ list_length(tlist) + 1,
+ pstrdup(resname),
+ true);
+ tlist = lappend(tlist, tle);
+ }
}
}
else
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 1237,1242 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1237,1243 ----
LOCKMODE lockmode;
List *inhOIDs;
List *appinfos;
+ bool hasForeign;
ListCell *l;
/* Does RT entry allow inheritance? */
***************
*** 1309,1314 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1310,1316 ----
/* Scan the inheritance set and expand it */
appinfos = NIL;
+ hasForeign = false;
foreach(l, inhOIDs)
{
Oid childOID = lfirst_oid(l);
***************
*** 1338,1348 **** 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);
--- 1340,1351 ----
/*
* 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);
***************
*** 1388,1400 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
newrc->rti = childRTindex;
newrc->prti = rti;
newrc->rowmarkId = oldrc->rowmarkId;
! newrc->markType = oldrc->markType;
newrc->noWait = oldrc->noWait;
newrc->isParent = false;
root->rowMarks = lappend(root->rowMarks, newrc);
}
/* Close child relations, but keep locks */
if (childOID != parentOID)
heap_close(newrelation, NoLock);
--- 1391,1409 ----
newrc->rti = childRTindex;
newrc->prti = rti;
newrc->rowmarkId = oldrc->rowmarkId;
! if (childrte->relkind == RELKIND_FOREIGN_TABLE)
! newrc->markType = ROW_MARK_COPY;
! else
! newrc->markType = oldrc->markType;
newrc->noWait = oldrc->noWait;
newrc->isParent = false;
root->rowMarks = lappend(root->rowMarks, newrc);
}
+ if (childrte->relkind == RELKIND_FOREIGN_TABLE)
+ hasForeign = true;
+
/* Close child relations, but keep locks */
if (childOID != parentOID)
heap_close(newrelation, NoLock);
***************
*** 1416,1421 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1425,1434 ----
/* Otherwise, OK to add to root->append_rel_list */
root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+
+ /* And mark the parent table as having children that are foreign, if so */
+ if (hasForeign)
+ rte->hasForeign = true;
}
/*
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
***************
*** 16,21 ****
--- 16,22 ----
#include <math.h>
+ #include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
***************
*** 1921,1926 **** reparameterize_path(PlannerInfo *root, Path *path,
--- 1922,1952 ----
case T_SubqueryScan:
return create_subqueryscan_path(root, rel, path->pathkeys,
required_outer);
+ case T_ForeignScan:
+ {
+ ForeignPath *newpath = NULL;
+
+ /* Let the FDW reparameterize the path node if possible */
+ if (rel->fdwroutine->ReparameterizeForeignPath != 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);
+
+ newpath =
+ rel->fdwroutine->ReparameterizeForeignPath(root,
+ rel,
+ rte->relid,
+ (ForeignPath *) path,
+ required_outer);
+ }
+ return (Path *) newpath;
+ }
default:
break;
}
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 4316,4347 **** 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;
}
;
--- 4316,4347 ----
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,191 ----
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,
***************
*** 120,125 **** typedef struct FdwRoutine
--- 126,132 ----
/* 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/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 810,815 **** typedef struct RangeTblEntry
--- 810,816 ----
Alias *eref; /* expanded reference names */
bool lateral; /* subquery, function, or values is LATERAL? */
bool inh; /* inheritance requested? */
+ bool hasForeign; /* does inheritance include foreign tables? */
bool inFromCl; /* present in FROM clause? */
AclMode requiredPerms; /* bitmask of required access permissions */
Oid checkAsUser; /* if valid, check access as this role */
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 815,820 **** typedef enum RowMarkType
--- 815,822 ----
* The tableoid column is only present for an inheritance hierarchy.
* When markType == ROW_MARK_COPY, there is instead a single column named
* wholerow%u whole-row value of relation
+ * The wholerow column is also present for an inheritance hierarchy
+ * in cases where the inheritance hierarchy contains foreign tables.
* In all three cases, %u represents the rowmark ID number (rowmarkId).
* This number is unique within a plan tree, except that child relation
* entries copy their parent's rowmarkId. (Assigning unique numbers
*** a/src/test/regress/expected/foreign_data.out
--- b/src/test/regress/expected/foreign_data.out
***************
*** 748,763 **** 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;
--- 748,759 ----
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;
***************
*** 1193,1198 **** DROP TRIGGER trigtest_before_row ON foreign_schema.foreign_table_1;
--- 1189,1379 ----
DROP TRIGGER trigtest_after_stmt ON foreign_schema.foreign_table_1;
DROP TRIGGER trigtest_after_row ON foreign_schema.foreign_table_1;
DROP FUNCTION dummy_trigger();
+ -- Table inheritance
+ CREATE TABLE pt1 (ff1 integer, ff2 text);
+ CREATE VIEW view1 AS SELECT 1;
+ ALTER TABLE view1 INHERIT pt1; -- ERROR
+ ERROR: "view1" is not a table or foreign table
+ CREATE FOREIGN TABLE ct1 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ CREATE FOREIGN TABLE ct2 (ff1 integer, ff2 text)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ ALTER FOREIGN TABLE ct1 NO INHERIT pt1;
+ ALTER FOREIGN TABLE ct1 INHERIT pt1;
+ ALTER FOREIGN TABLE ct2 INHERIT pt1;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ ff1 | integer | | plain | |
+ ff2 | text | | extended | |
+ Child tables: ct1,
+ ct2
+
+ \d+ ct1
+ Foreign table "public.ct1"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ \d+ ct2
+ Foreign table "public.ct2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ DROP FOREIGN TABLE ct1;
+ -- cannot change storage modes for foreign tables
+ ALTER TABLE pt1 ALTER COLUMN ff2 SET STORAGE EXTERNAL;
+ CREATE FOREIGN TABLE ct1 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ ff1 | integer | | plain | |
+ ff2 | text | | external | |
+ Child tables: ct1,
+ ct2
+
+ \d+ ct1
+ Foreign table "public.ct1"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ \d+ ct2
+ Foreign table "public.ct2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ DROP FOREIGN TABLE ct1;
+ DROP FOREIGN TABLE ct2;
+ ALTER TABLE pt1 ALTER COLUMN ff2 SET STORAGE EXTENDED;
+ -- connoinherit should be true for NO INHERIT constraint
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk1 CHECK (ff1 > 0) NO INHERIT;
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (ff1 > 10);
+ SELECT pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pgc.connoinherit FROM pg_class AS pc INNER JOIN pg_constraint AS pgc ON (pgc.conrelid = pc.oid) WHERE pc.relname = 'pt1' order by 1,2;
+ relname | conname | contype | conislocal | coninhcount | connoinherit
+ ---------+---------+---------+------------+-------------+--------------
+ pt1 | pt1chk1 | c | t | 0 | t
+ pt1 | pt1chk2 | c | t | 0 | f
+ (2 rows)
+
+ -- child does not inherit NO INHERIT constraints
+ CREATE FOREIGN TABLE ct1 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ CREATE FOREIGN TABLE ct2 (ff1 integer, ff2 text)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ -- child must have parent's INHERIT constraints
+ ALTER FOREIGN TABLE ct2 INHERIT pt1; -- ERROR
+ ERROR: child table is missing constraint "pt1chk2"
+ ALTER FOREIGN TABLE ct2 ADD CONSTRAINT pt1chk2 CHECK (ff1 > 10);
+ ALTER FOREIGN TABLE ct2 INHERIT pt1;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ ff1 | integer | | plain | |
+ ff2 | text | | extended | |
+ Check constraints:
+ "pt1chk1" CHECK (ff1 > 0) NO INHERIT
+ "pt1chk2" CHECK (ff1 > 10)
+ Child tables: ct1,
+ ct2
+
+ \d+ ct1
+ Foreign table "public.ct1"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Check constraints:
+ "pt1chk2" CHECK (ff1 > 10)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ \d+ ct2
+ Foreign table "public.ct2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Check constraints:
+ "pt1chk2" CHECK (ff1 > 10)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk2 CASCADE;
+ -- NOT VALID should be ignored for foreign tables
+ INSERT INTO pt1 VALUES (100, 'pt1');
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (ff1 > 10) NOT VALID;
+ NOTICE: merging constraint "pt1chk2" with inherited definition
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ ff1 | integer | | plain | |
+ ff2 | text | | extended | |
+ Check constraints:
+ "pt1chk1" CHECK (ff1 > 0) NO INHERIT
+ "pt1chk2" CHECK (ff1 > 10) NOT VALID
+ Child tables: ct1,
+ ct2
+
+ \d+ ct1
+ Foreign table "public.ct1"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Check constraints:
+ "pt1chk2" CHECK (ff1 > 10)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ \d+ ct2
+ Foreign table "public.ct2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Check constraints:
+ "pt1chk2" CHECK (ff1 > 10)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- VALIDATE CONSTRAINT should work by ignoring foreign tables
+ ALTER TABLE pt1 VALIDATE CONSTRAINT pt1chk2;
+ -- cannot add an OID system column
+ ALTER TABLE pt1 SET WITH OIDS; -- ERROR
+ ERROR: cannot add OID column to foreign table "ct1"
+ DROP VIEW view1;
+ DROP TABLE pt1 CASCADE;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to foreign table ct1
+ drop cascades to foreign table ct2
-- IMPORT FOREIGN SCHEMA
IMPORT FOREIGN SCHEMA s1 FROM SERVER s9 INTO public; -- ERROR
ERROR: foreign-data wrapper "foo" has no handler
*** 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 '@');
***************
*** 326,331 **** ALTER FOREIGN TABLE ft1 DROP COLUMN IF EXISTS no_column;
--- 326,332 ----
ALTER FOREIGN TABLE ft1 DROP COLUMN c9;
ALTER FOREIGN TABLE ft1 SET SCHEMA foreign_schema;
ALTER FOREIGN TABLE ft1 SET TABLESPACE ts; -- ERROR
+
ALTER FOREIGN TABLE foreign_schema.ft1 RENAME c1 TO foreign_column_1;
ALTER FOREIGN TABLE foreign_schema.ft1 RENAME TO foreign_table_1;
\d foreign_schema.foreign_table_1
***************
*** 514,519 **** DROP TRIGGER trigtest_after_row ON foreign_schema.foreign_table_1;
--- 515,583 ----
DROP FUNCTION dummy_trigger();
+ -- Table inheritance
+ CREATE TABLE pt1 (ff1 integer, ff2 text);
+ CREATE VIEW view1 AS SELECT 1;
+ ALTER TABLE view1 INHERIT pt1; -- ERROR
+ CREATE FOREIGN TABLE ct1 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ CREATE FOREIGN TABLE ct2 (ff1 integer, ff2 text)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ ALTER FOREIGN TABLE ct1 NO INHERIT pt1;
+ ALTER FOREIGN TABLE ct1 INHERIT pt1;
+ ALTER FOREIGN TABLE ct2 INHERIT pt1;
+ \d+ pt1
+ \d+ ct1
+ \d+ ct2
+
+ DROP FOREIGN TABLE ct1;
+
+ -- cannot change storage modes for foreign tables
+ ALTER TABLE pt1 ALTER COLUMN ff2 SET STORAGE EXTERNAL;
+ CREATE FOREIGN TABLE ct1 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ \d+ ct1
+ \d+ ct2
+
+ DROP FOREIGN TABLE ct1;
+ DROP FOREIGN TABLE ct2;
+ ALTER TABLE pt1 ALTER COLUMN ff2 SET STORAGE EXTENDED;
+
+ -- connoinherit should be true for NO INHERIT constraint
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk1 CHECK (ff1 > 0) NO INHERIT;
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (ff1 > 10);
+ SELECT pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pgc.connoinherit FROM pg_class AS pc INNER JOIN pg_constraint AS pgc ON (pgc.conrelid = pc.oid) WHERE pc.relname = 'pt1' order by 1,2;
+ -- child does not inherit NO INHERIT constraints
+ CREATE FOREIGN TABLE ct1 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ CREATE FOREIGN TABLE ct2 (ff1 integer, ff2 text)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ -- child must have parent's INHERIT constraints
+ ALTER FOREIGN TABLE ct2 INHERIT pt1; -- ERROR
+ ALTER FOREIGN TABLE ct2 ADD CONSTRAINT pt1chk2 CHECK (ff1 > 10);
+ ALTER FOREIGN TABLE ct2 INHERIT pt1;
+ \d+ pt1
+ \d+ ct1
+ \d+ ct2
+
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk2 CASCADE;
+
+ -- NOT VALID should be ignored for foreign tables
+ INSERT INTO pt1 VALUES (100, 'pt1');
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (ff1 > 10) NOT VALID;
+ \d+ pt1
+ \d+ ct1
+ \d+ ct2
+ -- VALIDATE CONSTRAINT should work by ignoring foreign tables
+ ALTER TABLE pt1 VALIDATE CONSTRAINT pt1chk2;
+
+ -- cannot add an OID system column
+ ALTER TABLE pt1 SET WITH OIDS; -- ERROR
+
+ DROP VIEW view1;
+ DROP TABLE pt1 CASCADE;
+
-- IMPORT FOREIGN SCHEMA
IMPORT FOREIGN SCHEMA s1 FROM SERVER s9 INTO public; -- ERROR
IMPORT FOREIGN SCHEMA s1 LIMIT TO (t1) FROM SERVER s9 INTO public; --ERROR
[text/x-diff] analyze.patch (611B, ../../53FEEF94.6040101@lab.ntt.co.jp/3-analyze.patch)
download | inline diff:
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 1478,1483 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1478,1487 ----
/* CCI because we already updated the pg_class row in this command */
CommandCounterIncrement();
SetRelationHasSubclass(RelationGetRelid(onerel), false);
+ ereport(elevel,
+ (errmsg("skipping analyze of \"%s.%s\" inheritance tree --- this inheritance tree contains no child tables",
+ get_namespace_name(RelationGetNamespace(onerel)),
+ RelationGetRelationName(onerel))));
return 0;
}
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-09-02 05:22 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2014-09-02 05:22 UTC (permalink / raw)
To: fujita.etsuro@lab.ntt.co.jp; +Cc: noah@leadboat.com; shigeru.hanada@gmail.com; pgsql-hackers
Hello, I have a request with slight significance for the messages.
> I'd like to address this by emitting the second message as shown below:
>
> INFO: analyzing "public.parent"
> INFO: "parent": scanned 0 of 0 pages, containing 0 live rows and 0 dead
> rows; 0 rows in sample, 0 estimated total rows
> INFO: analyzing "public.parent" inheritance tree
> INFO: skipping analyze of "public.parent" inheritance tree --- this
> inheritance tree contains foreign tables
In acquire_inherited_sample_rows(), the message below is emitted
when the parent explicitly specified in analyze command has at
least one foreign tables.
> "skipping analyze of \"%s.%s\" inheritance tree --- this
> inheritance tree contains foreign tables"
This message implicitly asserts (for me) that "A inheritance tree
containing at least one foreign tables *always* cannot be
analyzed" but in reality, we can let it go by specifying the
parent table explicitly. For example, the additional HINT or
DETAIL message would clarify that.
> INFO: analyzing "public.parent" inheritance tree
> INFO: skipping analyze of "public.parent" inheritance tree --- this
> inheritance tree contains foreign tables
+ HINT: You can analyze this inheritance tree by specifying "public.parent" to analze command
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-09-10 19:32 Heikki Linnakangas <hlinnakangas@vmware.com>
parent: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Heikki Linnakangas @ 2014-09-10 19:32 UTC (permalink / raw)
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; fujita.etsuro@lab.ntt.co.jp; +Cc: noah@leadboat.com; shigeru.hanada@gmail.com; pgsql-hackers
I had a cursory look at this patch and the discussions around this.
ISTM there are actually two new features in this: 1) allow CHECK
constraints on foreign tables, and 2) support inheritance for foreign
tables. How about splitting it into two?
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-09-11 09:22 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Heikki Linnakangas <hlinnakangas@vmware.com>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-09-11 09:22 UTC (permalink / raw)
To: Heikki Linnakangas <hlinnakangas@vmware.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: noah@leadboat.com; shigeru.hanada@gmail.com; pgsql-hackers
(2014/09/11 4:32), Heikki Linnakangas wrote:
> I had a cursory look at this patch and the discussions around this.
Thank you!
> ISTM there are actually two new features in this: 1) allow CHECK
> constraints on foreign tables, and 2) support inheritance for foreign
> tables. How about splitting it into two?
That's right. There are the two in this patch.
I'm not sure if I should split the patch into the two, because 1) won't
make sense without 2). As described in the following note added to the
docs on CREATE FOREIGN TABLE, CHECK constraints on foreign tables are
intended to support constraint exclusion for partitioned foreign tables:
+ Constraints on foreign tables are not enforced on insert or update.
+ Those definitions simply declare the constraints hold for all rows
+ in the foreign tables. It is the user's responsibility to ensure
+ that those definitions match the remote side. Such constraints are
+ used for some kind of query optimization such as constraint exclusion
+ for partitioned tables
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-09-11 10:46 Heikki Linnakangas <hlinnakangas@vmware.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Heikki Linnakangas @ 2014-09-11 10:46 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: noah@leadboat.com; shigeru.hanada@gmail.com; pgsql-hackers
On 09/11/2014 12:22 PM, Etsuro Fujita wrote:
> (2014/09/11 4:32), Heikki Linnakangas wrote:
>> I had a cursory look at this patch and the discussions around this.
>
> Thank you!
>
>> ISTM there are actually two new features in this: 1) allow CHECK
>> constraints on foreign tables, and 2) support inheritance for foreign
>> tables. How about splitting it into two?
>
> That's right. There are the two in this patch.
>
> I'm not sure if I should split the patch into the two, because 1) won't
> make sense without 2). As described in the following note added to the
> docs on CREATE FOREIGN TABLE, CHECK constraints on foreign tables are
> intended to support constraint exclusion for partitioned foreign tables:
>
> + Constraints on foreign tables are not enforced on insert or update.
> + Those definitions simply declare the constraints hold for all rows
> + in the foreign tables. It is the user's responsibility to ensure
> + that those definitions match the remote side. Such constraints are
> + used for some kind of query optimization such as constraint exclusion
> + for partitioned tables
The planner can do constraint exclusion based on CHECK constraints even
without inheritance. It's not enabled by default because it can increase
planning time, but if you set constraint_exclusion=on, it will work.
For example:
postgres=# create table foo (i int4 CHECK (i > 0));
CREATE TABLE
postgres=# explain select * from foo WHERE i < 0;
QUERY PLAN
------------------------------------------------------
Seq Scan on foo (cost=0.00..40.00 rows=800 width=4)
Filter: (i < 0)
Planning time: 0.335 ms
(3 rows)
postgres=# show constraint_exclusion ;
constraint_exclusion
----------------------
partition
(1 row)
postgres=# set constraint_exclusion ='on';
SET
postgres=# explain select * from foo WHERE i < 0;
QUERY PLAN
------------------------------------------
Result (cost=0.00..0.01 rows=1 width=0)
One-Time Filter: false
Planning time: 0.254 ms
(3 rows)
postgres=#
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-09-11 11:30 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Heikki Linnakangas <hlinnakangas@vmware.com>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-09-11 11:30 UTC (permalink / raw)
To: Heikki Linnakangas <hlinnakangas@vmware.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: noah@leadboat.com; shigeru.hanada@gmail.com; pgsql-hackers
(2014/09/11 19:46), Heikki Linnakangas wrote:
> On 09/11/2014 12:22 PM, Etsuro Fujita wrote:
>> (2014/09/11 4:32), Heikki Linnakangas wrote:
>>> I had a cursory look at this patch and the discussions around this.
>>
>> Thank you!
>>
>>> ISTM there are actually two new features in this: 1) allow CHECK
>>> constraints on foreign tables, and 2) support inheritance for foreign
>>> tables. How about splitting it into two?
>>
>> That's right. There are the two in this patch.
>>
>> I'm not sure if I should split the patch into the two, because 1) won't
>> make sense without 2). As described in the following note added to the
>> docs on CREATE FOREIGN TABLE, CHECK constraints on foreign tables are
>> intended to support constraint exclusion for partitioned foreign tables:
>>
>> + Constraints on foreign tables are not enforced on insert or update.
>> + Those definitions simply declare the constraints hold for all rows
>> + in the foreign tables. It is the user's responsibility to ensure
>> + that those definitions match the remote side. Such constraints are
>> + used for some kind of query optimization such as constraint
>> exclusion
>> + for partitioned tables
>
> The planner can do constraint exclusion based on CHECK constraints even
> without inheritance. It's not enabled by default because it can increase
> planning time, but if you set constraint_exclusion=on, it will work.
Exactly!
> For example:
>
> postgres=# create table foo (i int4 CHECK (i > 0));
> CREATE TABLE
> postgres=# explain select * from foo WHERE i < 0;
> QUERY PLAN
> ------------------------------------------------------
> Seq Scan on foo (cost=0.00..40.00 rows=800 width=4)
> Filter: (i < 0)
> Planning time: 0.335 ms
> (3 rows)
>
> postgres=# show constraint_exclusion ;
> constraint_exclusion
> ----------------------
> partition
> (1 row)
>
> postgres=# set constraint_exclusion ='on';
> SET
> postgres=# explain select * from foo WHERE i < 0;
> QUERY PLAN
> ------------------------------------------
> Result (cost=0.00..0.01 rows=1 width=0)
> One-Time Filter: false
> Planning time: 0.254 ms
> (3 rows)
>
> postgres=#
Actually, this patch allows the exact same thing to apply to foreign
tables. My explanation was insufficient about that. Sorry for that.
So, should I split the patch into the two?
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-09-11 11:51 Heikki Linnakangas <hlinnakangas@vmware.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Heikki Linnakangas @ 2014-09-11 11:51 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: noah@leadboat.com; shigeru.hanada@gmail.com; pgsql-hackers
On 09/11/2014 02:30 PM, Etsuro Fujita wrote:
> Actually, this patch allows the exact same thing to apply to foreign
> tables. My explanation was insufficient about that. Sorry for that.
Great, that's what I thought.
> So, should I split the patch into the two?
Yeah, please do.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-09-12 07:30 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Heikki Linnakangas <hlinnakangas@vmware.com>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-09-12 07:30 UTC (permalink / raw)
To: Heikki Linnakangas <hlinnakangas@vmware.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: noah@leadboat.com; shigeru.hanada@gmail.com; pgsql-hackers
(2014/09/11 20:51), Heikki Linnakangas wrote:
> On 09/11/2014 02:30 PM, Etsuro Fujita wrote:
>> So, should I split the patch into the two?
>
> Yeah, please do.
OK, Will do.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-10-14 11:00 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-10-14 11:00 UTC (permalink / raw)
To: Heikki Linnakangas <hlinnakangas@vmware.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: noah@leadboat.com; shigeru.hanada@gmail.com; pgsql-hackers
(2014/09/12 16:30), Etsuro Fujita wrote:
> (2014/09/11 20:51), Heikki Linnakangas wrote:
>> On 09/11/2014 02:30 PM, Etsuro Fujita wrote:
>>> So, should I split the patch into the two?
>>
>> Yeah, please do.
>
> OK, Will do.
Here are separated patches.
fdw-chk.patch - CHECK constraints on foreign tables
fdw-inh.patch - table inheritance with foreign tables
The latter has been created on top of [1]. I've addressed the comment
from Horiguchi-san [2] in it also, though I've slightly modified his
proposal.
There is the functionality for path reparameterization for ForeignScan
in the previous patch, but since the functionality would be useful not
only for such table inheritance cases but for UNION ALL cases, I'd add
the functionality as an independent feature maybe to CF 2014-12.
Thanks,
[1] http://www.postgresql.org/message-id/540DA168.3040407@lab.ntt.co.jp
[2]
http://www.postgresql.org/message-id/20140902.142218.253402812.horiguchi.kyotaro@lab.ntt.co.jp
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] fdw-chk.patch (25.0K, ../../543D0261.6020803@lab.ntt.co.jp/2-fdw-chk.patch)
download | inline diff:
*** a/contrib/file_fdw/input/file_fdw.source
--- b/contrib/file_fdw/input/file_fdw.source
***************
*** 134,139 **** DELETE FROM agg_csv WHERE a = 100;
--- 134,149 ----
-- but this should be ignored
SELECT * FROM agg_csv FOR UPDATE;
+ -- constraint exclusion tests
+ ALTER FOREIGN TABLE agg_csv ADD CONSTRAINT agg_csv_a_check CHECK (a >= 0);
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
+ SET constraint_exclusion = 'on';
+ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
+ SET constraint_exclusion = 'partition';
+ \t off
+ ALTER FOREIGN TABLE agg_csv DROP CONSTRAINT agg_csv_a_check;
+
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/file_fdw/output/file_fdw.source
--- b/contrib/file_fdw/output/file_fdw.source
***************
*** 219,224 **** SELECT * FROM agg_csv FOR UPDATE;
--- 219,242 ----
42 | 324.78
(3 rows)
+ -- constraint exclusion tests
+ ALTER FOREIGN TABLE agg_csv ADD CONSTRAINT agg_csv_a_check CHECK (a >= 0);
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
+ Foreign Scan on public.agg_csv
+ Output: a, b
+ Filter: (agg_csv.a < 0)
+ Foreign File: @abs_srcdir@/data/agg.csv
+
+ SET constraint_exclusion = 'on';
+ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
+ Result
+ Output: a, b
+ One-Time Filter: false
+
+ SET constraint_exclusion = 'partition';
+ \t off
+ ALTER FOREIGN TABLE agg_csv DROP CONSTRAINT agg_csv_a_check;
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 2488,2493 **** select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
--- 2488,2512 ----
(13 rows)
-- ===================================================================
+ -- test constraint exclusion stuff
+ -- ===================================================================
+ ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c1_check CHECK (c1 > 0);
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM ft1 WHERE c1 <= 0;
+ Foreign Scan on public.ft1
+ Output: c1, c2, c3, c4, c5, c6, c7, c8
+ Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" <= 0))
+
+ SET constraint_exclusion = 'on';
+ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM ft1 WHERE c1 <= 0;
+ Result
+ Output: c1, c2, c3, c4, c5, c6, c7, c8
+ One-Time Filter: false
+
+ SET constraint_exclusion = 'partition';
+ \t off
+ ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c1_check;
+ -- ===================================================================
-- test serial columns (ie, sequence-based defaults)
-- ===================================================================
create table loc1 (f1 serial, f2 text);
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 387,392 **** select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
--- 387,404 ----
select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
-- ===================================================================
+ -- test constraint exclusion stuff
+ -- ===================================================================
+ ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c1_check CHECK (c1 > 0);
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM ft1 WHERE c1 <= 0;
+ SET constraint_exclusion = 'on';
+ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM ft1 WHERE c1 <= 0;
+ SET constraint_exclusion = 'partition';
+ \t off
+ ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c1_check;
+
+ -- ===================================================================
-- test serial columns (ie, sequence-based defaults)
-- ===================================================================
create table loc1 (f1 serial, f2 text);
*** 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 ] <replaceable class="PARAMETER">name</replaceab
--- 42,49 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ ADD <replaceable class="PARAMETER">table_constraint</replaceable>
+ DROP CONSTRAINT [ IF EXISTS ] <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
DISABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
ENABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
ENABLE REPLICA TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
***************
*** 152,157 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 154,183 ----
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds a new constraint to a table using the same syntax as
+ <xref linkend="SQL-CREATEFOREIGNTABLE">.
+ 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.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DROP CONSTRAINT [ IF EXISTS ]</literal></term>
+ <listitem>
+ <para>
+ This form drops the specified constraint on a table.
+ If <literal>IF EXISTS</literal> is specified and the constraint
+ does not exist, no error is thrown. In this case a notice is issued instead.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>DISABLE</literal>/<literal>ENABLE [ REPLICA | ALWAYS ] TRIGGER</literal></term>
<listitem>
***************
*** 284,289 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 310,333 ----
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="PARAMETER">table_constraint</replaceable></term>
+ <listitem>
+ <para>
+ New table constraint for the table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">constraint_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing constraint to drop.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>CASCADE</literal></term>
<listitem>
***************
*** 365,374 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint is
! added, or a column type is changed with <literal>SET DATA TYPE</>. It is
! the user's responsibility to ensure that the table definition matches the
! remote side.
</para>
<para>
--- 409,418 ----
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint or
! <literal>CHECK</> constraint is added, or a column type is changed with
! <literal>SET DATA TYPE</>. It is the user's responsibility to ensure that
! the table definition matches the remote side.
</para>
<para>
*** a/doc/src/sgml/ref/create_foreign_table.sgml
--- b/doc/src/sgml/ref/create_foreign_table.sgml
***************
*** 19,25 ****
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
SERVER <replaceable class="parameter">server_name</replaceable>
--- 19,26 ----
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
! | <replaceable>table_constraint</replaceable> }
[, ... ]
] )
SERVER <replaceable class="parameter">server_name</replaceable>
***************
*** 30,36 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 31,43 ----
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ NOT NULL |
NULL |
+ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) |
DEFAULT <replaceable>default_expr</replaceable> }
+
+ <phrase>and <replaceable class="PARAMETER">table_constraint</replaceable> is:</phrase>
+
+ [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
+ { CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) }
</synopsis>
</refsynopsisdiv>
***************
*** 138,143 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 145,171 ----
</varlistentry>
<varlistentry>
+ <term><literal>CHECK ( <replaceable class="PARAMETER">expression</replaceable> )</literal></term>
+ <listitem>
+ <para>
+ The <literal>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.
+ </para>
+
+ <para>
+ Currently, <literal>CHECK</literal> expressions cannot contain
+ subqueries nor refer to variables other than columns of the
+ current row. The system column <literal>tableoid</literal>
+ may be referenced, but not any other system column.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>DEFAULT
<replaceable>default_expr</replaceable></literal></term>
<listitem>
***************
*** 187,192 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 215,230 ----
</refsect1>
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ Constraints on foreign tables are not enforced on insert or update.
+ Those definitions simply declare the constraints hold for all rows
+ in the foreign tables. It is the user's responsibility to ensure
+ that those definitions match the remote side.
+ </para>
+ </refsect1>
<refsect1 id="SQL-CREATEFOREIGNTABLE-examples">
<title>Examples</title>
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 2221,2226 **** AddRelationNewConstraints(Relation rel,
--- 2221,2240 ----
if (cdef->contype != CONSTR_CHECK)
continue;
+ /* Don't allow NO INHERIT for foreign tables */
+ if (cdef->is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("CHECK constraints on foreign tables cannot be marked NO INHERIT")));
+
+ /* Don't allow NOT VALID for foreign tables */
+ if (cdef->skip_validation &&
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("NOT VALID is not supported for CHECK constraints on foreign tables")));
+
if (cdef->raw_expr != NULL)
{
Assert(cdef->cooked_expr == NULL);
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 477,486 **** 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,
--- 477,482 ----
***************
*** 3142,3148 **** 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)
--- 3138,3144 ----
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)
***************
*** 3156,3162 **** 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)
--- 3152,3158 ----
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)
*** 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/test/regress/expected/foreign_data.out
--- b/src/test/regress/expected/foreign_data.out
***************
*** 670,676 **** LINE 1: CREATE FOREIGN TABLE ft1 () SERVER s0 WITH OIDS;
^
CREATE FOREIGN TABLE ft1 (
c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
! c2 text OPTIONS (param2 'val2', param3 'val3'),
c3 date
) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
COMMENT ON FOREIGN TABLE ft1 IS 'ft1';
--- 670,682 ----
^
CREATE FOREIGN TABLE ft1 (
c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
! c2 text OPTIONS (param2 'val2', param3 'val3') CHECK (c2 <> '') NO INHERIT,
! c3 date
! ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
! ERROR: CHECK constraints on foreign tables cannot be marked NO INHERIT
! CREATE FOREIGN TABLE ft1 (
! c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
! c2 text OPTIONS (param2 'val2', param3 'val3') CHECK (c2 <> ''),
c3 date
) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
COMMENT ON FOREIGN TABLE ft1 IS 'ft1';
***************
*** 682,687 **** COMMENT ON COLUMN ft1.c1 IS 'ft1.c1';
--- 688,695 ----
c1 | integer | not null | ("param 1" 'val1') | plain | | ft1.c1
c2 | text | | (param2 'val2', param3 'val3') | extended | |
c3 | date | | | plain | |
+ Check constraints:
+ "ft1_c2_check" CHECK (c2 <> ''::text)
Server: s0
FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
***************
*** 740,745 **** ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET STATISTICS -1;
--- 748,755 ----
c8 | text | | (p2 'V2') | extended | |
c9 | integer | | | plain | |
c10 | integer | | (p1 'v1') | plain | |
+ Check constraints:
+ "ft1_c2_check" CHECK (c2 <> ''::text)
Server: s0
FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
***************
*** 748,763 **** 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;
--- 758,775 ----
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) NO INHERIT; -- ERROR
! ERROR: CHECK constraints on foreign tables cannot be marked NO INHERIT
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0) NOT VALID; -- ERROR
! ERROR: NOT VALID is not supported for CHECK constraints on foreign tables
! 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_c1_check;
! ERROR: constraint "ft1_c1_check" of relation "ft1" does not exist
! 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;
***************
*** 785,790 **** ALTER FOREIGN TABLE foreign_schema.ft1 RENAME TO foreign_table_1;
--- 797,804 ----
c7 | integer | | (p1 'v1', p2 'v2')
c8 | text | | (p2 'V2')
c10 | integer | | (p1 'v1')
+ Check constraints:
+ "ft1_c2_check" CHECK (c2 <> ''::text)
Server: s0
FDW Options: (quote '~', "be quoted" 'value', escape '@')
*** a/src/test/regress/sql/foreign_data.sql
--- b/src/test/regress/sql/foreign_data.sql
***************
*** 270,276 **** CREATE FOREIGN TABLE ft1 () SERVER no_server; -- ERROR
CREATE FOREIGN TABLE ft1 () SERVER s0 WITH OIDS; -- ERROR
CREATE FOREIGN TABLE ft1 (
c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
! c2 text OPTIONS (param2 'val2', param3 'val3'),
c3 date
) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
COMMENT ON FOREIGN TABLE ft1 IS 'ft1';
--- 270,281 ----
CREATE FOREIGN TABLE ft1 () SERVER s0 WITH OIDS; -- ERROR
CREATE FOREIGN TABLE ft1 (
c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
! c2 text OPTIONS (param2 'val2', param3 'val3') CHECK (c2 <> '') NO INHERIT,
! c3 date
! ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
! CREATE FOREIGN TABLE ft1 (
! c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
! c2 text OPTIONS (param2 'val2', param3 'val3') CHECK (c2 <> ''),
c3 date
) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
COMMENT ON FOREIGN TABLE ft1 IS 'ft1';
***************
*** 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 '@');
--- 319,331 ----
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) NO INHERIT; -- ERROR
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0) NOT VALID; -- 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 '@');
[text/x-diff] fdw-inh.patch (78.6K, ../../543D0261.6020803@lab.ntt.co.jp/3-fdw-inh.patch)
download | inline diff:
*** a/contrib/file_fdw/input/file_fdw.source
--- b/contrib/file_fdw/input/file_fdw.source
***************
*** 144,149 **** SET constraint_exclusion = 'partition';
--- 144,168 ----
\t off
ALTER FOREIGN TABLE agg_csv DROP CONSTRAINT agg_csv_a_check;
+ -- table inheritance tests
+ CREATE TABLE agg (a int2, b float4);
+ ALTER FOREIGN TABLE agg_text INHERIT agg;
+ ALTER FOREIGN TABLE agg_csv INHERIT agg;
+ INSERT INTO agg
+ SELECT x, 5432.0 FROM generate_series(0,1000) x;
+ SELECT * FROM agg WHERE b < 10.0 ORDER BY a, b;
+ SELECT * FROM ONLY agg WHERE b < 10.0 ORDER BY a, b;
+ SELECT * FROM agg_text;
+ SELECT * FROM agg_csv;
+ -- updates aren't supported
+ UPDATE agg SET a = 1;
+ DELETE FROM agg WHERE a = 100;
+ -- but this should be ignored
+ SELECT * FROM agg WHERE b < 10.0 ORDER BY a, b FOR UPDATE;
+ ALTER FOREIGN TABLE agg_csv NO INHERIT agg;
+ ALTER FOREIGN TABLE agg_text NO INHERIT agg;
+ DROP TABLE agg;
+
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/file_fdw/output/file_fdw.source
--- b/contrib/file_fdw/output/file_fdw.source
***************
*** 237,242 **** EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
--- 237,295 ----
SET constraint_exclusion = 'partition';
\t off
ALTER FOREIGN TABLE agg_csv DROP CONSTRAINT agg_csv_a_check;
+ -- table inheritance tests
+ CREATE TABLE agg (a int2, b float4);
+ ALTER FOREIGN TABLE agg_text INHERIT agg;
+ ALTER FOREIGN TABLE agg_csv INHERIT agg;
+ INSERT INTO agg
+ SELECT x, 5432.0 FROM generate_series(0,1000) x;
+ SELECT * FROM agg WHERE b < 10.0 ORDER BY a, b;
+ a | b
+ ----+---------
+ 0 | 0.09561
+ 0 | 0.09561
+ 56 | 7.8
+ (3 rows)
+
+ SELECT * FROM ONLY agg WHERE b < 10.0 ORDER BY a, b;
+ a | b
+ ---+---
+ (0 rows)
+
+ SELECT * FROM agg_text;
+ a | b
+ -----+---------
+ 56 | 7.8
+ 100 | 99.097
+ 0 | 0.09561
+ 42 | 324.78
+ (4 rows)
+
+ SELECT * FROM agg_csv;
+ a | b
+ -----+---------
+ 100 | 99.097
+ 0 | 0.09561
+ 42 | 324.78
+ (3 rows)
+
+ -- updates aren't supported
+ UPDATE agg SET a = 1;
+ ERROR: cannot update foreign table "agg_text"
+ DELETE FROM agg WHERE a = 100;
+ ERROR: cannot delete from foreign table "agg_text"
+ -- but this should be ignored
+ SELECT * FROM agg WHERE b < 10.0 ORDER BY a, b FOR UPDATE;
+ a | b
+ ----+---------
+ 0 | 0.09561
+ 0 | 0.09561
+ 56 | 7.8
+ (3 rows)
+
+ ALTER FOREIGN TABLE agg_csv NO INHERIT agg;
+ ALTER FOREIGN TABLE agg_text NO INHERIT agg;
+ DROP TABLE agg;
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 2860,2865 **** NOTICE: NEW: (13,"test triggered !")
--- 2860,3347 ----
(1 row)
-- ===================================================================
+ -- test inheritance features
+ -- ===================================================================
+ CREATE TABLE a (aa TEXT);
+ CREATE TABLE loct (aa TEXT, bb TEXT);
+ CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a)
+ SERVER loopback OPTIONS (table_name 'loct');
+ INSERT INTO a(aa) VALUES('aaa');
+ INSERT INTO a(aa) VALUES('aaaa');
+ INSERT INTO a(aa) VALUES('aaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaaa');
+ INSERT INTO b(aa) VALUES('bbb');
+ INSERT INTO b(aa) VALUES('bbbb');
+ INSERT INTO b(aa) VALUES('bbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbbb');
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | aaa
+ a | aaaa
+ a | aaaaa
+ a | aaaaaa
+ a | aaaaaaa
+ a | aaaaaaaa
+ b | bbb
+ b | bbbb
+ b | bbbbb
+ b | bbbbbb
+ b | bbbbbbb
+ b | bbbbbbbb
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----------+----
+ b | bbb |
+ b | bbbb |
+ b | bbbbb |
+ b | bbbbbb |
+ b | bbbbbbb |
+ b | bbbbbbbb |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | aaa
+ a | aaaa
+ a | aaaaa
+ a | aaaaaa
+ a | aaaaaaa
+ a | aaaaaaaa
+ (6 rows)
+
+ UPDATE a SET aa='zzzzzz' WHERE aa LIKE 'aaa%';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ b | bbb
+ b | bbbb
+ b | bbbbb
+ b | bbbbbb
+ b | bbbbbbb
+ b | bbbbbbbb
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----------+----
+ b | bbb |
+ b | bbbb |
+ b | bbbbb |
+ b | bbbbbb |
+ b | bbbbbbb |
+ b | bbbbbbbb |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ (6 rows)
+
+ UPDATE b SET aa='new';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+-----+----
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ (6 rows)
+
+ UPDATE a SET aa='new';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+-----
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+-----+----
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+-----
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ (6 rows)
+
+ DELETE FROM a;
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----
+ (0 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----+----
+ (0 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----
+ (0 rows)
+
+ DROP TABLE a CASCADE;
+ NOTICE: drop cascades to foreign table b
+ DROP TABLE loct;
+ -- Check SELECT FOR UPDATE/SHARE with an inherited source table
+ create table loct1 (f1 int, f2 int, f3 int);
+ create table loct2 (f1 int, f2 int, f3 int);
+ create table foo (f1 int, f2 int);
+ create foreign table foo2 (f3 int) inherits (foo)
+ server loopback options (table_name 'loct1');
+ create table bar (f1 int, f2 int);
+ create foreign table bar2 (f3 int) inherits (bar)
+ server loopback options (table_name 'loct2');
+ insert into foo values(1,1);
+ insert into foo values(3,3);
+ insert into foo2 values(2,2,2);
+ insert into foo2 values(3,3,3);
+ insert into bar values(1,1);
+ insert into bar values(2,2);
+ insert into bar values(3,3);
+ insert into bar values(4,4);
+ insert into bar2 values(1,1,1);
+ insert into bar2 values(2,2,2);
+ insert into bar2 values(3,3,3);
+ insert into bar2 values(4,4,4);
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for update;
+ QUERY PLAN
+ ----------------------------------------------------------------------------------------------
+ LockRows
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ -> Hash Join
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Append
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.ctid, bar2.tableoid, bar2.*
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (22 rows)
+
+ select * from bar where f1 in (select f1 from foo) for update;
+ f1 | f2
+ ----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ (6 rows)
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for share;
+ QUERY PLAN
+ ----------------------------------------------------------------------------------------------
+ LockRows
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ -> Hash Join
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Append
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.ctid, bar2.tableoid, bar2.*
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (22 rows)
+
+ select * from bar where f1 in (select f1 from foo) for share;
+ f1 | f2
+ ----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ (6 rows)
+
+ -- Check UPDATE with inherited target and an inherited source table
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ QUERY PLAN
+ ---------------------------------------------------------------------------------------------
+ Update on public.bar
+ -> Hash Join
+ Output: bar.f1, (bar.f2 + 100), bar.ctid, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ -> Hash Join
+ Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar2.f1 = foo.f1)
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (34 rows)
+
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+ relname | f1 | f2
+ ---------+----+-----
+ bar | 1 | 101
+ bar | 2 | 102
+ bar | 3 | 103
+ bar | 4 | 4
+ bar2 | 1 | 101
+ bar2 | 2 | 102
+ bar2 | 3 | 103
+ bar2 | 4 | 4
+ (8 rows)
+
+ -- Check UPDATE with inherited target and an appendrel subquery
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ QUERY PLAN
+ --------------------------------------------------------------------------------------
+ Update on public.bar
+ -> Hash Join
+ Output: bar.f1, (bar.f2 + 100), bar.ctid, (ROW(foo.f1))
+ Hash Cond: (foo.f1 = bar.f1)
+ -> Append
+ -> Seq Scan on public.foo
+ Output: ROW(foo.f1), foo.f1
+ -> Foreign Scan on public.foo2
+ Output: ROW(foo2.f1), foo2.f1
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Seq Scan on public.foo foo_1
+ Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3)
+ -> Foreign Scan on public.foo2 foo2_1
+ Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3)
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Hash
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Merge Join
+ Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, (ROW(foo.f1))
+ Merge Cond: (bar2.f1 = foo.f1)
+ -> Sort
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Sort Key: bar2.f1
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Sort
+ Output: (ROW(foo.f1)), foo.f1
+ Sort Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: ROW(foo.f1), foo.f1
+ -> Foreign Scan on public.foo2
+ Output: ROW(foo2.f1), foo2.f1
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Seq Scan on public.foo foo_1
+ Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3)
+ -> Foreign Scan on public.foo2 foo2_1
+ Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3)
+ Remote SQL: SELECT f1 FROM public.loct1
+ (42 rows)
+
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+ relname | f1 | f2
+ ---------+----+-----
+ bar | 1 | 201
+ bar | 2 | 202
+ bar | 3 | 203
+ bar | 4 | 104
+ bar2 | 1 | 201
+ bar2 | 2 | 202
+ bar2 | 3 | 203
+ bar2 | 4 | 104
+ (8 rows)
+
+ drop table foo cascade;
+ NOTICE: drop cascades to foreign table foo2
+ drop table bar cascade;
+ NOTICE: drop cascades to foreign table bar2
+ drop table loct1;
+ drop table loct2;
+ -- Test that WHERE CURRENT OF is not supported
+ create table ltbl (a int, b text);
+ create table ptbl (a int, b text);
+ create table locc () inherits (ptbl);
+ insert into locc values(1, 'foo');
+ create foreign table remc () inherits (ptbl)
+ server loopback options (table_name 'ltbl');
+ insert into remc values(2, 'bar');
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'foo';
+ fetch from c;
+ ?column?
+ ----------
+ 1
+ (1 row)
+
+ update ptbl set b = null where current of c;
+ ERROR: WHERE CURRENT OF is not supported for this table type
+ rollback;
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'bar';
+ fetch from c;
+ ?column?
+ ----------
+ 1
+ (1 row)
+
+ update ptbl set b = null where current of c;
+ ERROR: WHERE CURRENT OF is not supported for this table type
+ rollback;
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ drop table ptbl cascade;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to table locc
+ drop cascades to foreign table remc
+ drop table ltbl;
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
CREATE SCHEMA import_source;
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 823,828 **** postgresGetForeignPlan(PlannerInfo *root,
--- 823,839 ----
{
RowMarkClause *rc = get_parse_rowmark(root->parse, baserel->relid);
+ if (rc == NULL)
+ {
+ PlanRowMark *prm = get_plan_rowmark(root->rowMarks, baserel->relid);
+
+ if (prm)
+ {
+ if (prm->rti != prm->prti)
+ rc = get_parse_rowmark(root->parse, prm->prti);
+ }
+ }
+
if (rc)
{
/*
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 629,634 **** UPDATE rem1 SET f2 = 'testo';
--- 629,777 ----
INSERT INTO rem1(f2) VALUES ('test') RETURNING ctid;
-- ===================================================================
+ -- test inheritance features
+ -- ===================================================================
+
+ CREATE TABLE a (aa TEXT);
+ CREATE TABLE loct (aa TEXT, bb TEXT);
+ CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a)
+ SERVER loopback OPTIONS (table_name 'loct');
+
+ INSERT INTO a(aa) VALUES('aaa');
+ INSERT INTO a(aa) VALUES('aaaa');
+ INSERT INTO a(aa) VALUES('aaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaaa');
+
+ INSERT INTO b(aa) VALUES('bbb');
+ INSERT INTO b(aa) VALUES('bbbb');
+ INSERT INTO b(aa) VALUES('bbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbbb');
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE a SET aa='zzzzzz' WHERE aa LIKE 'aaa%';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE b SET aa='new';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE a SET aa='new';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ DELETE FROM a;
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ DROP TABLE a CASCADE;
+ DROP TABLE loct;
+
+ -- Check SELECT FOR UPDATE/SHARE with an inherited source table
+ create table loct1 (f1 int, f2 int, f3 int);
+ create table loct2 (f1 int, f2 int, f3 int);
+
+ create table foo (f1 int, f2 int);
+ create foreign table foo2 (f3 int) inherits (foo)
+ server loopback options (table_name 'loct1');
+ create table bar (f1 int, f2 int);
+ create foreign table bar2 (f3 int) inherits (bar)
+ server loopback options (table_name 'loct2');
+
+ insert into foo values(1,1);
+ insert into foo values(3,3);
+ insert into foo2 values(2,2,2);
+ insert into foo2 values(3,3,3);
+ insert into bar values(1,1);
+ insert into bar values(2,2);
+ insert into bar values(3,3);
+ insert into bar values(4,4);
+ insert into bar2 values(1,1,1);
+ insert into bar2 values(2,2,2);
+ insert into bar2 values(3,3,3);
+ insert into bar2 values(4,4,4);
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for update;
+ select * from bar where f1 in (select f1 from foo) for update;
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for share;
+ select * from bar where f1 in (select f1 from foo) for share;
+
+ -- Check UPDATE with inherited target and an inherited source table
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+
+ -- Check UPDATE with inherited target and an appendrel subquery
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+
+ drop table foo cascade;
+ drop table bar cascade;
+ drop table loct1;
+ drop table loct2;
+
+ -- Test that WHERE CURRENT OF is not supported
+ create table ltbl (a int, b text);
+
+ create table ptbl (a int, b text);
+ create table locc () inherits (ptbl);
+ insert into locc values(1, 'foo');
+
+ create foreign table remc () inherits (ptbl)
+ server loopback options (table_name 'ltbl');
+ insert into remc values(2, 'bar');
+
+ select * from ptbl;
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'foo';
+ fetch from c;
+ update ptbl set b = null where current of c;
+ rollback;
+
+ select * from ptbl;
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'bar';
+ fetch from c;
+ update ptbl set b = null where current of c;
+ rollback;
+
+ select * from ptbl;
+
+ drop table ptbl cascade;
+ drop table ltbl;
+
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
*** a/doc/src/sgml/ddl.sgml
--- b/doc/src/sgml/ddl.sgml
***************
*** 2503,2508 **** VALUES ('Albany', NULL, NULL, 'NY');
--- 2503,2526 ----
further privileges to be granted.
</para>
+ <para>
+ Note that a foreign table can also inherit from more than one parent
+ table (see <xref linkend="ddl-foreign-data">).
+ Like normal tables, table inheritance is typically established when
+ the foreign table is created, using the <literal>INHERITS</> clause of
+ the <xref linkend="sql-createforeigntable"> statement. Alternatively,
+ a foreign table which is already defined in a compatible way can have
+ a new parent relationship added, using the <literal>INHERIT</literal>
+ variant of <xref linkend="sql-alterforeigntable">.
+ Similarly an inheritance link can be removed from a child using the
+ <literal>NO INHERIT</literal> variant of <command>ALTER FOREIGN TABLE</>.
+ <command>CREATE FOREIGN TABLE</command> and
+ <command>ALTER FOREIGN TABLE</command> follow the same rules for
+ duplicate column merging and rejection that apply during
+ <command>CREATE TABLE</command> and <command>ALTER TABLE</command>,
+ respectively.
+ </para>
+
<sect2 id="ddl-inherit-caveats">
<title>Caveats</title>
***************
*** 2650,2656 **** VALUES ('Albany', NULL, NULL, 'NY');
table of a single parent table. The parent table itself is normally
empty; it exists just to represent the entire data set. You should be
familiar with inheritance (see <xref linkend="ddl-inherit">) before
! attempting to set up partitioning.
</para>
<para>
--- 2668,2677 ----
table of a single parent table. The parent table itself is normally
empty; it exists just to represent the entire data set. You should be
familiar with inheritance (see <xref linkend="ddl-inherit">) before
! attempting to set up partitioning. (The setup and management of
! partitioned tables illustrated in this section assume that each
! partition is a normal table. However, you can do that in a similar way
! for cases where some or all partitions are foreign tables.)
</para>
<para>
***************
*** 2713,2720 **** VALUES ('Albany', NULL, NULL, 'NY');
</para>
<para>
! We will refer to the child tables as partitions, though they
! are in every way normal <productname>PostgreSQL</> tables.
</para>
</listitem>
--- 2734,2741 ----
</para>
<para>
! We will refer to the child tables as partitions, though we assume
! that they are normal <productname>PostgreSQL</> tables.
</para>
</listitem>
*** a/doc/src/sgml/ref/alter_foreign_table.sgml
--- b/doc/src/sgml/ref/alter_foreign_table.sgml
***************
*** 48,53 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 48,55 ----
ENABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
ENABLE REPLICA TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
ENABLE ALWAYS TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 190,195 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 192,217 ----
</varlistentry>
<varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OWNER</literal></term>
<listitem>
<para>
***************
*** 380,385 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 402,417 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">new_owner</replaceable></term>
<listitem>
<para>
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 1010,1015 **** ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
--- 1010,1022 ----
</para>
<para>
+ If a table has any descendant tables that are foreign, a recursive
+ <literal>SET STORAGE</literal> operation will be rejected since it
+ is not permitted to add an <literal>oid</literal> system column to
+ foreign tables.
+ </para>
+
+ <para>
The <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>,
and <literal>TABLESPACE</> actions never recurse to descendant tables;
that is, they always act as though <literal>ONLY</> were specified.
***************
*** 1018,1023 **** ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
--- 1025,1043 ----
</para>
<para>
+ When adding a <literal>CHECK</> constraint with the <literal>NOT VALID
+ </literal> option recursively, an inherited constraint on a descendant
+ table that is foreign will be marked valid without checking
+ consistency with the foreign server.
+ </para>
+
+ <para>
+ A recursive <literal>SET STORAGE</literal> operation will make the
+ storage mode for a descendant table's column unchanged if the table is
+ foreign.
+ </para>
+
+ <para>
Changing any part of a system catalog table is not permitted.
</para>
*** a/doc/src/sgml/ref/analyze.sgml
--- b/doc/src/sgml/ref/analyze.sgml
***************
*** 200,205 **** ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
--- 200,212 ----
</para>
<para>
+ 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
+ <command>ANALYZE</command>, the command prints a warning and does nothing.
+ </para>
+
+ <para>
If the table being analyzed is completely empty, <command>ANALYZE</command>
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
***************
*** 23,28 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 23,29 ----
| <replaceable>table_constraint</replaceable> }
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 187,192 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 188,205 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for the
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
***************
*** 223,228 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 236,250 ----
Those definitions simply declare the constraints hold for all rows
in the foreign tables. It is the user's responsibility to ensure
that those definitions match the remote side.
+ These constraints are used in some kind of query optimization such
+ as constraint exclusion for partitioned tables (see
+ <xref linkend="ddl-partitioning">).
+ </para>
+
+ <para>
+ Since it is not permitted to add an <literal>oid</> system column to
+ foreign tables, the command will be rejected if any parent tables
+ have <literal>oid</> system columns.
</para>
</refsect1>
*** 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;
***************
*** 102,107 **** static int acquire_sample_rows(Relation onerel, int elevel,
--- 103,109 ----
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
static int compare_rows(const void *a, const void *b);
+ static bool has_foreign(Oid parentrelId, List *tableOIDs);
static int acquire_inherited_sample_rows(Relation onerel, int elevel,
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
***************
*** 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;
--- 117,126 ----
* 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)
--- 134,140 ----
elevel = DEBUG2;
/* Set up static variables */
+ vac_mode = vacmode;
vac_strategy = bstrategy;
/*
***************
*** 294,302 **** analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
* do_analyze_rel() -- analyze one relation, recursively or not
*
* Note that "acquirefunc" is only relevant for the non-inherited case.
! * If we supported foreign tables in inheritance trees,
! * acquire_inherited_sample_rows would need to determine the appropriate
! * acquirefunc for each child table.
*/
static void
do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
--- 300,307 ----
* do_analyze_rel() -- analyze one relation, recursively or not
*
* Note that "acquirefunc" is only relevant for the non-inherited case.
! * For the inherited case, acquire_inherited_sample_rows determines the
! * appropriate acquirefunc for each child table.
*/
static void
do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
***************
*** 1439,1449 **** compare_rows(const void *a, const void *b)
/*
* acquire_inherited_sample_rows -- acquire sample rows from inheritance tree
*
* This has the same API as acquire_sample_rows, except that rows are
* collected from all inheritance children as well as the specified table.
! * We fail and return zero if there are no inheritance children.
*/
static int
acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1444,1490 ----
/*
+ * Check wether to contain at least one foreign table
+ */
+ static bool
+ has_foreign(Oid parentrelId, List *tableOIDs)
+ {
+ bool result;
+ ListCell *lc;
+
+ /* There are no children */
+ if (list_length(tableOIDs) < 2)
+ return false;
+
+ result = false;
+ 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, NoLock);
+ }
+ return result;
+ }
+
+ /*
* acquire_inherited_sample_rows -- acquire sample rows from inheritance tree
*
* This has the same API as acquire_sample_rows, except that rows are
* collected from all inheritance children as well as the specified table.
! * We fail and return zero if there are no inheritance children or there are
! * inheritance children that foreign tables.
*/
static int
acquire_inherited_sample_rows(Relation onerel, int elevel,
***************
*** 1452,1457 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1493,1499 ----
{
List *tableOIDs;
Relation *rels;
+ AcquireSampleRowsFunc *acquirefunc;
double *relblocks;
double totalblocks;
int numrows,
***************
*** 1482,1491 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1524,1554 ----
}
/*
+ * If we are not in VAC_MODE_SINGLE case and the inheritance set contains
+ * at least one foreign table, then fail.
+ */
+ if (vac_mode != VAC_MODE_SINGLE)
+ {
+ if (has_foreign(RelationGetRelid(onerel), tableOIDs))
+ {
+ ereport(elevel,
+ (errmsg("skipping analyze of \"%s.%s\" inheritance tree --- this inheritance tree contains foreign tables",
+ get_namespace_name(RelationGetNamespace(onerel)),
+ RelationGetRelationName(onerel)),
+ errhint("Try ANALYZE \"%s.%s\" for the inheritance statistics.",
+ get_namespace_name(RelationGetNamespace(onerel)),
+ RelationGetRelationName(onerel))));
+ return 0;
+ }
+ }
+
+ /*
* Count the blocks in all the relations. The result could overflow
* 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++;
}
--- 1570,1605 ----
}
rels[nrels] = childrel;
!
! if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
! {
! FdwRoutine *fdwroutine;
! BlockNumber relpages = 0;
! bool ok = false;
!
! /* 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,
--- 1617,1623 ----
{
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 &&
--- 1633,1644 ----
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
***************
*** 315,321 **** 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,
--- 315,322 ----
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,
***************
*** 566,571 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
--- 567,596 ----
stmt->relation->relpersistence,
&inheritOids, &old_constraints, &parentOidCount);
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * Don't allow a foreign table to inherit from parents that have OID
+ * system columns.
+ */
+ if (parentOidCount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot inherit from relation with OIDs")));
+
+ /*
+ * Reset the storage parameter for inherited attributes that have
+ * non-default values.
+ */
+ foreach(listptr, schema)
+ {
+ ColumnDef *colDef = lfirst(listptr);
+
+ if (colDef->storage != 0)
+ colDef->storage = 0;
+ }
+ }
+
/*
* Create a tuple descriptor from the relation schema. Note that this
* deals with column names, types, and NOT NULL constraints, but not
***************
*** 3090,3113 **** 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;
--- 3115,3142 ----
* 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;
***************
*** 3120,3126 **** 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;
--- 3149,3156 ----
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;
***************
*** 3242,3252 **** 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;
--- 3272,3288 ----
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);
pass = AT_PASS_MISC;
***************
*** 3280,3286 **** 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 */
case AT_EnableRowSecurity:
--- 3316,3321 ----
***************
*** 4274,4280 **** 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
--- 4309,4316 ----
*/
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
***************
*** 4302,4309 **** 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);
}
}
--- 4338,4349 ----
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);
}
}
***************
*** 4593,4599 **** 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);
--- 4633,4639 ----
/* 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);
***************
*** 4889,4894 **** ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4929,4939 ----
/* 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 */
***************
*** 5489,5495 **** 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
--- 5534,5540 ----
/* 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
***************
*** 5881,5887 **** 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
--- 5926,5932 ----
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/*
* Call AddRelationNewConstraints to do the work, making sure it works on
***************
*** 5892,5900 **** 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 */
--- 5937,5953 ----
* 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 */
***************
*** 7381,7387 **** 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);
--- 7434,7440 ----
/* 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);
***************
*** 7716,7722 **** 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,
--- 7769,7778 ----
* 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;
***************
*** 149,154 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
--- 150,169 ----
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.
*/
***************
*** 251,257 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
PushActiveSnapshot(GetTransactionSnapshot());
}
! analyze_rel(relid, vacstmt, vac_strategy);
if (use_own_xacts)
{
--- 266,272 ----
PushActiveSnapshot(GetTransactionSnapshot());
}
! analyze_rel(relid, vacstmt, vac_mode, vac_strategy);
if (use_own_xacts)
{
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 2308,2313 **** EvalPlanQualFetchRowMarks(EPQState *epqstate)
--- 2308,2333 ----
Assert(erm->markType == ROW_MARK_COPY);
+ /* if child rel, must check whether it produced this row */
+ if (erm->rti != erm->prti)
+ {
+ Oid tableoid;
+
+ datum = ExecGetJunkAttribute(epqstate->origslot,
+ aerm->toidAttNo,
+ &isNull);
+ /* non-locked rels could be on the inside of outer joins */
+ if (isNull)
+ continue;
+ tableoid = DatumGetObjectId(datum);
+
+ if (tableoid != RelationGetRelid(erm->relation))
+ {
+ /* this child is inactive right now */
+ continue;
+ }
+ }
+
/* fetch the whole-row Var for the relation */
datum = ExecGetJunkAttribute(epqstate->origslot,
aerm->wholeAttNo,
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 1994,1999 **** _copyRangeTblEntry(const RangeTblEntry *from)
--- 1994,2000 ----
COPY_NODE_FIELD(eref);
COPY_SCALAR_FIELD(lateral);
COPY_SCALAR_FIELD(inh);
+ COPY_SCALAR_FIELD(hasForeign);
COPY_SCALAR_FIELD(inFromCl);
COPY_SCALAR_FIELD(requiredPerms);
COPY_SCALAR_FIELD(checkAsUser);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 2341,2346 **** _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
--- 2341,2347 ----
COMPARE_NODE_FIELD(eref);
COMPARE_SCALAR_FIELD(lateral);
COMPARE_SCALAR_FIELD(inh);
+ COMPARE_SCALAR_FIELD(hasForeign);
COMPARE_SCALAR_FIELD(inFromCl);
COMPARE_SCALAR_FIELD(requiredPerms);
COMPARE_SCALAR_FIELD(checkAsUser);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 2420,2425 **** _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
--- 2420,2426 ----
WRITE_BOOL_FIELD(lateral);
WRITE_BOOL_FIELD(inh);
+ WRITE_BOOL_FIELD(hasForeign);
WRITE_BOOL_FIELD(inFromCl);
WRITE_UINT_FIELD(requiredPerms);
WRITE_OID_FIELD(checkAsUser);
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 1249,1254 **** _readRangeTblEntry(void)
--- 1249,1255 ----
READ_BOOL_FIELD(lateral);
READ_BOOL_FIELD(inh);
+ READ_BOOL_FIELD(hasForeign);
READ_BOOL_FIELD(inFromCl);
READ_UINT_FIELD(requiredPerms);
READ_OID_FIELD(checkAsUser);
*** a/src/backend/optimizer/prep/preptlist.c
--- b/src/backend/optimizer/prep/preptlist.c
***************
*** 111,116 **** preprocess_targetlist(PlannerInfo *root, List *tlist)
--- 111,118 ----
/* if parent of inheritance tree, need the tableoid too */
if (rc->isParent)
{
+ RangeTblEntry *rte = rt_fetch(rc->rti, parse->rtable);
+
var = makeVar(rc->rti,
TableOidAttributeNumber,
OIDOID,
***************
*** 123,128 **** preprocess_targetlist(PlannerInfo *root, List *tlist)
--- 125,146 ----
pstrdup(resname),
true);
tlist = lappend(tlist, tle);
+
+ /* if including foreign tables, fetch the whole row too */
+ if (rte->hasForeign)
+ {
+ /* Not a table, so we need the whole row as a junk var */
+ var = makeWholeRowVar(rt_fetch(rc->rti, range_table),
+ rc->rti,
+ 0,
+ false);
+ snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId);
+ tle = makeTargetEntry((Expr *) var,
+ list_length(tlist) + 1,
+ pstrdup(resname),
+ true);
+ tlist = lappend(tlist, tle);
+ }
}
}
else
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 1237,1242 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1237,1243 ----
LOCKMODE lockmode;
List *inhOIDs;
List *appinfos;
+ bool hasForeign;
ListCell *l;
/* Does RT entry allow inheritance? */
***************
*** 1309,1314 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1310,1316 ----
/* Scan the inheritance set and expand it */
appinfos = NIL;
+ hasForeign = false;
foreach(l, inhOIDs)
{
Oid childOID = lfirst_oid(l);
***************
*** 1338,1348 **** 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);
--- 1340,1351 ----
/*
* 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);
***************
*** 1388,1400 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
newrc->rti = childRTindex;
newrc->prti = rti;
newrc->rowmarkId = oldrc->rowmarkId;
! newrc->markType = oldrc->markType;
newrc->waitPolicy = oldrc->waitPolicy;
newrc->isParent = false;
root->rowMarks = lappend(root->rowMarks, newrc);
}
/* Close child relations, but keep locks */
if (childOID != parentOID)
heap_close(newrelation, NoLock);
--- 1391,1409 ----
newrc->rti = childRTindex;
newrc->prti = rti;
newrc->rowmarkId = oldrc->rowmarkId;
! if (childrte->relkind == RELKIND_FOREIGN_TABLE)
! newrc->markType = ROW_MARK_COPY;
! else
! newrc->markType = oldrc->markType;
newrc->waitPolicy = oldrc->waitPolicy;
newrc->isParent = false;
root->rowMarks = lappend(root->rowMarks, newrc);
}
+ if (childrte->relkind == RELKIND_FOREIGN_TABLE)
+ hasForeign = true;
+
/* Close child relations, but keep locks */
if (childOID != parentOID)
heap_close(newrelation, NoLock);
***************
*** 1416,1421 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1425,1434 ----
/* Otherwise, OK to add to root->append_rel_list */
root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+
+ /* And mark the parent table as having children that are foreign, if so */
+ if (hasForeign)
+ rte->hasForeign = true;
}
/*
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 4351,4382 **** 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;
}
;
--- 4351,4382 ----
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/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,191 ----
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/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 812,817 **** typedef struct RangeTblEntry
--- 812,818 ----
Alias *eref; /* expanded reference names */
bool lateral; /* subquery, function, or values is LATERAL? */
bool inh; /* inheritance requested? */
+ bool hasForeign; /* does inheritance include foreign tables? */
bool inFromCl; /* present in FROM clause? */
AclMode requiredPerms; /* bitmask of required access permissions */
Oid checkAsUser; /* if valid, check access as this role */
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 819,824 **** typedef enum RowMarkType
--- 819,826 ----
* The tableoid column is only present for an inheritance hierarchy.
* When markType == ROW_MARK_COPY, there is instead a single column named
* wholerow%u whole-row value of relation
+ * The wholerow column is also present for an inheritance hierarchy
+ * in cases where the inheritance hierarchy contains foreign tables.
* In all three cases, %u represents the rowmark ID number (rowmarkId).
* This number is unique within a plan tree, except that child relation
* entries copy their parent's rowmarkId. (Assigning unique numbers
*** a/src/test/regress/expected/foreign_data.out
--- b/src/test/regress/expected/foreign_data.out
***************
*** 1207,1212 **** DROP TRIGGER trigtest_before_row ON foreign_schema.foreign_table_1;
--- 1207,1397 ----
DROP TRIGGER trigtest_after_stmt ON foreign_schema.foreign_table_1;
DROP TRIGGER trigtest_after_row ON foreign_schema.foreign_table_1;
DROP FUNCTION dummy_trigger();
+ -- Table inheritance
+ CREATE TABLE pt1 (ff1 integer, ff2 text);
+ CREATE VIEW view1 AS SELECT 1;
+ ALTER TABLE view1 INHERIT pt1; -- ERROR
+ ERROR: "view1" is not a table or foreign table
+ CREATE FOREIGN TABLE ct1 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ CREATE FOREIGN TABLE ct2 (ff1 integer, ff2 text)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ ALTER FOREIGN TABLE ct1 NO INHERIT pt1;
+ ALTER FOREIGN TABLE ct1 INHERIT pt1;
+ ALTER FOREIGN TABLE ct2 INHERIT pt1;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ ff1 | integer | | plain | |
+ ff2 | text | | extended | |
+ Child tables: ct1,
+ ct2
+
+ \d+ ct1
+ Foreign table "public.ct1"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ \d+ ct2
+ Foreign table "public.ct2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ DROP FOREIGN TABLE ct1;
+ -- cannot change storage modes for foreign tables
+ ALTER TABLE pt1 ALTER COLUMN ff2 SET STORAGE EXTERNAL;
+ CREATE FOREIGN TABLE ct1 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ ff1 | integer | | plain | |
+ ff2 | text | | external | |
+ Child tables: ct1,
+ ct2
+
+ \d+ ct1
+ Foreign table "public.ct1"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ \d+ ct2
+ Foreign table "public.ct2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ DROP FOREIGN TABLE ct1;
+ DROP FOREIGN TABLE ct2;
+ ALTER TABLE pt1 ALTER COLUMN ff2 SET STORAGE EXTENDED;
+ -- connoinherit should be true for NO INHERIT constraint
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk1 CHECK (ff1 > 0) NO INHERIT;
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (ff1 > 10);
+ SELECT pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pgc.connoinherit FROM pg_class AS pc INNER JOIN pg_constraint AS pgc ON (pgc.conrelid = pc.oid) WHERE pc.relname = 'pt1' order by 1,2;
+ relname | conname | contype | conislocal | coninhcount | connoinherit
+ ---------+---------+---------+------------+-------------+--------------
+ pt1 | pt1chk1 | c | t | 0 | t
+ pt1 | pt1chk2 | c | t | 0 | f
+ (2 rows)
+
+ -- child does not inherit NO INHERIT constraints
+ CREATE FOREIGN TABLE ct1 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ CREATE FOREIGN TABLE ct2 (ff1 integer, ff2 text)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ -- child must have parent's INHERIT constraints
+ ALTER FOREIGN TABLE ct2 INHERIT pt1; -- ERROR
+ ERROR: child table is missing constraint "pt1chk2"
+ ALTER FOREIGN TABLE ct2 ADD CONSTRAINT pt1chk2 CHECK (ff1 > 10);
+ ALTER FOREIGN TABLE ct2 INHERIT pt1;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ ff1 | integer | | plain | |
+ ff2 | text | | extended | |
+ Check constraints:
+ "pt1chk1" CHECK (ff1 > 0) NO INHERIT
+ "pt1chk2" CHECK (ff1 > 10)
+ Child tables: ct1,
+ ct2
+
+ \d+ ct1
+ Foreign table "public.ct1"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Check constraints:
+ "pt1chk2" CHECK (ff1 > 10)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ \d+ ct2
+ Foreign table "public.ct2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Check constraints:
+ "pt1chk2" CHECK (ff1 > 10)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk2 CASCADE;
+ -- NOT VALID should be ignored for foreign tables
+ INSERT INTO pt1 VALUES (100, 'pt1');
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (ff1 > 10) NOT VALID;
+ NOTICE: merging constraint "pt1chk2" with inherited definition
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ ff1 | integer | | plain | |
+ ff2 | text | | extended | |
+ Check constraints:
+ "pt1chk1" CHECK (ff1 > 0) NO INHERIT
+ "pt1chk2" CHECK (ff1 > 10) NOT VALID
+ Child tables: ct1,
+ ct2
+
+ \d+ ct1
+ Foreign table "public.ct1"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Check constraints:
+ "pt1chk2" CHECK (ff1 > 10)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ \d+ ct2
+ Foreign table "public.ct2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Check constraints:
+ "pt1chk2" CHECK (ff1 > 10)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- VALIDATE CONSTRAINT should work by ignoring foreign tables
+ ALTER TABLE pt1 VALIDATE CONSTRAINT pt1chk2;
+ -- cannot add an OID system column
+ ALTER TABLE pt1 SET WITH OIDS; -- ERROR
+ ERROR: cannot add OID column to foreign table "ct1"
+ DROP VIEW view1;
+ DROP TABLE pt1 CASCADE;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to foreign table ct1
+ drop cascades to foreign table ct2
-- IMPORT FOREIGN SCHEMA
IMPORT FOREIGN SCHEMA s1 FROM SERVER s9 INTO public; -- ERROR
ERROR: foreign-data wrapper "foo" has no handler
*** a/src/test/regress/sql/foreign_data.sql
--- b/src/test/regress/sql/foreign_data.sql
***************
*** 522,527 **** DROP TRIGGER trigtest_after_row ON foreign_schema.foreign_table_1;
--- 522,590 ----
DROP FUNCTION dummy_trigger();
+ -- Table inheritance
+ CREATE TABLE pt1 (ff1 integer, ff2 text);
+ CREATE VIEW view1 AS SELECT 1;
+ ALTER TABLE view1 INHERIT pt1; -- ERROR
+ CREATE FOREIGN TABLE ct1 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ CREATE FOREIGN TABLE ct2 (ff1 integer, ff2 text)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ ALTER FOREIGN TABLE ct1 NO INHERIT pt1;
+ ALTER FOREIGN TABLE ct1 INHERIT pt1;
+ ALTER FOREIGN TABLE ct2 INHERIT pt1;
+ \d+ pt1
+ \d+ ct1
+ \d+ ct2
+
+ DROP FOREIGN TABLE ct1;
+
+ -- cannot change storage modes for foreign tables
+ ALTER TABLE pt1 ALTER COLUMN ff2 SET STORAGE EXTERNAL;
+ CREATE FOREIGN TABLE ct1 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ \d+ ct1
+ \d+ ct2
+
+ DROP FOREIGN TABLE ct1;
+ DROP FOREIGN TABLE ct2;
+ ALTER TABLE pt1 ALTER COLUMN ff2 SET STORAGE EXTENDED;
+
+ -- connoinherit should be true for NO INHERIT constraint
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk1 CHECK (ff1 > 0) NO INHERIT;
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (ff1 > 10);
+ SELECT pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pgc.connoinherit FROM pg_class AS pc INNER JOIN pg_constraint AS pgc ON (pgc.conrelid = pc.oid) WHERE pc.relname = 'pt1' order by 1,2;
+ -- child does not inherit NO INHERIT constraints
+ CREATE FOREIGN TABLE ct1 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ CREATE FOREIGN TABLE ct2 (ff1 integer, ff2 text)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ -- child must have parent's INHERIT constraints
+ ALTER FOREIGN TABLE ct2 INHERIT pt1; -- ERROR
+ ALTER FOREIGN TABLE ct2 ADD CONSTRAINT pt1chk2 CHECK (ff1 > 10);
+ ALTER FOREIGN TABLE ct2 INHERIT pt1;
+ \d+ pt1
+ \d+ ct1
+ \d+ ct2
+
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk2 CASCADE;
+
+ -- NOT VALID should be ignored for foreign tables
+ INSERT INTO pt1 VALUES (100, 'pt1');
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (ff1 > 10) NOT VALID;
+ \d+ pt1
+ \d+ ct1
+ \d+ ct2
+ -- VALIDATE CONSTRAINT should work by ignoring foreign tables
+ ALTER TABLE pt1 VALIDATE CONSTRAINT pt1chk2;
+
+ -- cannot add an OID system column
+ ALTER TABLE pt1 SET WITH OIDS; -- ERROR
+
+ DROP VIEW view1;
+ DROP TABLE pt1 CASCADE;
+
-- IMPORT FOREIGN SCHEMA
IMPORT FOREIGN SCHEMA s1 FROM SERVER s9 INTO public; -- ERROR
IMPORT FOREIGN SCHEMA s1 LIMIT TO (t1) FROM SERVER s9 INTO public; --ERROR
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-10-16 07:39 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-10-16 07:39 UTC (permalink / raw)
To: Noah Misch <noah@leadboat.com>; +Cc: shigeru.hanada@gmail.com, Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; pgsql-hackers
(2014/08/28 18:00), Etsuro Fujita wrote:
> (2014/08/22 11:51), Noah Misch wrote:
>> Today's ANALYZE VERBOSE messaging for former inheritance parents
>> (tables with
>> relhassubclass = true but no pg_inherits.inhparent links) is
>> deceptive, and I
>> welcome a fix to omit the spurious message. As defects go, this is quite
>> minor. There's fundamentally no value in collecting inheritance tree
>> statistics for such a parent, and no PostgreSQL command will do so.
>> A
>> credible alternative is to emit a second message indicating that we
>> skipped
>> the inheritance tree statistics after all, and why we skipped them.
> I'd like to address this by emitting the second message as shown below:
> A separate patch (analyze.patch) handles the former case in a similar way.
I'll add to the upcoming CF, the analyze.patch as an independent item,
which emits a second message indicating that we skipped the inheritance
tree statistics and why we skipped them.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-patch] analyze.patch (611B, ../../543F7639.300@lab.ntt.co.jp/2-analyze.patch)
download | inline diff:
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 1478,1483 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1478,1487 ----
/* CCI because we already updated the pg_class row in this command */
CommandCounterIncrement();
SetRelationHasSubclass(RelationGetRelid(onerel), false);
+ ereport(elevel,
+ (errmsg("skipping analyze of \"%s.%s\" inheritance tree --- this inheritance tree contains no child tables",
+ get_namespace_name(RelationGetNamespace(onerel)),
+ RelationGetRelationName(onerel))));
return 0;
}
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-10-21 08:40 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 2 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-10-21 08:40 UTC (permalink / raw)
To: Heikki Linnakangas <hlinnakangas@vmware.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: noah@leadboat.com; shigeru.hanada@gmail.com; pgsql-hackers
(2014/10/14 20:00), Etsuro Fujita wrote:
> Here are separated patches.
>
> fdw-chk.patch - CHECK constraints on foreign tables
> fdw-inh.patch - table inheritance with foreign tables
>
> The latter has been created on top of [1].
> [1] http://www.postgresql.org/message-id/540DA168.3040407@lab.ntt.co.jp
To be exact, it has been created on top of [1] and fdw-chk.patch.
I noticed that the latter disallows TRUNCATE on inheritance trees that
contain at least one child foreign table. But I think it would be
better to allow it, with the semantics that we quietly ignore the child
foreign tables and apply the operation to the child plain tables, which
is the same semantics as ALTER COLUMN SET STORAGE on such inheritance
trees. Comments welcome.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-10-24 06:15 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-10-24 06:15 UTC (permalink / raw)
To: Heikki Linnakangas <hlinnakangas@vmware.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: noah@leadboat.com; shigeru.hanada@gmail.com; pgsql-hackers
(2014/10/21 17:40), Etsuro Fujita wrote:
> (2014/10/14 20:00), Etsuro Fujita wrote:
>> Here are separated patches.
>>
>> fdw-chk.patch - CHECK constraints on foreign tables
>> fdw-inh.patch - table inheritance with foreign tables
>>
>> The latter has been created on top of [1].
>
>> [1] http://www.postgresql.org/message-id/540DA168.3040407@lab.ntt.co.jp
>
> To be exact, it has been created on top of [1] and fdw-chk.patch.
>
> I noticed that the latter disallows TRUNCATE on inheritance trees that
> contain at least one child foreign table. But I think it would be
> better to allow it, with the semantics that we quietly ignore the child
> foreign tables and apply the operation to the child plain tables, which
> is the same semantics as ALTER COLUMN SET STORAGE on such inheritance
> trees. Comments welcome.
Done. And I've also a bit revised regression tests for both patches.
Patches attached.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] fdw-chk-2.patch (25.1K, ../../5449EE98.1040704@lab.ntt.co.jp/2-fdw-chk-2.patch)
download | inline diff:
*** a/contrib/file_fdw/input/file_fdw.source
--- b/contrib/file_fdw/input/file_fdw.source
***************
*** 134,139 **** DELETE FROM agg_csv WHERE a = 100;
--- 134,149 ----
-- but this should be ignored
SELECT * FROM agg_csv FOR UPDATE;
+ -- constraint exclusion tests
+ ALTER FOREIGN TABLE agg_csv ADD CONSTRAINT agg_csv_a_check CHECK (a >= 0);
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
+ SET constraint_exclusion = 'on';
+ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
+ SET constraint_exclusion = 'partition';
+ \t off
+ ALTER FOREIGN TABLE agg_csv DROP CONSTRAINT agg_csv_a_check;
+
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/file_fdw/output/file_fdw.source
--- b/contrib/file_fdw/output/file_fdw.source
***************
*** 219,224 **** SELECT * FROM agg_csv FOR UPDATE;
--- 219,242 ----
42 | 324.78
(3 rows)
+ -- constraint exclusion tests
+ ALTER FOREIGN TABLE agg_csv ADD CONSTRAINT agg_csv_a_check CHECK (a >= 0);
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
+ Foreign Scan on public.agg_csv
+ Output: a, b
+ Filter: (agg_csv.a < 0)
+ Foreign File: @abs_srcdir@/data/agg.csv
+
+ SET constraint_exclusion = 'on';
+ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
+ Result
+ Output: a, b
+ One-Time Filter: false
+
+ SET constraint_exclusion = 'partition';
+ \t off
+ ALTER FOREIGN TABLE agg_csv DROP CONSTRAINT agg_csv_a_check;
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 2488,2493 **** select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
--- 2488,2515 ----
(13 rows)
-- ===================================================================
+ -- test constraint exclusion stuff
+ -- ===================================================================
+ ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0);
+ EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 WHERE c2 < 0;
+ QUERY PLAN
+ ------------------------------------------------------------------------------------------
+ Foreign Scan on public.ft1
+ Output: c1, c2, c3, c4, c5, c6, c7, c8
+ Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c2 < 0))
+ (3 rows)
+
+ SET constraint_exclusion = 'on';
+ EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 WHERE c2 < 0;
+ QUERY PLAN
+ ------------------------------------------
+ Result
+ Output: c1, c2, c3, c4, c5, c6, c7, c8
+ One-Time Filter: false
+ (3 rows)
+
+ SET constraint_exclusion = 'partition';
+ -- ===================================================================
-- test serial columns (ie, sequence-based defaults)
-- ===================================================================
create table loc1 (f1 serial, f2 text);
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 387,392 **** select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
--- 387,401 ----
select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
-- ===================================================================
+ -- test constraint exclusion stuff
+ -- ===================================================================
+ ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0);
+ EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 WHERE c2 < 0;
+ SET constraint_exclusion = 'on';
+ EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 WHERE c2 < 0;
+ SET constraint_exclusion = 'partition';
+
+ -- ===================================================================
-- test serial columns (ie, sequence-based defaults)
-- ===================================================================
create table loc1 (f1 serial, f2 text);
*** 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 ] <replaceable class="PARAMETER">name</replaceab
--- 42,49 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ ADD <replaceable class="PARAMETER">table_constraint</replaceable>
+ DROP CONSTRAINT [ IF EXISTS ] <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
DISABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
ENABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
ENABLE REPLICA TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
***************
*** 152,157 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 154,183 ----
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds a new constraint to a table using the same syntax as
+ <xref linkend="SQL-CREATEFOREIGNTABLE">.
+ 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.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DROP CONSTRAINT [ IF EXISTS ]</literal></term>
+ <listitem>
+ <para>
+ This form drops the specified constraint on a table.
+ If <literal>IF EXISTS</literal> is specified and the constraint
+ does not exist, no error is thrown. In this case a notice is issued instead.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>DISABLE</literal>/<literal>ENABLE [ REPLICA | ALWAYS ] TRIGGER</literal></term>
<listitem>
***************
*** 284,289 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 310,333 ----
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="PARAMETER">table_constraint</replaceable></term>
+ <listitem>
+ <para>
+ New table constraint for the table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">constraint_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing constraint to drop.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>CASCADE</literal></term>
<listitem>
***************
*** 365,374 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint is
! added, or a column type is changed with <literal>SET DATA TYPE</>. It is
! the user's responsibility to ensure that the table definition matches the
! remote side.
</para>
<para>
--- 409,418 ----
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint or
! <literal>CHECK</> constraint is added, or a column type is changed with
! <literal>SET DATA TYPE</>. It is the user's responsibility to ensure that
! the table definition matches the remote side.
</para>
<para>
*** a/doc/src/sgml/ref/create_foreign_table.sgml
--- b/doc/src/sgml/ref/create_foreign_table.sgml
***************
*** 19,25 ****
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
SERVER <replaceable class="parameter">server_name</replaceable>
--- 19,26 ----
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
! | <replaceable>table_constraint</replaceable> }
[, ... ]
] )
SERVER <replaceable class="parameter">server_name</replaceable>
***************
*** 30,36 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 31,43 ----
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ NOT NULL |
NULL |
+ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) |
DEFAULT <replaceable>default_expr</replaceable> }
+
+ <phrase>and <replaceable class="PARAMETER">table_constraint</replaceable> is:</phrase>
+
+ [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
+ { CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) }
</synopsis>
</refsynopsisdiv>
***************
*** 138,143 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 145,171 ----
</varlistentry>
<varlistentry>
+ <term><literal>CHECK ( <replaceable class="PARAMETER">expression</replaceable> )</literal></term>
+ <listitem>
+ <para>
+ The <literal>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.
+ </para>
+
+ <para>
+ Currently, <literal>CHECK</literal> expressions cannot contain
+ subqueries nor refer to variables other than columns of the
+ current row. The system column <literal>tableoid</literal>
+ may be referenced, but not any other system column.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>DEFAULT
<replaceable>default_expr</replaceable></literal></term>
<listitem>
***************
*** 187,192 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 215,230 ----
</refsect1>
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ Constraints on foreign tables are not enforced on insert or update.
+ Those definitions simply declare the constraints hold for all rows
+ in the foreign tables. It is the user's responsibility to ensure
+ that those definitions match the remote side.
+ </para>
+ </refsect1>
<refsect1 id="SQL-CREATEFOREIGNTABLE-examples">
<title>Examples</title>
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 2221,2226 **** AddRelationNewConstraints(Relation rel,
--- 2221,2240 ----
if (cdef->contype != CONSTR_CHECK)
continue;
+ /* Don't allow NO INHERIT for foreign tables */
+ if (cdef->is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("CHECK constraints on foreign tables cannot be marked NO INHERIT")));
+
+ /* Don't allow NOT VALID for foreign tables */
+ if (cdef->skip_validation &&
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("NOT VALID is not supported for CHECK constraints on foreign tables")));
+
if (cdef->raw_expr != NULL)
{
Assert(cdef->cooked_expr == NULL);
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 477,486 **** 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,
--- 477,482 ----
***************
*** 3142,3148 **** 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)
--- 3138,3144 ----
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)
***************
*** 3156,3162 **** 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)
--- 3152,3158 ----
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)
*** 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/test/regress/expected/foreign_data.out
--- b/src/test/regress/expected/foreign_data.out
***************
*** 670,676 **** LINE 1: CREATE FOREIGN TABLE ft1 () SERVER s0 WITH OIDS;
^
CREATE FOREIGN TABLE ft1 (
c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
! c2 text OPTIONS (param2 'val2', param3 'val3'),
c3 date
) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
COMMENT ON FOREIGN TABLE ft1 IS 'ft1';
--- 670,682 ----
^
CREATE FOREIGN TABLE ft1 (
c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
! c2 text OPTIONS (param2 'val2', param3 'val3') CHECK (c2 <> '') NO INHERIT,
! c3 date
! ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
! ERROR: CHECK constraints on foreign tables cannot be marked NO INHERIT
! CREATE FOREIGN TABLE ft1 (
! c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
! c2 text OPTIONS (param2 'val2', param3 'val3') CHECK (c2 <> ''),
c3 date
) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
COMMENT ON FOREIGN TABLE ft1 IS 'ft1';
***************
*** 682,687 **** COMMENT ON COLUMN ft1.c1 IS 'ft1.c1';
--- 688,695 ----
c1 | integer | not null | ("param 1" 'val1') | plain | | ft1.c1
c2 | text | | (param2 'val2', param3 'val3') | extended | |
c3 | date | | | plain | |
+ Check constraints:
+ "ft1_c2_check" CHECK (c2 <> ''::text)
Server: s0
FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
***************
*** 740,745 **** ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET STATISTICS -1;
--- 748,755 ----
c8 | text | | (p2 'V2') | extended | |
c9 | integer | | | plain | |
c10 | integer | | (p1 'v1') | plain | |
+ Check constraints:
+ "ft1_c2_check" CHECK (c2 <> ''::text)
Server: s0
FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
***************
*** 748,763 **** 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;
--- 758,775 ----
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) NO INHERIT; -- ERROR
! ERROR: CHECK constraints on foreign tables cannot be marked NO INHERIT
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0) NOT VALID; -- ERROR
! ERROR: NOT VALID is not supported for CHECK constraints on foreign tables
! 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_c1_check;
! ERROR: constraint "ft1_c1_check" of relation "ft1" does not exist
! 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;
***************
*** 785,790 **** ALTER FOREIGN TABLE foreign_schema.ft1 RENAME TO foreign_table_1;
--- 797,804 ----
c7 | integer | | (p1 'v1', p2 'v2')
c8 | text | | (p2 'V2')
c10 | integer | | (p1 'v1')
+ Check constraints:
+ "ft1_c2_check" CHECK (c2 <> ''::text)
Server: s0
FDW Options: (quote '~', "be quoted" 'value', escape '@')
*** a/src/test/regress/sql/foreign_data.sql
--- b/src/test/regress/sql/foreign_data.sql
***************
*** 270,276 **** CREATE FOREIGN TABLE ft1 () SERVER no_server; -- ERROR
CREATE FOREIGN TABLE ft1 () SERVER s0 WITH OIDS; -- ERROR
CREATE FOREIGN TABLE ft1 (
c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
! c2 text OPTIONS (param2 'val2', param3 'val3'),
c3 date
) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
COMMENT ON FOREIGN TABLE ft1 IS 'ft1';
--- 270,281 ----
CREATE FOREIGN TABLE ft1 () SERVER s0 WITH OIDS; -- ERROR
CREATE FOREIGN TABLE ft1 (
c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
! c2 text OPTIONS (param2 'val2', param3 'val3') CHECK (c2 <> '') NO INHERIT,
! c3 date
! ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
! CREATE FOREIGN TABLE ft1 (
! c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
! c2 text OPTIONS (param2 'val2', param3 'val3') CHECK (c2 <> ''),
c3 date
) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
COMMENT ON FOREIGN TABLE ft1 IS 'ft1';
***************
*** 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 '@');
--- 319,331 ----
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) NO INHERIT; -- ERROR
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0) NOT VALID; -- 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 '@');
[text/x-diff] fdw-inh-2.patch (87.6K, ../../5449EE98.1040704@lab.ntt.co.jp/3-fdw-inh-2.patch)
download | inline diff:
*** a/contrib/file_fdw/input/file_fdw.source
--- b/contrib/file_fdw/input/file_fdw.source
***************
*** 144,149 **** SET constraint_exclusion = 'partition';
--- 144,166 ----
\t off
ALTER FOREIGN TABLE agg_csv DROP CONSTRAINT agg_csv_a_check;
+ -- table inheritance tests
+ CREATE TABLE agg (a int2, b float4);
+ ALTER FOREIGN TABLE agg_csv INHERIT agg;
+ ALTER FOREIGN TABLE agg_text INHERIT agg;
+ SELECT * FROM agg WHERE b < 10.0 ORDER BY a, b;
+ SELECT * FROM ONLY agg WHERE b < 10.0 ORDER BY a, b;
+ SELECT * FROM agg_csv WHERE b < 10.0 ORDER BY a, b;
+ SELECT * FROM agg_text WHERE b < 10.0 ORDER BY a, b;
+ -- updates aren't supported
+ UPDATE agg SET a = 1;
+ DELETE FROM agg WHERE a = 100;
+ -- but this should be ignored
+ SELECT * FROM agg WHERE b < 10.0 ORDER BY a, b FOR UPDATE;
+ ALTER FOREIGN TABLE agg_csv NO INHERIT agg;
+ ALTER FOREIGN TABLE agg_text NO INHERIT agg;
+ DROP TABLE agg;
+
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/file_fdw/output/file_fdw.source
--- b/contrib/file_fdw/output/file_fdw.source
***************
*** 237,242 **** EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
--- 237,289 ----
SET constraint_exclusion = 'partition';
\t off
ALTER FOREIGN TABLE agg_csv DROP CONSTRAINT agg_csv_a_check;
+ -- table inheritance tests
+ CREATE TABLE agg (a int2, b float4);
+ ALTER FOREIGN TABLE agg_csv INHERIT agg;
+ ALTER FOREIGN TABLE agg_text INHERIT agg;
+ SELECT * FROM agg WHERE b < 10.0 ORDER BY a, b;
+ a | b
+ ----+---------
+ 0 | 0.09561
+ 0 | 0.09561
+ 56 | 7.8
+ (3 rows)
+
+ SELECT * FROM ONLY agg WHERE b < 10.0 ORDER BY a, b;
+ a | b
+ ---+---
+ (0 rows)
+
+ SELECT * FROM agg_csv WHERE b < 10.0 ORDER BY a, b;
+ a | b
+ ---+---------
+ 0 | 0.09561
+ (1 row)
+
+ SELECT * FROM agg_text WHERE b < 10.0 ORDER BY a, b;
+ a | b
+ ----+---------
+ 0 | 0.09561
+ 56 | 7.8
+ (2 rows)
+
+ -- updates aren't supported
+ UPDATE agg SET a = 1;
+ ERROR: cannot update foreign table "agg_text"
+ DELETE FROM agg WHERE a = 100;
+ ERROR: cannot delete from foreign table "agg_text"
+ -- but this should be ignored
+ SELECT * FROM agg WHERE b < 10.0 ORDER BY a, b FOR UPDATE;
+ a | b
+ ----+---------
+ 0 | 0.09561
+ 0 | 0.09561
+ 56 | 7.8
+ (3 rows)
+
+ ALTER FOREIGN TABLE agg_csv NO INHERIT agg;
+ ALTER FOREIGN TABLE agg_text NO INHERIT agg;
+ DROP TABLE agg;
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 2863,2868 **** NOTICE: NEW: (13,"test triggered !")
--- 2863,3376 ----
(1 row)
-- ===================================================================
+ -- test inheritance features
+ -- ===================================================================
+ CREATE TABLE a (aa TEXT);
+ CREATE TABLE loct (aa TEXT, bb TEXT);
+ CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a)
+ SERVER loopback OPTIONS (table_name 'loct');
+ INSERT INTO a(aa) VALUES('aaa');
+ INSERT INTO a(aa) VALUES('aaaa');
+ INSERT INTO a(aa) VALUES('aaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaaa');
+ INSERT INTO b(aa) VALUES('bbb');
+ INSERT INTO b(aa) VALUES('bbbb');
+ INSERT INTO b(aa) VALUES('bbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbbb');
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | aaa
+ a | aaaa
+ a | aaaaa
+ a | aaaaaa
+ a | aaaaaaa
+ a | aaaaaaaa
+ b | bbb
+ b | bbbb
+ b | bbbbb
+ b | bbbbbb
+ b | bbbbbbb
+ b | bbbbbbbb
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----------+----
+ b | bbb |
+ b | bbbb |
+ b | bbbbb |
+ b | bbbbbb |
+ b | bbbbbbb |
+ b | bbbbbbbb |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | aaa
+ a | aaaa
+ a | aaaaa
+ a | aaaaaa
+ a | aaaaaaa
+ a | aaaaaaaa
+ (6 rows)
+
+ UPDATE a SET aa='zzzzzz' WHERE aa LIKE 'aaa%';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ b | bbb
+ b | bbbb
+ b | bbbbb
+ b | bbbbbb
+ b | bbbbbbb
+ b | bbbbbbbb
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----------+----
+ b | bbb |
+ b | bbbb |
+ b | bbbbb |
+ b | bbbbbb |
+ b | bbbbbbb |
+ b | bbbbbbbb |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ (6 rows)
+
+ UPDATE b SET aa='new';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+-----+----
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ (6 rows)
+
+ UPDATE a SET aa='new';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+-----
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+-----+----
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+-----
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ (6 rows)
+
+ DELETE FROM a;
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----
+ (0 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----+----
+ (0 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----
+ (0 rows)
+
+ DROP TABLE a CASCADE;
+ NOTICE: drop cascades to foreign table b
+ DROP TABLE loct;
+ -- Check SELECT FOR UPDATE/SHARE with an inherited source table
+ create table loct1 (f1 int, f2 int, f3 int);
+ create table loct2 (f1 int, f2 int, f3 int);
+ create table foo (f1 int, f2 int);
+ create foreign table foo2 (f3 int) inherits (foo)
+ server loopback options (table_name 'loct1');
+ create table bar (f1 int, f2 int);
+ create foreign table bar2 (f3 int) inherits (bar)
+ server loopback options (table_name 'loct2');
+ insert into foo values(1,1);
+ insert into foo values(3,3);
+ insert into foo2 values(2,2,2);
+ insert into foo2 values(3,3,3);
+ insert into bar values(1,1);
+ insert into bar values(2,2);
+ insert into bar values(3,3);
+ insert into bar values(4,4);
+ insert into bar2 values(1,1,1);
+ insert into bar2 values(2,2,2);
+ insert into bar2 values(3,3,3);
+ insert into bar2 values(4,4,4);
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for update;
+ QUERY PLAN
+ ----------------------------------------------------------------------------------------------
+ LockRows
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ -> Hash Join
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Append
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.ctid, bar2.tableoid, bar2.*
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (22 rows)
+
+ select * from bar where f1 in (select f1 from foo) for update;
+ f1 | f2
+ ----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ (6 rows)
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for share;
+ QUERY PLAN
+ ----------------------------------------------------------------------------------------------
+ LockRows
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ -> Hash Join
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Append
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.ctid, bar2.tableoid, bar2.*
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (22 rows)
+
+ select * from bar where f1 in (select f1 from foo) for share;
+ f1 | f2
+ ----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ (6 rows)
+
+ -- Check UPDATE with inherited target and an inherited source table
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ QUERY PLAN
+ ---------------------------------------------------------------------------------------------
+ Update on public.bar
+ -> Hash Join
+ Output: bar.f1, (bar.f2 + 100), bar.ctid, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ -> Hash Join
+ Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar2.f1 = foo.f1)
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (34 rows)
+
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+ relname | f1 | f2
+ ---------+----+-----
+ bar | 1 | 101
+ bar | 2 | 102
+ bar | 3 | 103
+ bar | 4 | 4
+ bar2 | 1 | 101
+ bar2 | 2 | 102
+ bar2 | 3 | 103
+ bar2 | 4 | 4
+ (8 rows)
+
+ -- Check UPDATE with inherited target and an appendrel subquery
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ QUERY PLAN
+ --------------------------------------------------------------------------------------
+ Update on public.bar
+ -> Hash Join
+ Output: bar.f1, (bar.f2 + 100), bar.ctid, (ROW(foo.f1))
+ Hash Cond: (foo.f1 = bar.f1)
+ -> Append
+ -> Seq Scan on public.foo
+ Output: ROW(foo.f1), foo.f1
+ -> Foreign Scan on public.foo2
+ Output: ROW(foo2.f1), foo2.f1
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Seq Scan on public.foo foo_1
+ Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3)
+ -> Foreign Scan on public.foo2 foo2_1
+ Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3)
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Hash
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Merge Join
+ Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, (ROW(foo.f1))
+ Merge Cond: (bar2.f1 = foo.f1)
+ -> Sort
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Sort Key: bar2.f1
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Sort
+ Output: (ROW(foo.f1)), foo.f1
+ Sort Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: ROW(foo.f1), foo.f1
+ -> Foreign Scan on public.foo2
+ Output: ROW(foo2.f1), foo2.f1
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Seq Scan on public.foo foo_1
+ Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3)
+ -> Foreign Scan on public.foo2 foo2_1
+ Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3)
+ Remote SQL: SELECT f1 FROM public.loct1
+ (42 rows)
+
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+ relname | f1 | f2
+ ---------+----+-----
+ bar | 1 | 201
+ bar | 2 | 202
+ bar | 3 | 203
+ bar | 4 | 104
+ bar2 | 1 | 201
+ bar2 | 2 | 202
+ bar2 | 3 | 203
+ bar2 | 4 | 104
+ (8 rows)
+
+ drop table foo cascade;
+ NOTICE: drop cascades to foreign table foo2
+ drop table bar cascade;
+ NOTICE: drop cascades to foreign table bar2
+ drop table loct1;
+ drop table loct2;
+ -- Test that WHERE CURRENT OF is not supported
+ create table ltbl (a int, b text);
+ create table ptbl (a int, b text);
+ create table locc () inherits (ptbl);
+ insert into locc values(1, 'foo');
+ create foreign table remc () inherits (ptbl)
+ server loopback options (table_name 'ltbl');
+ insert into remc values(2, 'bar');
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'foo';
+ fetch from c;
+ ?column?
+ ----------
+ 1
+ (1 row)
+
+ update ptbl set b = null where current of c;
+ ERROR: WHERE CURRENT OF is not supported for this table type
+ rollback;
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'bar';
+ fetch from c;
+ ?column?
+ ----------
+ 1
+ (1 row)
+
+ update ptbl set b = null where current of c;
+ ERROR: WHERE CURRENT OF is not supported for this table type
+ rollback;
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ -- Test TRUNCATE
+ truncate ptbl;
+ select * from ptbl;
+ a | b
+ ---+-----
+ 2 | bar
+ (1 row)
+
+ select * from only ptbl;
+ a | b
+ ---+---
+ (0 rows)
+
+ select * from locc;
+ a | b
+ ---+---
+ (0 rows)
+
+ select * from remc;
+ a | b
+ ---+-----
+ 2 | bar
+ (1 row)
+
+ truncate remc; -- ERROR
+ ERROR: "remc" is not a table
+ drop table ptbl cascade;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to table locc
+ drop cascades to foreign table remc
+ drop table ltbl;
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
CREATE SCHEMA import_source;
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 823,828 **** postgresGetForeignPlan(PlannerInfo *root,
--- 823,839 ----
{
RowMarkClause *rc = get_parse_rowmark(root->parse, baserel->relid);
+ if (rc == NULL)
+ {
+ PlanRowMark *prm = get_plan_rowmark(root->rowMarks, baserel->relid);
+
+ if (prm)
+ {
+ if (prm->rti != prm->prti)
+ rc = get_parse_rowmark(root->parse, prm->prti);
+ }
+ }
+
if (rc)
{
/*
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 626,631 **** UPDATE rem1 SET f2 = 'testo';
--- 626,782 ----
INSERT INTO rem1(f2) VALUES ('test') RETURNING ctid;
-- ===================================================================
+ -- test inheritance features
+ -- ===================================================================
+
+ CREATE TABLE a (aa TEXT);
+ CREATE TABLE loct (aa TEXT, bb TEXT);
+ CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a)
+ SERVER loopback OPTIONS (table_name 'loct');
+
+ INSERT INTO a(aa) VALUES('aaa');
+ INSERT INTO a(aa) VALUES('aaaa');
+ INSERT INTO a(aa) VALUES('aaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaaa');
+
+ INSERT INTO b(aa) VALUES('bbb');
+ INSERT INTO b(aa) VALUES('bbbb');
+ INSERT INTO b(aa) VALUES('bbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbbb');
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE a SET aa='zzzzzz' WHERE aa LIKE 'aaa%';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE b SET aa='new';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE a SET aa='new';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ DELETE FROM a;
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ DROP TABLE a CASCADE;
+ DROP TABLE loct;
+
+ -- Check SELECT FOR UPDATE/SHARE with an inherited source table
+ create table loct1 (f1 int, f2 int, f3 int);
+ create table loct2 (f1 int, f2 int, f3 int);
+
+ create table foo (f1 int, f2 int);
+ create foreign table foo2 (f3 int) inherits (foo)
+ server loopback options (table_name 'loct1');
+ create table bar (f1 int, f2 int);
+ create foreign table bar2 (f3 int) inherits (bar)
+ server loopback options (table_name 'loct2');
+
+ insert into foo values(1,1);
+ insert into foo values(3,3);
+ insert into foo2 values(2,2,2);
+ insert into foo2 values(3,3,3);
+ insert into bar values(1,1);
+ insert into bar values(2,2);
+ insert into bar values(3,3);
+ insert into bar values(4,4);
+ insert into bar2 values(1,1,1);
+ insert into bar2 values(2,2,2);
+ insert into bar2 values(3,3,3);
+ insert into bar2 values(4,4,4);
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for update;
+ select * from bar where f1 in (select f1 from foo) for update;
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for share;
+ select * from bar where f1 in (select f1 from foo) for share;
+
+ -- Check UPDATE with inherited target and an inherited source table
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+
+ -- Check UPDATE with inherited target and an appendrel subquery
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+
+ drop table foo cascade;
+ drop table bar cascade;
+ drop table loct1;
+ drop table loct2;
+
+ -- Test that WHERE CURRENT OF is not supported
+ create table ltbl (a int, b text);
+
+ create table ptbl (a int, b text);
+ create table locc () inherits (ptbl);
+ insert into locc values(1, 'foo');
+
+ create foreign table remc () inherits (ptbl)
+ server loopback options (table_name 'ltbl');
+ insert into remc values(2, 'bar');
+
+ select * from ptbl;
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'foo';
+ fetch from c;
+ update ptbl set b = null where current of c;
+ rollback;
+
+ select * from ptbl;
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'bar';
+ fetch from c;
+ update ptbl set b = null where current of c;
+ rollback;
+
+ select * from ptbl;
+
+ -- Test TRUNCATE
+ truncate ptbl;
+ select * from ptbl;
+ select * from only ptbl;
+ select * from locc;
+ select * from remc;
+ truncate remc; -- ERROR
+
+ drop table ptbl cascade;
+ drop table ltbl;
+
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
*** a/doc/src/sgml/ddl.sgml
--- b/doc/src/sgml/ddl.sgml
***************
*** 2503,2508 **** VALUES ('Albany', NULL, NULL, 'NY');
--- 2503,2526 ----
further privileges to be granted.
</para>
+ <para>
+ Note that a foreign table can also inherit from more than one parent
+ table (see <xref linkend="ddl-foreign-data">).
+ Like normal tables, table inheritance is typically established when
+ the foreign table is created, using the <literal>INHERITS</> clause of
+ the <xref linkend="sql-createforeigntable"> statement. Alternatively,
+ a foreign table which is already defined in a compatible way can have
+ a new parent relationship added, using the <literal>INHERIT</literal>
+ variant of <xref linkend="sql-alterforeigntable">.
+ Similarly an inheritance link can be removed from a child using the
+ <literal>NO INHERIT</literal> variant of <command>ALTER FOREIGN TABLE</>.
+ <command>CREATE FOREIGN TABLE</command> and
+ <command>ALTER FOREIGN TABLE</command> follow the same rules for
+ duplicate column merging and rejection that apply during
+ <command>CREATE TABLE</command> and <command>ALTER TABLE</command>,
+ respectively.
+ </para>
+
<sect2 id="ddl-inherit-caveats">
<title>Caveats</title>
***************
*** 2650,2656 **** VALUES ('Albany', NULL, NULL, 'NY');
table of a single parent table. The parent table itself is normally
empty; it exists just to represent the entire data set. You should be
familiar with inheritance (see <xref linkend="ddl-inherit">) before
! attempting to set up partitioning.
</para>
<para>
--- 2668,2677 ----
table of a single parent table. The parent table itself is normally
empty; it exists just to represent the entire data set. You should be
familiar with inheritance (see <xref linkend="ddl-inherit">) before
! attempting to set up partitioning. (The setup and management of
! partitioned tables illustrated in this section assume that each
! partition is a normal table. However, you can do that in a similar way
! for cases where some or all partitions are foreign tables.)
</para>
<para>
***************
*** 2713,2720 **** VALUES ('Albany', NULL, NULL, 'NY');
</para>
<para>
! We will refer to the child tables as partitions, though they
! are in every way normal <productname>PostgreSQL</> tables.
</para>
</listitem>
--- 2734,2741 ----
</para>
<para>
! We will refer to the child tables as partitions, though we assume
! that they are normal <productname>PostgreSQL</> tables.
</para>
</listitem>
*** a/doc/src/sgml/ref/alter_foreign_table.sgml
--- b/doc/src/sgml/ref/alter_foreign_table.sgml
***************
*** 48,53 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 48,55 ----
ENABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
ENABLE REPLICA TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
ENABLE ALWAYS TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 190,195 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 192,217 ----
</varlistentry>
<varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OWNER</literal></term>
<listitem>
<para>
***************
*** 380,385 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 402,417 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">new_owner</replaceable></term>
<listitem>
<para>
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 1010,1015 **** ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
--- 1010,1022 ----
</para>
<para>
+ If a table has any descendant tables that are foreign, a recursive
+ <literal>SET STORAGE</literal> operation will be rejected since it
+ is not permitted to add an <literal>oid</literal> system column to
+ foreign tables.
+ </para>
+
+ <para>
The <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>,
and <literal>TABLESPACE</> actions never recurse to descendant tables;
that is, they always act as though <literal>ONLY</> were specified.
***************
*** 1018,1023 **** ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
--- 1025,1043 ----
</para>
<para>
+ When adding a <literal>CHECK</> constraint with the <literal>NOT VALID
+ </literal> option recursively, an inherited constraint on a descendant
+ table that is foreign will be marked valid without checking
+ consistency with the foreign server.
+ </para>
+
+ <para>
+ A recursive <literal>SET STORAGE</literal> operation will make the
+ storage mode for a descendant table's column unchanged if the table is
+ foreign.
+ </para>
+
+ <para>
Changing any part of a system catalog table is not permitted.
</para>
*** a/doc/src/sgml/ref/analyze.sgml
--- b/doc/src/sgml/ref/analyze.sgml
***************
*** 200,205 **** ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
--- 200,212 ----
</para>
<para>
+ 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
+ <command>ANALYZE</command>, the command prints a warning and does nothing.
+ </para>
+
+ <para>
If the table being analyzed is completely empty, <command>ANALYZE</command>
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
***************
*** 23,28 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 23,29 ----
| <replaceable>table_constraint</replaceable> }
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 187,192 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 188,205 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for the
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
***************
*** 223,228 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 236,250 ----
Those definitions simply declare the constraints hold for all rows
in the foreign tables. It is the user's responsibility to ensure
that those definitions match the remote side.
+ Those constraints are used in some kind of query optimization such
+ as constraint exclusion for partitioned tables (see
+ <xref linkend="ddl-partitioning">).
+ </para>
+
+ <para>
+ Since it is not permitted to add an <literal>oid</> system column to
+ foreign tables, the command will be rejected if any parent tables
+ have <literal>oid</> system columns.
</para>
</refsect1>
*** 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;
***************
*** 102,107 **** static int acquire_sample_rows(Relation onerel, int elevel,
--- 103,109 ----
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
static int compare_rows(const void *a, const void *b);
+ static bool has_foreign(Oid parentrelId, List *tableOIDs);
static int acquire_inherited_sample_rows(Relation onerel, int elevel,
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
***************
*** 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;
--- 117,126 ----
* 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)
--- 134,140 ----
elevel = DEBUG2;
/* Set up static variables */
+ vac_mode = vacmode;
vac_strategy = bstrategy;
/*
***************
*** 294,302 **** analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
* do_analyze_rel() -- analyze one relation, recursively or not
*
* Note that "acquirefunc" is only relevant for the non-inherited case.
! * If we supported foreign tables in inheritance trees,
! * acquire_inherited_sample_rows would need to determine the appropriate
! * acquirefunc for each child table.
*/
static void
do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
--- 300,307 ----
* do_analyze_rel() -- analyze one relation, recursively or not
*
* Note that "acquirefunc" is only relevant for the non-inherited case.
! * For the inherited case, acquire_inherited_sample_rows determines the
! * appropriate acquirefunc for each child table.
*/
static void
do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
***************
*** 1439,1449 **** compare_rows(const void *a, const void *b)
/*
* acquire_inherited_sample_rows -- acquire sample rows from inheritance tree
*
* This has the same API as acquire_sample_rows, except that rows are
* collected from all inheritance children as well as the specified table.
! * We fail and return zero if there are no inheritance children.
*/
static int
acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1444,1490 ----
/*
+ * Check wether to contain at least one foreign table
+ */
+ static bool
+ has_foreign(Oid parentrelId, List *tableOIDs)
+ {
+ bool result;
+ ListCell *lc;
+
+ /* There are no children */
+ if (list_length(tableOIDs) < 2)
+ return false;
+
+ result = false;
+ 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, NoLock);
+ }
+ return result;
+ }
+
+ /*
* acquire_inherited_sample_rows -- acquire sample rows from inheritance tree
*
* This has the same API as acquire_sample_rows, except that rows are
* collected from all inheritance children as well as the specified table.
! * We fail and return zero if there are no inheritance children or there are
! * inheritance children that foreign tables.
*/
static int
acquire_inherited_sample_rows(Relation onerel, int elevel,
***************
*** 1452,1457 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1493,1499 ----
{
List *tableOIDs;
Relation *rels;
+ AcquireSampleRowsFunc *acquirefunc;
double *relblocks;
double totalblocks;
int numrows,
***************
*** 1482,1491 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1524,1554 ----
}
/*
+ * If we are not in VAC_MODE_SINGLE case and the inheritance set contains
+ * at least one foreign table, then fail.
+ */
+ if (vac_mode != VAC_MODE_SINGLE)
+ {
+ if (has_foreign(RelationGetRelid(onerel), tableOIDs))
+ {
+ ereport(elevel,
+ (errmsg("skipping analyze of \"%s.%s\" inheritance tree --- this inheritance tree contains foreign tables",
+ get_namespace_name(RelationGetNamespace(onerel)),
+ RelationGetRelationName(onerel)),
+ errhint("Try ANALYZE \"%s.%s\" for the inheritance statistics.",
+ get_namespace_name(RelationGetNamespace(onerel)),
+ RelationGetRelationName(onerel))));
+ return 0;
+ }
+ }
+
+ /*
* Count the blocks in all the relations. The result could overflow
* 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++;
}
--- 1570,1605 ----
}
rels[nrels] = childrel;
!
! if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
! {
! FdwRoutine *fdwroutine;
! BlockNumber relpages = 0;
! bool ok = false;
!
! /* 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,
--- 1617,1623 ----
{
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 &&
--- 1633,1644 ----
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
***************
*** 315,321 **** 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,
--- 315,322 ----
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,
***************
*** 566,571 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
--- 567,596 ----
stmt->relation->relpersistence,
&inheritOids, &old_constraints, &parentOidCount);
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * Don't allow a foreign table to inherit from parents that have OID
+ * system columns.
+ */
+ if (parentOidCount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot inherit from relation with OIDs")));
+
+ /*
+ * Reset the storage parameter for inherited attributes that have
+ * non-default values.
+ */
+ foreach(listptr, schema)
+ {
+ ColumnDef *colDef = lfirst(listptr);
+
+ if (colDef->storage != 0)
+ colDef->storage = 0;
+ }
+ }
+
/*
* Create a tuple descriptor from the relation schema. Note that this
* deals with column names, types, and NOT NULL constraints, but not
***************
*** 1023,1028 **** ExecuteTruncate(TruncateStmt *stmt)
--- 1048,1059 ----
/* find_all_inheritors already got lock */
rel = heap_open(childrelid, NoLock);
+ /* ignore if the rel is a foreign table */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ heap_close(rel, NoLock);
+ continue;
+ }
truncate_check_rel(rel);
rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
***************
*** 3090,3113 **** 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;
--- 3121,3148 ----
* 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;
***************
*** 3120,3126 **** 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;
--- 3155,3162 ----
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;
***************
*** 3242,3252 **** 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;
--- 3278,3294 ----
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);
pass = AT_PASS_MISC;
***************
*** 3280,3286 **** 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 */
case AT_EnableRowSecurity:
--- 3322,3327 ----
***************
*** 4274,4280 **** 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
--- 4315,4322 ----
*/
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
***************
*** 4302,4309 **** 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);
}
}
--- 4344,4355 ----
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);
}
}
***************
*** 4593,4599 **** 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);
--- 4639,4645 ----
/* 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);
***************
*** 4889,4894 **** ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4935,4945 ----
/* 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 */
***************
*** 5489,5495 **** 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
--- 5540,5546 ----
/* 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
***************
*** 5881,5887 **** 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
--- 5932,5938 ----
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/*
* Call AddRelationNewConstraints to do the work, making sure it works on
***************
*** 5892,5900 **** 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 */
--- 5943,5959 ----
* 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 */
***************
*** 7381,7387 **** 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);
--- 7440,7446 ----
/* 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);
***************
*** 7716,7722 **** 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,
--- 7775,7784 ----
* 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;
***************
*** 149,154 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
--- 150,169 ----
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.
*/
***************
*** 251,257 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
PushActiveSnapshot(GetTransactionSnapshot());
}
! analyze_rel(relid, vacstmt, vac_strategy);
if (use_own_xacts)
{
--- 266,272 ----
PushActiveSnapshot(GetTransactionSnapshot());
}
! analyze_rel(relid, vacstmt, vac_mode, vac_strategy);
if (use_own_xacts)
{
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 2308,2313 **** EvalPlanQualFetchRowMarks(EPQState *epqstate)
--- 2308,2333 ----
Assert(erm->markType == ROW_MARK_COPY);
+ /* if child rel, must check whether it produced this row */
+ if (erm->rti != erm->prti)
+ {
+ Oid tableoid;
+
+ datum = ExecGetJunkAttribute(epqstate->origslot,
+ aerm->toidAttNo,
+ &isNull);
+ /* non-locked rels could be on the inside of outer joins */
+ if (isNull)
+ continue;
+ tableoid = DatumGetObjectId(datum);
+
+ if (tableoid != RelationGetRelid(erm->relation))
+ {
+ /* this child is inactive right now */
+ continue;
+ }
+ }
+
/* fetch the whole-row Var for the relation */
datum = ExecGetJunkAttribute(epqstate->origslot,
aerm->wholeAttNo,
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 1994,1999 **** _copyRangeTblEntry(const RangeTblEntry *from)
--- 1994,2000 ----
COPY_NODE_FIELD(eref);
COPY_SCALAR_FIELD(lateral);
COPY_SCALAR_FIELD(inh);
+ COPY_SCALAR_FIELD(hasForeign);
COPY_SCALAR_FIELD(inFromCl);
COPY_SCALAR_FIELD(requiredPerms);
COPY_SCALAR_FIELD(checkAsUser);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 2341,2346 **** _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
--- 2341,2347 ----
COMPARE_NODE_FIELD(eref);
COMPARE_SCALAR_FIELD(lateral);
COMPARE_SCALAR_FIELD(inh);
+ COMPARE_SCALAR_FIELD(hasForeign);
COMPARE_SCALAR_FIELD(inFromCl);
COMPARE_SCALAR_FIELD(requiredPerms);
COMPARE_SCALAR_FIELD(checkAsUser);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 2420,2425 **** _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
--- 2420,2426 ----
WRITE_BOOL_FIELD(lateral);
WRITE_BOOL_FIELD(inh);
+ WRITE_BOOL_FIELD(hasForeign);
WRITE_BOOL_FIELD(inFromCl);
WRITE_UINT_FIELD(requiredPerms);
WRITE_OID_FIELD(checkAsUser);
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 1249,1254 **** _readRangeTblEntry(void)
--- 1249,1255 ----
READ_BOOL_FIELD(lateral);
READ_BOOL_FIELD(inh);
+ READ_BOOL_FIELD(hasForeign);
READ_BOOL_FIELD(inFromCl);
READ_UINT_FIELD(requiredPerms);
READ_OID_FIELD(checkAsUser);
*** a/src/backend/optimizer/prep/preptlist.c
--- b/src/backend/optimizer/prep/preptlist.c
***************
*** 111,116 **** preprocess_targetlist(PlannerInfo *root, List *tlist)
--- 111,118 ----
/* if parent of inheritance tree, need the tableoid too */
if (rc->isParent)
{
+ RangeTblEntry *rte = rt_fetch(rc->rti, parse->rtable);
+
var = makeVar(rc->rti,
TableOidAttributeNumber,
OIDOID,
***************
*** 123,128 **** preprocess_targetlist(PlannerInfo *root, List *tlist)
--- 125,146 ----
pstrdup(resname),
true);
tlist = lappend(tlist, tle);
+
+ /* if including foreign tables, fetch the whole row too */
+ if (rte->hasForeign)
+ {
+ /* Not a table, so we need the whole row as a junk var */
+ var = makeWholeRowVar(rt_fetch(rc->rti, range_table),
+ rc->rti,
+ 0,
+ false);
+ snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId);
+ tle = makeTargetEntry((Expr *) var,
+ list_length(tlist) + 1,
+ pstrdup(resname),
+ true);
+ tlist = lappend(tlist, tle);
+ }
}
}
else
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 1237,1242 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1237,1243 ----
LOCKMODE lockmode;
List *inhOIDs;
List *appinfos;
+ bool hasForeign;
ListCell *l;
/* Does RT entry allow inheritance? */
***************
*** 1309,1314 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1310,1316 ----
/* Scan the inheritance set and expand it */
appinfos = NIL;
+ hasForeign = false;
foreach(l, inhOIDs)
{
Oid childOID = lfirst_oid(l);
***************
*** 1338,1348 **** 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);
--- 1340,1351 ----
/*
* 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);
***************
*** 1388,1400 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
newrc->rti = childRTindex;
newrc->prti = rti;
newrc->rowmarkId = oldrc->rowmarkId;
! newrc->markType = oldrc->markType;
newrc->waitPolicy = oldrc->waitPolicy;
newrc->isParent = false;
root->rowMarks = lappend(root->rowMarks, newrc);
}
/* Close child relations, but keep locks */
if (childOID != parentOID)
heap_close(newrelation, NoLock);
--- 1391,1409 ----
newrc->rti = childRTindex;
newrc->prti = rti;
newrc->rowmarkId = oldrc->rowmarkId;
! if (childrte->relkind == RELKIND_FOREIGN_TABLE)
! newrc->markType = ROW_MARK_COPY;
! else
! newrc->markType = oldrc->markType;
newrc->waitPolicy = oldrc->waitPolicy;
newrc->isParent = false;
root->rowMarks = lappend(root->rowMarks, newrc);
}
+ if (childrte->relkind == RELKIND_FOREIGN_TABLE)
+ hasForeign = true;
+
/* Close child relations, but keep locks */
if (childOID != parentOID)
heap_close(newrelation, NoLock);
***************
*** 1416,1421 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1425,1434 ----
/* Otherwise, OK to add to root->append_rel_list */
root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+
+ /* And mark the parent table as having children that are foreign, if so */
+ if (hasForeign)
+ rte->hasForeign = true;
}
/*
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 4351,4382 **** 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;
}
;
--- 4351,4382 ----
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/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,191 ----
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/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 812,817 **** typedef struct RangeTblEntry
--- 812,818 ----
Alias *eref; /* expanded reference names */
bool lateral; /* subquery, function, or values is LATERAL? */
bool inh; /* inheritance requested? */
+ bool hasForeign; /* does inheritance include foreign tables? */
bool inFromCl; /* present in FROM clause? */
AclMode requiredPerms; /* bitmask of required access permissions */
Oid checkAsUser; /* if valid, check access as this role */
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 819,824 **** typedef enum RowMarkType
--- 819,826 ----
* The tableoid column is only present for an inheritance hierarchy.
* When markType == ROW_MARK_COPY, there is instead a single column named
* wholerow%u whole-row value of relation
+ * The wholerow column is also present for an inheritance hierarchy
+ * in cases where the inheritance hierarchy contains foreign tables.
* In all three cases, %u represents the rowmark ID number (rowmarkId).
* This number is unique within a plan tree, except that child relation
* entries copy their parent's rowmarkId. (Assigning unique numbers
*** a/src/test/regress/expected/foreign_data.out
--- b/src/test/regress/expected/foreign_data.out
***************
*** 1207,1212 **** DROP TRIGGER trigtest_before_row ON foreign_schema.foreign_table_1;
--- 1207,1547 ----
DROP TRIGGER trigtest_after_stmt ON foreign_schema.foreign_table_1;
DROP TRIGGER trigtest_after_row ON foreign_schema.foreign_table_1;
DROP FUNCTION dummy_trigger();
+ -- Table inheritance
+ CREATE TABLE pt1 (ff1 integer, ff2 text);
+ CREATE VIEW view1 AS SELECT 1;
+ ALTER TABLE view1 INHERIT pt1; -- ERROR
+ ERROR: "view1" is not a table or foreign table
+ DROP VIEW view1;
+ CREATE FOREIGN TABLE ct1 (ff1 integer, ff2 text)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ ALTER TABLE ct1 INHERIT pt1;
+ DROP FOREIGN TABLE ct1;
+ CREATE FOREIGN TABLE ct1 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ ff1 | integer | | plain | |
+ ff2 | text | | extended | |
+ Child tables: ct1
+
+ \d+ ct1
+ Foreign table "public.ct1"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ ALTER FOREIGN TABLE ct1 NO INHERIT pt1;
+ ALTER FOREIGN TABLE ct1 INHERIT pt1;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ ff1 | integer | | plain | |
+ ff2 | text | | extended | |
+ Child tables: ct1
+
+ \d+ ct1
+ Foreign table "public.ct1"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ ALTER TABLE pt1 ADD COLUMN ff3 integer;
+ ALTER TABLE pt1 ADD COLUMN ff4 integer;
+ ALTER TABLE pt1 ADD COLUMN ff5 integer DEFAULT 0;
+ ALTER TABLE pt1 ADD COLUMN ff6 integer;
+ ALTER TABLE pt1 ADD COLUMN ff7 integer NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN ff3 TYPE char(10) USING '0'; -- ERROR
+ ERROR: "ct1" is not a table
+ ALTER TABLE pt1 ALTER COLUMN ff3 TYPE char(10);
+ ALTER TABLE pt1 ALTER COLUMN ff3 SET DATA TYPE text;
+ ALTER TABLE pt1 ALTER COLUMN ff4 SET DEFAULT 0;
+ ALTER TABLE pt1 ALTER COLUMN ff5 DROP DEFAULT;
+ ALTER TABLE pt1 ALTER COLUMN ff6 SET NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN ff7 DROP NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN ff1 SET STATISTICS 10000;
+ ALTER TABLE pt1 ALTER COLUMN ff1 SET (n_distinct = 100);
+ ALTER TABLE pt1 ALTER COLUMN ff3 SET STATISTICS -1;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ ff1 | integer | | plain | 10000 |
+ ff2 | text | | extended | |
+ ff3 | text | | extended | |
+ ff4 | integer | default 0 | plain | |
+ ff5 | integer | | plain | |
+ ff6 | integer | not null | plain | |
+ ff7 | integer | | plain | |
+ Child tables: ct1
+
+ \d+ ct1
+ Foreign table "public.ct1"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | 10000 |
+ ff2 | text | | | extended | |
+ ff3 | text | | | extended | |
+ ff4 | integer | default 0 | | plain | |
+ ff5 | integer | | | plain | |
+ ff6 | integer | not null | | plain | |
+ ff7 | integer | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ ALTER TABLE pt1 ALTER COLUMN ff1 SET STATISTICS -1;
+ ALTER TABLE pt1 ALTER COLUMN ff2 SET STATISTICS -1;
+ ALTER TABLE pt1 DROP COLUMN ff3;
+ ALTER TABLE pt1 DROP COLUMN ff4;
+ ALTER TABLE pt1 DROP COLUMN ff5;
+ ALTER TABLE pt1 DROP COLUMN ff6;
+ ALTER TABLE pt1 DROP COLUMN ff7;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ ff1 | integer | | plain | |
+ ff2 | text | | extended | |
+ Child tables: ct1
+
+ \d+ ct1
+ Foreign table "public.ct1"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- cannot change storage modes for foreign tables
+ ALTER TABLE pt1 ALTER COLUMN ff2 SET STORAGE EXTERNAL;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ ff1 | integer | | plain | |
+ ff2 | text | | external | |
+ Child tables: ct1
+
+ \d+ ct1
+ Foreign table "public.ct1"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ DROP FOREIGN TABLE ct1;
+ CREATE FOREIGN TABLE ct1 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ ff1 | integer | | plain | |
+ ff2 | text | | external | |
+ Child tables: ct1
+
+ \d+ ct1
+ Foreign table "public.ct1"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ ALTER FOREIGN TABLE ct1 NO INHERIT pt1;
+ ALTER FOREIGN TABLE ct1 INHERIT pt1;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ ff1 | integer | | plain | |
+ ff2 | text | | external | |
+ Child tables: ct1
+
+ \d+ ct1
+ Foreign table "public.ct1"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ ALTER TABLE pt1 ALTER COLUMN ff2 SET STORAGE EXTENDED;
+ -- connoinherit should be true for NO INHERIT constraint
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk1 CHECK (ff1 > 0) NO INHERIT;
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (ff1 > 10);
+ SELECT pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pgc.connoinherit FROM pg_class AS pc INNER JOIN pg_constraint AS pgc ON (pgc.conrelid = pc.oid) WHERE pc.relname = 'pt1' order by 1,2;
+ relname | conname | contype | conislocal | coninhcount | connoinherit
+ ---------+---------+---------+------------+-------------+--------------
+ pt1 | pt1chk1 | c | t | 0 | t
+ pt1 | pt1chk2 | c | t | 0 | f
+ (2 rows)
+
+ -- child does not inherit NO INHERIT constraints
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ ff1 | integer | | plain | |
+ ff2 | text | | extended | |
+ Check constraints:
+ "pt1chk1" CHECK (ff1 > 0) NO INHERIT
+ "pt1chk2" CHECK (ff1 > 10)
+ Child tables: ct1
+
+ \d+ ct1
+ Foreign table "public.ct1"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Check constraints:
+ "pt1chk2" CHECK (ff1 > 10)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ DROP FOREIGN TABLE ct1;
+ CREATE FOREIGN TABLE ct1 (ff1 integer, ff2 text)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ -- child must have parent's INHERIT constraints
+ ALTER FOREIGN TABLE ct1 INHERIT pt1; -- ERROR
+ ERROR: child table is missing constraint "pt1chk2"
+ ALTER FOREIGN TABLE ct1 ADD CONSTRAINT pt1chk2 CHECK (ff1 > 10);
+ ALTER FOREIGN TABLE ct1 INHERIT pt1;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ ff1 | integer | | plain | |
+ ff2 | text | | extended | |
+ Check constraints:
+ "pt1chk1" CHECK (ff1 > 0) NO INHERIT
+ "pt1chk2" CHECK (ff1 > 10)
+ Child tables: ct1
+
+ \d+ ct1
+ Foreign table "public.ct1"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Check constraints:
+ "pt1chk2" CHECK (ff1 > 10)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- NOT VALID should be ignored for foreign tables
+ INSERT INTO pt1 VALUES (1000, 'pt1');
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk3 CHECK (ff1 > 100) NOT VALID;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ ff1 | integer | | plain | |
+ ff2 | text | | extended | |
+ Check constraints:
+ "pt1chk1" CHECK (ff1 > 0) NO INHERIT
+ "pt1chk2" CHECK (ff1 > 10)
+ "pt1chk3" CHECK (ff1 > 100) NOT VALID
+ Child tables: ct1
+
+ \d+ ct1
+ Foreign table "public.ct1"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Check constraints:
+ "pt1chk2" CHECK (ff1 > 10)
+ "pt1chk3" CHECK (ff1 > 100)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- VALIDATE CONSTRAINT should work by ignoring foreign tables
+ ALTER TABLE pt1 VALIDATE CONSTRAINT pt1chk3;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ ff1 | integer | | plain | |
+ ff2 | text | | extended | |
+ Check constraints:
+ "pt1chk1" CHECK (ff1 > 0) NO INHERIT
+ "pt1chk2" CHECK (ff1 > 10)
+ "pt1chk3" CHECK (ff1 > 100)
+ Child tables: ct1
+
+ \d+ ct1
+ Foreign table "public.ct1"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ ff1 | integer | | | plain | |
+ ff2 | text | | | extended | |
+ Check constraints:
+ "pt1chk2" CHECK (ff1 > 10)
+ "pt1chk3" CHECK (ff1 > 100)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk3 CASCADE;
+ -- cannot add an OID system column
+ ALTER TABLE pt1 SET WITH OIDS; -- ERROR
+ ERROR: cannot add OID column to foreign table "ct1"
+ -- changes name of an attribute recursively
+ ALTER TABLE pt1 RENAME COLUMN ff1 TO x;
+ ALTER TABLE pt1 RENAME COLUMN ff2 TO y;
+ -- changes name of a constraint recursively
+ ALTER TABLE pt1 RENAME CONSTRAINT pt1chk2 TO x_check;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ x | integer | | plain | |
+ y | text | | extended | |
+ Check constraints:
+ "pt1chk1" CHECK (x > 0) NO INHERIT
+ "x_check" CHECK (x > 10)
+ Child tables: ct1
+
+ \d+ ct1
+ Foreign table "public.ct1"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ x | integer | | | plain | |
+ y | text | | | extended | |
+ Check constraints:
+ "x_check" CHECK (x > 10)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ DROP TABLE pt1 CASCADE;
+ NOTICE: drop cascades to foreign table ct1
-- IMPORT FOREIGN SCHEMA
IMPORT FOREIGN SCHEMA s1 FROM SERVER s9 INTO public; -- ERROR
ERROR: foreign-data wrapper "foo" has no handler
*** a/src/test/regress/sql/foreign_data.sql
--- b/src/test/regress/sql/foreign_data.sql
***************
*** 522,527 **** DROP TRIGGER trigtest_after_row ON foreign_schema.foreign_table_1;
--- 522,631 ----
DROP FUNCTION dummy_trigger();
+ -- Table inheritance
+ CREATE TABLE pt1 (ff1 integer, ff2 text);
+ CREATE VIEW view1 AS SELECT 1;
+ ALTER TABLE view1 INHERIT pt1; -- ERROR
+ DROP VIEW view1;
+ CREATE FOREIGN TABLE ct1 (ff1 integer, ff2 text)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ ALTER TABLE ct1 INHERIT pt1;
+ DROP FOREIGN TABLE ct1;
+ CREATE FOREIGN TABLE ct1 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ \d+ ct1
+ ALTER FOREIGN TABLE ct1 NO INHERIT pt1;
+ ALTER FOREIGN TABLE ct1 INHERIT pt1;
+ \d+ pt1
+ \d+ ct1
+
+ ALTER TABLE pt1 ADD COLUMN ff3 integer;
+ ALTER TABLE pt1 ADD COLUMN ff4 integer;
+ ALTER TABLE pt1 ADD COLUMN ff5 integer DEFAULT 0;
+ ALTER TABLE pt1 ADD COLUMN ff6 integer;
+ ALTER TABLE pt1 ADD COLUMN ff7 integer NOT NULL;
+
+ ALTER TABLE pt1 ALTER COLUMN ff3 TYPE char(10) USING '0'; -- ERROR
+ ALTER TABLE pt1 ALTER COLUMN ff3 TYPE char(10);
+ ALTER TABLE pt1 ALTER COLUMN ff3 SET DATA TYPE text;
+ ALTER TABLE pt1 ALTER COLUMN ff4 SET DEFAULT 0;
+ ALTER TABLE pt1 ALTER COLUMN ff5 DROP DEFAULT;
+ ALTER TABLE pt1 ALTER COLUMN ff6 SET NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN ff7 DROP NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN ff1 SET STATISTICS 10000;
+ ALTER TABLE pt1 ALTER COLUMN ff1 SET (n_distinct = 100);
+ ALTER TABLE pt1 ALTER COLUMN ff3 SET STATISTICS -1;
+ \d+ pt1
+ \d+ ct1
+
+ ALTER TABLE pt1 ALTER COLUMN ff1 SET STATISTICS -1;
+ ALTER TABLE pt1 ALTER COLUMN ff2 SET STATISTICS -1;
+
+ ALTER TABLE pt1 DROP COLUMN ff3;
+ ALTER TABLE pt1 DROP COLUMN ff4;
+ ALTER TABLE pt1 DROP COLUMN ff5;
+ ALTER TABLE pt1 DROP COLUMN ff6;
+ ALTER TABLE pt1 DROP COLUMN ff7;
+ \d+ pt1
+ \d+ ct1
+
+ -- cannot change storage modes for foreign tables
+ ALTER TABLE pt1 ALTER COLUMN ff2 SET STORAGE EXTERNAL;
+ \d+ pt1
+ \d+ ct1
+ DROP FOREIGN TABLE ct1;
+ CREATE FOREIGN TABLE ct1 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ \d+ ct1
+ ALTER FOREIGN TABLE ct1 NO INHERIT pt1;
+ ALTER FOREIGN TABLE ct1 INHERIT pt1;
+ \d+ pt1
+ \d+ ct1
+ ALTER TABLE pt1 ALTER COLUMN ff2 SET STORAGE EXTENDED;
+
+ -- connoinherit should be true for NO INHERIT constraint
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk1 CHECK (ff1 > 0) NO INHERIT;
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (ff1 > 10);
+ SELECT pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pgc.connoinherit FROM pg_class AS pc INNER JOIN pg_constraint AS pgc ON (pgc.conrelid = pc.oid) WHERE pc.relname = 'pt1' order by 1,2;
+ -- child does not inherit NO INHERIT constraints
+ \d+ pt1
+ \d+ ct1
+ DROP FOREIGN TABLE ct1;
+ CREATE FOREIGN TABLE ct1 (ff1 integer, ff2 text)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ -- child must have parent's INHERIT constraints
+ ALTER FOREIGN TABLE ct1 INHERIT pt1; -- ERROR
+ ALTER FOREIGN TABLE ct1 ADD CONSTRAINT pt1chk2 CHECK (ff1 > 10);
+ ALTER FOREIGN TABLE ct1 INHERIT pt1;
+ \d+ pt1
+ \d+ ct1
+
+ -- NOT VALID should be ignored for foreign tables
+ INSERT INTO pt1 VALUES (1000, 'pt1');
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk3 CHECK (ff1 > 100) NOT VALID;
+ \d+ pt1
+ \d+ ct1
+ -- VALIDATE CONSTRAINT should work by ignoring foreign tables
+ ALTER TABLE pt1 VALIDATE CONSTRAINT pt1chk3;
+ \d+ pt1
+ \d+ ct1
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk3 CASCADE;
+
+ -- cannot add an OID system column
+ ALTER TABLE pt1 SET WITH OIDS; -- ERROR
+
+ -- changes name of an attribute recursively
+ ALTER TABLE pt1 RENAME COLUMN ff1 TO x;
+ ALTER TABLE pt1 RENAME COLUMN ff2 TO y;
+ -- changes name of a constraint recursively
+ ALTER TABLE pt1 RENAME CONSTRAINT pt1chk2 TO x_check;
+ \d+ pt1
+ \d+ ct1
+
+ DROP TABLE pt1 CASCADE;
+
-- IMPORT FOREIGN SCHEMA
IMPORT FOREIGN SCHEMA s1 FROM SERVER s9 INTO public; -- ERROR
IMPORT FOREIGN SCHEMA s1 LIMIT TO (t1) FROM SERVER s9 INTO public; --ERROR
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-11-07 05:57 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2014-11-07 05:57 UTC (permalink / raw)
To: fujita.etsuro@lab.ntt.co.jp; +Cc: hlinnakangas@vmware.com; noah@leadboat.com; shigeru.hanada@gmail.com; pgsql-hackers
Hello, I don't fully catch up this topic but tried this one.
> >> Here are separated patches.
> >>
> >> fdw-chk.patch - CHECK constraints on foreign tables
> >> fdw-inh.patch - table inheritance with foreign tables
> >>
> >> The latter has been created on top of [1].
> >
> >> [1]
> >> http://www.postgresql.org/message-id/540DA168.3040407@lab.ntt.co.jp
>
> > To be exact, it has been created on top of [1] and fdw-chk.patch.
I tried both patches on the current head, the newly added
parameter to analyze_rel() hampered them from applying but it is
easy to fix seemingly and almost all the other part was applied
cleanly.
By the way, are these the result of simply splitting of your last
patch, foreign_inherit-v15.patch?
http://www.postgresql.org/message-id/53FEEF94.6040101@lab.ntt.co.jp
The result of apllying whole-in-one version and this splitted
version seem to have many differences. Did you added even other
changes? Or do I understand this patch wrongly?
git diff --numstat 0_foreign_inherit_one 0_foreign_inherit_two
5 51 contrib/file_fdw/file_fdw.c
10 19 contrib/file_fdw/input/file_fdw.source
18 71 contrib/file_fdw/output/file_fdw.source
19 70 contrib/postgres_fdw/expected/postgres_fdw.out
9 66 contrib/postgres_fdw/postgres_fdw.c
12 35 contrib/postgres_fdw/sql/postgres_fdw.sql
13 48 doc/src/sgml/fdwhandler.sgml
39 39 doc/src/sgml/ref/alter_foreign_table.sgml
4 3 doc/src/sgml/ref/create_foreign_table.sgml
8 0 src/backend/catalog/heap.c
7 3 src/backend/commands/analyze.c
0 7 src/backend/commands/tablecmds.c
1 22 src/backend/optimizer/plan/createplan.c
7 7 src/backend/optimizer/prep/prepunion.c
0 26 src/backend/optimizer/util/pathnode.c
1 1 src/include/commands/vacuum.h
0 7 src/include/foreign/fdwapi.h
19 1 src/test/regress/expected/foreign_data.out
9 2 src/test/regress/sql/foreign_data.sql
> > I noticed that the latter disallows TRUNCATE on inheritance trees that
> > contain at least one child foreign table. But I think it would be
> > better to allow it, with the semantics that we quietly ignore the
> > child
> > foreign tables and apply the operation to the child plain tables,
> > which
> > is the same semantics as ALTER COLUMN SET STORAGE on such inheritance
> > trees. Comments welcome.
>
> Done. And I've also a bit revised regression tests for both
> patches. Patches attached.
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-11-07 07:54 furuyao@pm.nttdata.co.jp
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: furuyao@pm.nttdata.co.jp @ 2014-11-07 07:54 UTC (permalink / raw)
To: fujita.etsuro@lab.ntt.co.jp; noah@leadboat.com; +Cc: shigeru.hanada@gmail.com; horiguchi.kyotaro@lab.ntt.co.jp; pgsql-hackers; teranishih@nttdata.co.jp
> (2014/08/28 18:00), Etsuro Fujita wrote:
> > (2014/08/22 11:51), Noah Misch wrote:
> >> Today's ANALYZE VERBOSE messaging for former inheritance parents
> >> (tables with relhassubclass = true but no pg_inherits.inhparent
> >> links) is deceptive, and I welcome a fix to omit the spurious
> >> message. As defects go, this is quite minor. There's fundamentally
> >> no value in collecting inheritance tree statistics for such a parent,
> >> and no PostgreSQL command will do so.
>
> >> A
> >> credible alternative is to emit a second message indicating that we
> >> skipped the inheritance tree statistics after all, and why we skipped
> >> them.
>
> > I'd like to address this by emitting the second message as shown below:
>
> > A separate patch (analyze.patch) handles the former case in a similar
> way.
>
> I'll add to the upcoming CF, the analyze.patch as an independent item,
> which emits a second message indicating that we skipped the inheritance
> tree statistics and why we skipped them.
I did a review of the patch.
There was no problem.
I confirmed the following.
1. applied cleanly and compilation was without warnings and errors
2. all regress tests was passed ok
3. The message output from ANALYZE VERBOSE.
Following are the SQL which I used to check messages.
create table parent (id serial);
create table child (name text) inherits (parent);
ANALYZE VERBOSE parent ;
drop table child ;
ANALYZE VERBOSE parent ;
Regards,
--
Furuya Osamu
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-11-07 08:17 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: furuyao@pm.nttdata.co.jp
0 siblings, 0 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-11-07 08:17 UTC (permalink / raw)
To: furuyao@pm.nttdata.co.jp; noah@leadboat.com; +Cc: shigeru.hanada@gmail.com; horiguchi.kyotaro@lab.ntt.co.jp; pgsql-hackers; teranishih@nttdata.co.jp
Hi Furuya-san,
(2014/11/07 16:54), furuyao@pm.nttdata.co.jp wrote:
>> (2014/08/28 18:00), Etsuro Fujita wrote:
>>> (2014/08/22 11:51), Noah Misch wrote:
>>>> Today's ANALYZE VERBOSE messaging for former inheritance parents
>>>> (tables with relhassubclass = true but no pg_inherits.inhparent
>>>> links) is deceptive, and I welcome a fix to omit the spurious
>>>> message. As defects go, this is quite minor. There's fundamentally
>>>> no value in collecting inheritance tree statistics for such a parent,
>>>> and no PostgreSQL command will do so.
>>
>>>> A
>>>> credible alternative is to emit a second message indicating that we
>>>> skipped the inheritance tree statistics after all, and why we skipped
>>>> them.
>>
>>> I'd like to address this by emitting the second message as shown below:
>>
>>> A separate patch (analyze.patch) handles the former case in a similar
>> way.
>>
>> I'll add to the upcoming CF, the analyze.patch as an independent item,
>> which emits a second message indicating that we skipped the inheritance
>> tree statistics and why we skipped them.
>
> I did a review of the patch.
> There was no problem.
> I confirmed the following.
>
> 1. applied cleanly and compilation was without warnings and errors
> 2. all regress tests was passed ok
> 3. The message output from ANALYZE VERBOSE.
>
> Following are the SQL which I used to check messages.
>
> create table parent (id serial);
> create table child (name text) inherits (parent);
> ANALYZE VERBOSE parent ;
> drop table child ;
> ANALYZE VERBOSE parent ;
I think that that is a correct test for this patch.
Thanks for the review!
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-11-07 12:01 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
0 siblings, 2 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-11-07 12:01 UTC (permalink / raw)
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: hlinnakangas@vmware.com; noah@leadboat.com; shigeru.hanada@gmail.com; pgsql-hackers
(2014/11/07 14:57), Kyotaro HORIGUCHI wrote:
>>>> Here are separated patches.
>>>>
>>>> fdw-chk.patch - CHECK constraints on foreign tables
>>>> fdw-inh.patch - table inheritance with foreign tables
>>>>
>>>> The latter has been created on top of [1].
>>>
>>>> [1]
>>>> http://www.postgresql.org/message-id/540DA168.3040407@lab.ntt.co.jp
>>
>>> To be exact, it has been created on top of [1] and fdw-chk.patch.
>
> I tried both patches on the current head, the newly added
> parameter to analyze_rel() hampered them from applying but it is
> easy to fix seemingly and almost all the other part was applied
> cleanly.
Thanks for the review!
> By the way, are these the result of simply splitting of your last
> patch, foreign_inherit-v15.patch?
>
> http://www.postgresql.org/message-id/53FEEF94.6040101@lab.ntt.co.jp
The answer is "no".
> The result of apllying whole-in-one version and this splitted
> version seem to have many differences. Did you added even other
> changes? Or do I understand this patch wrongly?
As I said before, I splitted the whole-in-one version into three: 1)
CHECK constraint patch (ie fdw-chk.patch), 2) table inheritance patch
(ie fdw-inh.patch) and 3) path reparameterization patch (not posted).
In addition to that, I slightly modified #1 and #2.
IIUC, #3 would be useful not only for the inheritance cases but for
union all cases. So, I plan to propose it independently in the next CF.
>>> I noticed that the latter disallows TRUNCATE on inheritance trees that
>>> contain at least one child foreign table. But I think it would be
>>> better to allow it, with the semantics that we quietly ignore the
>>> child
>>> foreign tables and apply the operation to the child plain tables,
>>> which
>>> is the same semantics as ALTER COLUMN SET STORAGE on such inheritance
>>> trees. Comments welcome.
>>
>> Done. And I've also a bit revised regression tests for both
>> patches. Patches attached.
I rebased the patches to the latest head. Here are updated patches.
Other changes:
* fdw-chk-3.patch: the updated patch revises some ereport messages a
little bit.
* fdw-inh-3.patch: I noticed that there is a doc bug in the previous
patch. The updated patch fixes that, adds a bit more docs, and revises
regression tests in foreign_data.sql a bit further.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-patch] fdw-inh-3.patch (90.6K, ../../545CB4A2.1030303@lab.ntt.co.jp/2-fdw-inh-3.patch)
download | inline diff:
*** a/contrib/file_fdw/input/file_fdw.source
--- b/contrib/file_fdw/input/file_fdw.source
***************
*** 144,149 **** SET constraint_exclusion = 'partition';
--- 144,166 ----
\t off
ALTER FOREIGN TABLE agg_csv DROP CONSTRAINT agg_csv_a_check;
+ -- table inheritance tests
+ CREATE TABLE agg (a int2, b float4);
+ ALTER FOREIGN TABLE agg_csv INHERIT agg;
+ ALTER FOREIGN TABLE agg_text INHERIT agg;
+ SELECT * FROM agg WHERE b < 10.0 ORDER BY a, b;
+ SELECT * FROM ONLY agg WHERE b < 10.0 ORDER BY a, b;
+ SELECT * FROM agg_csv WHERE b < 10.0 ORDER BY a, b;
+ SELECT * FROM agg_text WHERE b < 10.0 ORDER BY a, b;
+ -- updates aren't supported
+ UPDATE agg SET a = 1;
+ DELETE FROM agg WHERE a = 100;
+ -- but this should be ignored
+ SELECT * FROM agg WHERE b < 10.0 ORDER BY a, b FOR UPDATE;
+ ALTER FOREIGN TABLE agg_csv NO INHERIT agg;
+ ALTER FOREIGN TABLE agg_text NO INHERIT agg;
+ DROP TABLE agg;
+
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/file_fdw/output/file_fdw.source
--- b/contrib/file_fdw/output/file_fdw.source
***************
*** 237,242 **** EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
--- 237,289 ----
SET constraint_exclusion = 'partition';
\t off
ALTER FOREIGN TABLE agg_csv DROP CONSTRAINT agg_csv_a_check;
+ -- table inheritance tests
+ CREATE TABLE agg (a int2, b float4);
+ ALTER FOREIGN TABLE agg_csv INHERIT agg;
+ ALTER FOREIGN TABLE agg_text INHERIT agg;
+ SELECT * FROM agg WHERE b < 10.0 ORDER BY a, b;
+ a | b
+ ----+---------
+ 0 | 0.09561
+ 0 | 0.09561
+ 56 | 7.8
+ (3 rows)
+
+ SELECT * FROM ONLY agg WHERE b < 10.0 ORDER BY a, b;
+ a | b
+ ---+---
+ (0 rows)
+
+ SELECT * FROM agg_csv WHERE b < 10.0 ORDER BY a, b;
+ a | b
+ ---+---------
+ 0 | 0.09561
+ (1 row)
+
+ SELECT * FROM agg_text WHERE b < 10.0 ORDER BY a, b;
+ a | b
+ ----+---------
+ 0 | 0.09561
+ 56 | 7.8
+ (2 rows)
+
+ -- updates aren't supported
+ UPDATE agg SET a = 1;
+ ERROR: cannot update foreign table "agg_text"
+ DELETE FROM agg WHERE a = 100;
+ ERROR: cannot delete from foreign table "agg_text"
+ -- but this should be ignored
+ SELECT * FROM agg WHERE b < 10.0 ORDER BY a, b FOR UPDATE;
+ a | b
+ ----+---------
+ 0 | 0.09561
+ 0 | 0.09561
+ 56 | 7.8
+ (3 rows)
+
+ ALTER FOREIGN TABLE agg_csv NO INHERIT agg;
+ ALTER FOREIGN TABLE agg_text NO INHERIT agg;
+ DROP TABLE agg;
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 2863,2868 **** NOTICE: NEW: (13,"test triggered !")
--- 2863,3376 ----
(1 row)
-- ===================================================================
+ -- test inheritance features
+ -- ===================================================================
+ CREATE TABLE a (aa TEXT);
+ CREATE TABLE loct (aa TEXT, bb TEXT);
+ CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a)
+ SERVER loopback OPTIONS (table_name 'loct');
+ INSERT INTO a(aa) VALUES('aaa');
+ INSERT INTO a(aa) VALUES('aaaa');
+ INSERT INTO a(aa) VALUES('aaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaaa');
+ INSERT INTO b(aa) VALUES('bbb');
+ INSERT INTO b(aa) VALUES('bbbb');
+ INSERT INTO b(aa) VALUES('bbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbbb');
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | aaa
+ a | aaaa
+ a | aaaaa
+ a | aaaaaa
+ a | aaaaaaa
+ a | aaaaaaaa
+ b | bbb
+ b | bbbb
+ b | bbbbb
+ b | bbbbbb
+ b | bbbbbbb
+ b | bbbbbbbb
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----------+----
+ b | bbb |
+ b | bbbb |
+ b | bbbbb |
+ b | bbbbbb |
+ b | bbbbbbb |
+ b | bbbbbbbb |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | aaa
+ a | aaaa
+ a | aaaaa
+ a | aaaaaa
+ a | aaaaaaa
+ a | aaaaaaaa
+ (6 rows)
+
+ UPDATE a SET aa='zzzzzz' WHERE aa LIKE 'aaa%';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ b | bbb
+ b | bbbb
+ b | bbbbb
+ b | bbbbbb
+ b | bbbbbbb
+ b | bbbbbbbb
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----------+----
+ b | bbb |
+ b | bbbb |
+ b | bbbbb |
+ b | bbbbbb |
+ b | bbbbbbb |
+ b | bbbbbbbb |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ (6 rows)
+
+ UPDATE b SET aa='new';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+-----+----
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ (6 rows)
+
+ UPDATE a SET aa='new';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+-----
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+-----+----
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+-----
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ (6 rows)
+
+ DELETE FROM a;
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----
+ (0 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----+----
+ (0 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----
+ (0 rows)
+
+ DROP TABLE a CASCADE;
+ NOTICE: drop cascades to foreign table b
+ DROP TABLE loct;
+ -- Check SELECT FOR UPDATE/SHARE with an inherited source table
+ create table loct1 (f1 int, f2 int, f3 int);
+ create table loct2 (f1 int, f2 int, f3 int);
+ create table foo (f1 int, f2 int);
+ create foreign table foo2 (f3 int) inherits (foo)
+ server loopback options (table_name 'loct1');
+ create table bar (f1 int, f2 int);
+ create foreign table bar2 (f3 int) inherits (bar)
+ server loopback options (table_name 'loct2');
+ insert into foo values(1,1);
+ insert into foo values(3,3);
+ insert into foo2 values(2,2,2);
+ insert into foo2 values(3,3,3);
+ insert into bar values(1,1);
+ insert into bar values(2,2);
+ insert into bar values(3,3);
+ insert into bar values(4,4);
+ insert into bar2 values(1,1,1);
+ insert into bar2 values(2,2,2);
+ insert into bar2 values(3,3,3);
+ insert into bar2 values(4,4,4);
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for update;
+ QUERY PLAN
+ ----------------------------------------------------------------------------------------------
+ LockRows
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ -> Hash Join
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Append
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.ctid, bar2.tableoid, bar2.*
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (22 rows)
+
+ select * from bar where f1 in (select f1 from foo) for update;
+ f1 | f2
+ ----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ (6 rows)
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for share;
+ QUERY PLAN
+ ----------------------------------------------------------------------------------------------
+ LockRows
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ -> Hash Join
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Append
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.ctid, bar2.tableoid, bar2.*
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (22 rows)
+
+ select * from bar where f1 in (select f1 from foo) for share;
+ f1 | f2
+ ----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ (6 rows)
+
+ -- Check UPDATE with inherited target and an inherited source table
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ QUERY PLAN
+ ---------------------------------------------------------------------------------------------
+ Update on public.bar
+ -> Hash Join
+ Output: bar.f1, (bar.f2 + 100), bar.ctid, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ -> Hash Join
+ Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar2.f1 = foo.f1)
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (34 rows)
+
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+ relname | f1 | f2
+ ---------+----+-----
+ bar | 1 | 101
+ bar | 2 | 102
+ bar | 3 | 103
+ bar | 4 | 4
+ bar2 | 1 | 101
+ bar2 | 2 | 102
+ bar2 | 3 | 103
+ bar2 | 4 | 4
+ (8 rows)
+
+ -- Check UPDATE with inherited target and an appendrel subquery
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ QUERY PLAN
+ --------------------------------------------------------------------------------------
+ Update on public.bar
+ -> Hash Join
+ Output: bar.f1, (bar.f2 + 100), bar.ctid, (ROW(foo.f1))
+ Hash Cond: (foo.f1 = bar.f1)
+ -> Append
+ -> Seq Scan on public.foo
+ Output: ROW(foo.f1), foo.f1
+ -> Foreign Scan on public.foo2
+ Output: ROW(foo2.f1), foo2.f1
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Seq Scan on public.foo foo_1
+ Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3)
+ -> Foreign Scan on public.foo2 foo2_1
+ Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3)
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Hash
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Merge Join
+ Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, (ROW(foo.f1))
+ Merge Cond: (bar2.f1 = foo.f1)
+ -> Sort
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Sort Key: bar2.f1
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Sort
+ Output: (ROW(foo.f1)), foo.f1
+ Sort Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: ROW(foo.f1), foo.f1
+ -> Foreign Scan on public.foo2
+ Output: ROW(foo2.f1), foo2.f1
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Seq Scan on public.foo foo_1
+ Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3)
+ -> Foreign Scan on public.foo2 foo2_1
+ Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3)
+ Remote SQL: SELECT f1 FROM public.loct1
+ (42 rows)
+
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+ relname | f1 | f2
+ ---------+----+-----
+ bar | 1 | 201
+ bar | 2 | 202
+ bar | 3 | 203
+ bar | 4 | 104
+ bar2 | 1 | 201
+ bar2 | 2 | 202
+ bar2 | 3 | 203
+ bar2 | 4 | 104
+ (8 rows)
+
+ drop table foo cascade;
+ NOTICE: drop cascades to foreign table foo2
+ drop table bar cascade;
+ NOTICE: drop cascades to foreign table bar2
+ drop table loct1;
+ drop table loct2;
+ -- Test that WHERE CURRENT OF is not supported
+ create table ltbl (a int, b text);
+ create table ptbl (a int, b text);
+ create table locc () inherits (ptbl);
+ insert into locc values(1, 'foo');
+ create foreign table remc () inherits (ptbl)
+ server loopback options (table_name 'ltbl');
+ insert into remc values(2, 'bar');
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'foo';
+ fetch from c;
+ ?column?
+ ----------
+ 1
+ (1 row)
+
+ update ptbl set b = null where current of c;
+ ERROR: WHERE CURRENT OF is not supported for this table type
+ rollback;
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'bar';
+ fetch from c;
+ ?column?
+ ----------
+ 1
+ (1 row)
+
+ update ptbl set b = null where current of c;
+ ERROR: WHERE CURRENT OF is not supported for this table type
+ rollback;
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ -- Test TRUNCATE
+ truncate ptbl;
+ select * from ptbl;
+ a | b
+ ---+-----
+ 2 | bar
+ (1 row)
+
+ select * from only ptbl;
+ a | b
+ ---+---
+ (0 rows)
+
+ select * from locc;
+ a | b
+ ---+---
+ (0 rows)
+
+ select * from remc;
+ a | b
+ ---+-----
+ 2 | bar
+ (1 row)
+
+ truncate remc; -- ERROR
+ ERROR: "remc" is not a table
+ drop table ptbl cascade;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to table locc
+ drop cascades to foreign table remc
+ drop table ltbl;
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
CREATE SCHEMA import_source;
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 823,828 **** postgresGetForeignPlan(PlannerInfo *root,
--- 823,839 ----
{
RowMarkClause *rc = get_parse_rowmark(root->parse, baserel->relid);
+ if (rc == NULL)
+ {
+ PlanRowMark *prm = get_plan_rowmark(root->rowMarks, baserel->relid);
+
+ if (prm)
+ {
+ if (prm->rti != prm->prti)
+ rc = get_parse_rowmark(root->parse, prm->prti);
+ }
+ }
+
if (rc)
{
/*
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 626,631 **** UPDATE rem1 SET f2 = 'testo';
--- 626,782 ----
INSERT INTO rem1(f2) VALUES ('test') RETURNING ctid;
-- ===================================================================
+ -- test inheritance features
+ -- ===================================================================
+
+ CREATE TABLE a (aa TEXT);
+ CREATE TABLE loct (aa TEXT, bb TEXT);
+ CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a)
+ SERVER loopback OPTIONS (table_name 'loct');
+
+ INSERT INTO a(aa) VALUES('aaa');
+ INSERT INTO a(aa) VALUES('aaaa');
+ INSERT INTO a(aa) VALUES('aaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaaa');
+
+ INSERT INTO b(aa) VALUES('bbb');
+ INSERT INTO b(aa) VALUES('bbbb');
+ INSERT INTO b(aa) VALUES('bbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbbb');
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE a SET aa='zzzzzz' WHERE aa LIKE 'aaa%';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE b SET aa='new';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE a SET aa='new';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ DELETE FROM a;
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ DROP TABLE a CASCADE;
+ DROP TABLE loct;
+
+ -- Check SELECT FOR UPDATE/SHARE with an inherited source table
+ create table loct1 (f1 int, f2 int, f3 int);
+ create table loct2 (f1 int, f2 int, f3 int);
+
+ create table foo (f1 int, f2 int);
+ create foreign table foo2 (f3 int) inherits (foo)
+ server loopback options (table_name 'loct1');
+ create table bar (f1 int, f2 int);
+ create foreign table bar2 (f3 int) inherits (bar)
+ server loopback options (table_name 'loct2');
+
+ insert into foo values(1,1);
+ insert into foo values(3,3);
+ insert into foo2 values(2,2,2);
+ insert into foo2 values(3,3,3);
+ insert into bar values(1,1);
+ insert into bar values(2,2);
+ insert into bar values(3,3);
+ insert into bar values(4,4);
+ insert into bar2 values(1,1,1);
+ insert into bar2 values(2,2,2);
+ insert into bar2 values(3,3,3);
+ insert into bar2 values(4,4,4);
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for update;
+ select * from bar where f1 in (select f1 from foo) for update;
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for share;
+ select * from bar where f1 in (select f1 from foo) for share;
+
+ -- Check UPDATE with inherited target and an inherited source table
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+
+ -- Check UPDATE with inherited target and an appendrel subquery
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+
+ drop table foo cascade;
+ drop table bar cascade;
+ drop table loct1;
+ drop table loct2;
+
+ -- Test that WHERE CURRENT OF is not supported
+ create table ltbl (a int, b text);
+
+ create table ptbl (a int, b text);
+ create table locc () inherits (ptbl);
+ insert into locc values(1, 'foo');
+
+ create foreign table remc () inherits (ptbl)
+ server loopback options (table_name 'ltbl');
+ insert into remc values(2, 'bar');
+
+ select * from ptbl;
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'foo';
+ fetch from c;
+ update ptbl set b = null where current of c;
+ rollback;
+
+ select * from ptbl;
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'bar';
+ fetch from c;
+ update ptbl set b = null where current of c;
+ rollback;
+
+ select * from ptbl;
+
+ -- Test TRUNCATE
+ truncate ptbl;
+ select * from ptbl;
+ select * from only ptbl;
+ select * from locc;
+ select * from remc;
+ truncate remc; -- ERROR
+
+ drop table ptbl cascade;
+ drop table ltbl;
+
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
*** a/doc/src/sgml/ddl.sgml
--- b/doc/src/sgml/ddl.sgml
***************
*** 2503,2508 **** VALUES ('Albany', NULL, NULL, 'NY');
--- 2503,2527 ----
further privileges to be granted.
</para>
+ <para>
+ Note that a foreign table can also inherit from more than one parent
+ table (see <xref linkend="ddl-foreign-data">
+ for an overview of foreign tables).
+ Like normal tables, table inheritance is typically established when
+ the foreign table is created, using the <literal>INHERITS</> clause of
+ the <xref linkend="sql-createforeigntable"> statement. Alternatively,
+ a foreign table which is already defined in a compatible way can have
+ a new parent relationship added, using the <literal>INHERIT</literal>
+ variant of <xref linkend="sql-alterforeigntable">.
+ Similarly an inheritance link can be removed from a child using the
+ <literal>NO INHERIT</literal> variant of <command>ALTER FOREIGN TABLE</>.
+ <command>CREATE FOREIGN TABLE</command> and
+ <command>ALTER FOREIGN TABLE</command> follow the same rules for
+ duplicate column merging and rejection that apply during
+ <command>CREATE TABLE</command> and <command>ALTER TABLE</command>,
+ respectively.
+ </para>
+
<sect2 id="ddl-inherit-caveats">
<title>Caveats</title>
***************
*** 2650,2656 **** VALUES ('Albany', NULL, NULL, 'NY');
table of a single parent table. The parent table itself is normally
empty; it exists just to represent the entire data set. You should be
familiar with inheritance (see <xref linkend="ddl-inherit">) before
! attempting to set up partitioning.
</para>
<para>
--- 2669,2678 ----
table of a single parent table. The parent table itself is normally
empty; it exists just to represent the entire data set. You should be
familiar with inheritance (see <xref linkend="ddl-inherit">) before
! attempting to set up partitioning. (The setup and management of
! partitioned tables illustrated in this section assume that each
! partition is a normal table. However, you can do that in a similar way
! for cases where some or all partitions are foreign tables.)
</para>
<para>
***************
*** 2713,2720 **** VALUES ('Albany', NULL, NULL, 'NY');
</para>
<para>
! We will refer to the child tables as partitions, though they
! are in every way normal <productname>PostgreSQL</> tables.
</para>
</listitem>
--- 2735,2742 ----
</para>
<para>
! We will refer to the child tables as partitions, though we assume
! that they are normal <productname>PostgreSQL</> tables.
</para>
</listitem>
*** a/doc/src/sgml/ref/alter_foreign_table.sgml
--- b/doc/src/sgml/ref/alter_foreign_table.sgml
***************
*** 48,53 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 48,55 ----
ENABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
ENABLE REPLICA TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
ENABLE ALWAYS TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 190,195 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 192,217 ----
</varlistentry>
<varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OWNER</literal></term>
<listitem>
<para>
***************
*** 380,385 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 402,417 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">new_owner</replaceable></term>
<listitem>
<para>
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 1010,1015 **** ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
--- 1010,1028 ----
</para>
<para>
+ A recursive <literal>SET STORAGE</literal> operation will make
+ the storage mode unchanged for any column of descendant tables
+ that are foreign.
+ </para>
+
+ <para>
+ A recursive <literal>SET WITH OIDS</literal> operation will be
+ rejected if any of descendant tables is foreign, since it is
+ not permitted to add an <literal>oid</literal> system column to
+ foreign tables.
+ </para>
+
+ <para>
The <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>,
and <literal>TABLESPACE</> actions never recurse to descendant tables;
that is, they always act as though <literal>ONLY</> were specified.
***************
*** 1018,1023 **** ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
--- 1031,1044 ----
</para>
<para>
+ When adding a <literal>CHECK</> constraint with the <literal>NOT VALID
+ </literal> option recursively, the inherited constraint on a descendant
+ table that is foreign will be marked valid without checking consistency
+ with the foreign server. It is the user's resposibility to ensure that
+ the <literal>CHECK</> constraint matches the foreign server.
+ </para>
+
+ <para>
Changing any part of a system catalog table is not permitted.
</para>
*** a/doc/src/sgml/ref/analyze.sgml
--- b/doc/src/sgml/ref/analyze.sgml
***************
*** 200,205 **** ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
--- 200,212 ----
</para>
<para>
+ 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
+ <command>ANALYZE</command>, the command prints a warning and does nothing.
+ </para>
+
+ <para>
If the table being analyzed is completely empty, <command>ANALYZE</command>
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
***************
*** 23,28 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 23,29 ----
| <replaceable>table_constraint</replaceable> }
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 121,126 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 122,147 ----
</varlistentry>
<varlistentry>
+ <term><literal>INHERITS ( <replaceable>parent_table</replaceable> [, ... ] )</literal></term>
+ <listitem>
+ <para>
+ The optional <literal>INHERITS</> clause specifies a list of
+ tables from which the new foreign table automatically inherits
+ all columns.
+ See the similar form of <xref linkend="sql-createtable">
+ for more details.
+ </para>
+
+ <para>
+ Note that unlike <command>CREATE TABLE</command>, column
+ <literal>STORAGE</> settings are not copied from parent tables,
+ resulting in the copied columns in the new foreign table having
+ type-specific default settings.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>NOT NULL</></term>
<listitem>
<para>
***************
*** 187,192 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 208,225 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for the
+ details of table inheritance.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
***************
*** 223,228 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 256,270 ----
Those definitions simply declare the constraints hold for all rows
in the foreign tables. It is the user's responsibility to ensure
that those definitions match the remote side.
+ Those constraints are used in some kind of query optimization such
+ as constraint exclusion for partitioned tables (see
+ <xref linkend="ddl-partitioning">).
+ </para>
+
+ <para>
+ Since it is not permitted to add an <literal>oid</> system column to
+ foreign tables, the command will be rejected if any of parent tables
+ has an <literal>oid</> system column.
</para>
</refsect1>
*** a/doc/src/sgml/ref/truncate.sgml
--- b/doc/src/sgml/ref/truncate.sgml
***************
*** 179,184 **** TRUNCATE [ TABLE ] [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [
--- 179,189 ----
This is similar to the usual behavior of <function>currval()</> after
a failed transaction.
</para>
+
+ <para>
+ A recursive <command>TRUNCATE</command> ignores descendant tables that
+ are foreign (if any), although it locks these tables exclusively.
+ </para>
</refsect1>
<refsect1>
*** 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;
***************
*** 102,107 **** static int acquire_sample_rows(Relation onerel, int elevel,
--- 103,109 ----
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
static int compare_rows(const void *a, const void *b);
+ static bool has_foreign(Oid parentrelId, List *tableOIDs);
static int acquire_inherited_sample_rows(Relation onerel, int elevel,
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
***************
*** 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,
bool in_outer_xact, BufferAccessStrategy bstrategy)
{
Relation onerel;
--- 117,123 ----
* analyze_rel() -- analyze one relation
*/
void
! analyze_rel(Oid relid, VacuumStmt *vacstmt, VacuumMode vacmode,
bool in_outer_xact, BufferAccessStrategy bstrategy)
{
Relation onerel;
***************
*** 130,135 **** analyze_rel(Oid relid, VacuumStmt *vacstmt,
--- 132,138 ----
elevel = DEBUG2;
/* Set up static variables */
+ vac_mode = vacmode;
vac_strategy = bstrategy;
/*
***************
*** 297,305 **** analyze_rel(Oid relid, VacuumStmt *vacstmt,
* do_analyze_rel() -- analyze one relation, recursively or not
*
* Note that "acquirefunc" is only relevant for the non-inherited case.
! * If we supported foreign tables in inheritance trees,
! * acquire_inherited_sample_rows would need to determine the appropriate
! * acquirefunc for each child table.
*/
static void
do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
--- 300,307 ----
* do_analyze_rel() -- analyze one relation, recursively or not
*
* Note that "acquirefunc" is only relevant for the non-inherited case.
! * For the inherited case, acquire_inherited_sample_rows determines the
! * appropriate acquirefunc for each child table.
*/
static void
do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
***************
*** 1444,1454 **** compare_rows(const void *a, const void *b)
/*
* acquire_inherited_sample_rows -- acquire sample rows from inheritance tree
*
* This has the same API as acquire_sample_rows, except that rows are
* collected from all inheritance children as well as the specified table.
! * We fail and return zero if there are no inheritance children.
*/
static int
acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1446,1492 ----
/*
+ * Check wether to contain at least one foreign table
+ */
+ static bool
+ has_foreign(Oid parentrelId, List *tableOIDs)
+ {
+ bool result;
+ ListCell *lc;
+
+ /* There are no children */
+ if (list_length(tableOIDs) < 2)
+ return false;
+
+ result = false;
+ 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, NoLock);
+ }
+ return result;
+ }
+
+ /*
* acquire_inherited_sample_rows -- acquire sample rows from inheritance tree
*
* This has the same API as acquire_sample_rows, except that rows are
* collected from all inheritance children as well as the specified table.
! * We fail and return zero if there are no inheritance children or there are
! * inheritance children that foreign tables.
*/
static int
acquire_inherited_sample_rows(Relation onerel, int elevel,
***************
*** 1457,1462 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1495,1501 ----
{
List *tableOIDs;
Relation *rels;
+ AcquireSampleRowsFunc *acquirefunc;
double *relblocks;
double totalblocks;
int numrows,
***************
*** 1487,1496 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1526,1556 ----
}
/*
+ * If we are not in VAC_MODE_SINGLE case and the inheritance set contains
+ * at least one foreign table, then fail.
+ */
+ if (vac_mode != VAC_MODE_SINGLE)
+ {
+ if (has_foreign(RelationGetRelid(onerel), tableOIDs))
+ {
+ ereport(elevel,
+ (errmsg("skipping analyze of \"%s.%s\" inheritance tree --- this inheritance tree contains foreign tables",
+ get_namespace_name(RelationGetNamespace(onerel)),
+ RelationGetRelationName(onerel)),
+ errhint("Try ANALYZE \"%s.%s\" for the inheritance statistics.",
+ get_namespace_name(RelationGetNamespace(onerel)),
+ RelationGetRelationName(onerel))));
+ return 0;
+ }
+ }
+
+ /*
* Count the blocks in all the relations. The result could overflow
* 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;
***************
*** 1512,1518 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
}
rels[nrels] = childrel;
! relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
totalblocks += relblocks[nrels];
nrels++;
}
--- 1572,1607 ----
}
rels[nrels] = childrel;
!
! if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
! {
! FdwRoutine *fdwroutine;
! BlockNumber relpages = 0;
! bool ok = false;
!
! /* 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++;
}
***************
*** 1530,1535 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1619,1625 ----
{
Relation childrel = rels[i];
double childblocks = relblocks[i];
+ AcquireSampleRowsFunc childacquirefunc = acquirefunc[i];
if (childblocks > 0)
{
***************
*** 1545,1556 **** 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 &&
--- 1635,1646 ----
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
***************
*** 316,322 **** 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,
--- 316,323 ----
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,
***************
*** 567,572 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
--- 568,597 ----
stmt->relation->relpersistence,
&inheritOids, &old_constraints, &parentOidCount);
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * Don't allow a foreign table to inherit from parents that have OID
+ * system columns.
+ */
+ if (parentOidCount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot inherit from relation with OIDs")));
+
+ /*
+ * Reset the storage parameter for inherited attributes that have
+ * non-default values.
+ */
+ foreach(listptr, schema)
+ {
+ ColumnDef *colDef = lfirst(listptr);
+
+ if (colDef->storage != 0)
+ colDef->storage = 0;
+ }
+ }
+
/*
* Create a tuple descriptor from the relation schema. Note that this
* deals with column names, types, and NOT NULL constraints, but not
***************
*** 1024,1029 **** ExecuteTruncate(TruncateStmt *stmt)
--- 1049,1060 ----
/* find_all_inheritors already got lock */
rel = heap_open(childrelid, NoLock);
+ /* ignore if the rel is a foreign table */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ heap_close(rel, NoLock);
+ continue;
+ }
truncate_check_rel(rel);
rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
***************
*** 3091,3114 **** 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;
--- 3122,3149 ----
* 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;
***************
*** 3121,3127 **** 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;
--- 3156,3163 ----
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;
***************
*** 3243,3253 **** 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;
--- 3279,3295 ----
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);
pass = AT_PASS_MISC;
***************
*** 3281,3287 **** 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 */
case AT_EnableRowSecurity:
--- 3323,3328 ----
***************
*** 4275,4281 **** 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
--- 4316,4323 ----
*/
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
***************
*** 4303,4310 **** 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);
}
}
--- 4345,4356 ----
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);
}
}
***************
*** 4594,4600 **** 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);
--- 4640,4646 ----
/* 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);
***************
*** 4890,4895 **** ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4936,4946 ----
/* 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 */
***************
*** 5490,5496 **** 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
--- 5541,5547 ----
/* 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
***************
*** 5882,5888 **** 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
--- 5933,5939 ----
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/*
* Call AddRelationNewConstraints to do the work, making sure it works on
***************
*** 5893,5901 **** 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 */
--- 5944,5960 ----
* 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 */
***************
*** 7382,7388 **** 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);
--- 7441,7447 ----
/* 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);
***************
*** 7717,7723 **** 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,
--- 7776,7785 ----
* 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;
***************
*** 149,154 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
--- 150,169 ----
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.
*/
***************
*** 253,259 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
PushActiveSnapshot(GetTransactionSnapshot());
}
! analyze_rel(relid, vacstmt, in_outer_xact, vac_strategy);
if (use_own_xacts)
{
--- 268,275 ----
PushActiveSnapshot(GetTransactionSnapshot());
}
! analyze_rel(relid, vacstmt, vac_mode, in_outer_xact,
! vac_strategy);
if (use_own_xacts)
{
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 2308,2313 **** EvalPlanQualFetchRowMarks(EPQState *epqstate)
--- 2308,2333 ----
Assert(erm->markType == ROW_MARK_COPY);
+ /* if child rel, must check whether it produced this row */
+ if (erm->rti != erm->prti)
+ {
+ Oid tableoid;
+
+ datum = ExecGetJunkAttribute(epqstate->origslot,
+ aerm->toidAttNo,
+ &isNull);
+ /* non-locked rels could be on the inside of outer joins */
+ if (isNull)
+ continue;
+ tableoid = DatumGetObjectId(datum);
+
+ if (tableoid != RelationGetRelid(erm->relation))
+ {
+ /* this child is inactive right now */
+ continue;
+ }
+ }
+
/* fetch the whole-row Var for the relation */
datum = ExecGetJunkAttribute(epqstate->origslot,
aerm->wholeAttNo,
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 1994,1999 **** _copyRangeTblEntry(const RangeTblEntry *from)
--- 1994,2000 ----
COPY_NODE_FIELD(eref);
COPY_SCALAR_FIELD(lateral);
COPY_SCALAR_FIELD(inh);
+ COPY_SCALAR_FIELD(hasForeign);
COPY_SCALAR_FIELD(inFromCl);
COPY_SCALAR_FIELD(requiredPerms);
COPY_SCALAR_FIELD(checkAsUser);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 2342,2347 **** _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
--- 2342,2348 ----
COMPARE_NODE_FIELD(eref);
COMPARE_SCALAR_FIELD(lateral);
COMPARE_SCALAR_FIELD(inh);
+ COMPARE_SCALAR_FIELD(hasForeign);
COMPARE_SCALAR_FIELD(inFromCl);
COMPARE_SCALAR_FIELD(requiredPerms);
COMPARE_SCALAR_FIELD(checkAsUser);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 2423,2428 **** _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
--- 2423,2429 ----
WRITE_BOOL_FIELD(lateral);
WRITE_BOOL_FIELD(inh);
+ WRITE_BOOL_FIELD(hasForeign);
WRITE_BOOL_FIELD(inFromCl);
WRITE_UINT_FIELD(requiredPerms);
WRITE_OID_FIELD(checkAsUser);
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 1249,1254 **** _readRangeTblEntry(void)
--- 1249,1255 ----
READ_BOOL_FIELD(lateral);
READ_BOOL_FIELD(inh);
+ READ_BOOL_FIELD(hasForeign);
READ_BOOL_FIELD(inFromCl);
READ_UINT_FIELD(requiredPerms);
READ_OID_FIELD(checkAsUser);
*** a/src/backend/optimizer/prep/preptlist.c
--- b/src/backend/optimizer/prep/preptlist.c
***************
*** 111,116 **** preprocess_targetlist(PlannerInfo *root, List *tlist)
--- 111,118 ----
/* if parent of inheritance tree, need the tableoid too */
if (rc->isParent)
{
+ RangeTblEntry *rte = rt_fetch(rc->rti, parse->rtable);
+
var = makeVar(rc->rti,
TableOidAttributeNumber,
OIDOID,
***************
*** 123,128 **** preprocess_targetlist(PlannerInfo *root, List *tlist)
--- 125,146 ----
pstrdup(resname),
true);
tlist = lappend(tlist, tle);
+
+ /* if including foreign tables, fetch the whole row too */
+ if (rte->hasForeign)
+ {
+ /* Not a table, so we need the whole row as a junk var */
+ var = makeWholeRowVar(rt_fetch(rc->rti, range_table),
+ rc->rti,
+ 0,
+ false);
+ snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId);
+ tle = makeTargetEntry((Expr *) var,
+ list_length(tlist) + 1,
+ pstrdup(resname),
+ true);
+ tlist = lappend(tlist, tle);
+ }
}
}
else
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 1237,1242 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1237,1243 ----
LOCKMODE lockmode;
List *inhOIDs;
List *appinfos;
+ bool hasForeign;
ListCell *l;
/* Does RT entry allow inheritance? */
***************
*** 1309,1314 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1310,1316 ----
/* Scan the inheritance set and expand it */
appinfos = NIL;
+ hasForeign = false;
foreach(l, inhOIDs)
{
Oid childOID = lfirst_oid(l);
***************
*** 1338,1348 **** 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);
--- 1340,1351 ----
/*
* 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);
***************
*** 1388,1400 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
newrc->rti = childRTindex;
newrc->prti = rti;
newrc->rowmarkId = oldrc->rowmarkId;
! newrc->markType = oldrc->markType;
newrc->waitPolicy = oldrc->waitPolicy;
newrc->isParent = false;
root->rowMarks = lappend(root->rowMarks, newrc);
}
/* Close child relations, but keep locks */
if (childOID != parentOID)
heap_close(newrelation, NoLock);
--- 1391,1409 ----
newrc->rti = childRTindex;
newrc->prti = rti;
newrc->rowmarkId = oldrc->rowmarkId;
! if (childrte->relkind == RELKIND_FOREIGN_TABLE)
! newrc->markType = ROW_MARK_COPY;
! else
! newrc->markType = oldrc->markType;
newrc->waitPolicy = oldrc->waitPolicy;
newrc->isParent = false;
root->rowMarks = lappend(root->rowMarks, newrc);
}
+ if (childrte->relkind == RELKIND_FOREIGN_TABLE)
+ hasForeign = true;
+
/* Close child relations, but keep locks */
if (childOID != parentOID)
heap_close(newrelation, NoLock);
***************
*** 1416,1421 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1425,1434 ----
/* Otherwise, OK to add to root->append_rel_list */
root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+
+ /* And mark the parent table as having children that are foreign, if so */
+ if (hasForeign)
+ rte->hasForeign = true;
}
/*
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 4335,4366 **** 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;
}
;
--- 4335,4366 ----
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/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);
***************
*** 175,181 **** extern void lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt,
BufferAccessStrategy bstrategy);
/* in commands/analyze.c */
! extern void analyze_rel(Oid relid, VacuumStmt *vacstmt,
bool in_outer_xact, BufferAccessStrategy bstrategy);
extern bool std_typanalyze(VacAttrStats *stats);
extern double anl_random_fract(void);
--- 184,190 ----
BufferAccessStrategy bstrategy);
/* in commands/analyze.c */
! extern void analyze_rel(Oid relid, VacuumStmt *vacstmt, VacuumMode vacmode,
bool in_outer_xact, BufferAccessStrategy bstrategy);
extern bool std_typanalyze(VacAttrStats *stats);
extern double anl_random_fract(void);
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 812,817 **** typedef struct RangeTblEntry
--- 812,818 ----
Alias *eref; /* expanded reference names */
bool lateral; /* subquery, function, or values is LATERAL? */
bool inh; /* inheritance requested? */
+ bool hasForeign; /* does inheritance include foreign tables? */
bool inFromCl; /* present in FROM clause? */
AclMode requiredPerms; /* bitmask of required access permissions */
Oid checkAsUser; /* if valid, check access as this role */
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 819,824 **** typedef enum RowMarkType
--- 819,826 ----
* The tableoid column is only present for an inheritance hierarchy.
* When markType == ROW_MARK_COPY, there is instead a single column named
* wholerow%u whole-row value of relation
+ * The wholerow column is also present for an inheritance hierarchy that
+ * contains foreign tables.
* In all three cases, %u represents the rowmark ID number (rowmarkId).
* This number is unique within a plan tree, except that child relation
* entries copy their parent's rowmarkId. (Assigning unique numbers
*** a/src/test/regress/expected/foreign_data.out
--- b/src/test/regress/expected/foreign_data.out
***************
*** 1207,1212 **** DROP TRIGGER trigtest_before_row ON foreign_schema.foreign_table_1;
--- 1207,1554 ----
DROP TRIGGER trigtest_after_stmt ON foreign_schema.foreign_table_1;
DROP TRIGGER trigtest_after_row ON foreign_schema.foreign_table_1;
DROP FUNCTION dummy_trigger();
+ -- Table inheritance
+ CREATE TABLE pt1 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ );
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ ALTER FOREIGN TABLE ft2 NO INHERIT pt1;
+ DROP FOREIGN TABLE ft2;
+ CREATE FOREIGN TABLE ft2 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ ALTER FOREIGN TABLE ft2 INHERIT pt1;
+ CREATE VIEW v1 (c1, c2, c3) AS SELECT 1, 'v1'::text, '1994-01-01'::date;
+ ALTER TABLE v1 INHERIT pt1; -- ERROR
+ ERROR: "v1" is not a table or foreign table
+ DROP VIEW v1;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- add attributes recursively
+ ALTER TABLE pt1 ADD COLUMN c4 integer;
+ ALTER TABLE pt1 ADD COLUMN c5 integer DEFAULT 0;
+ ALTER TABLE pt1 ADD COLUMN c6 integer;
+ ALTER TABLE pt1 ADD COLUMN c7 integer NOT NULL;
+ ALTER TABLE pt1 ADD COLUMN c8 integer;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ c4 | integer | | plain | |
+ c5 | integer | default 0 | plain | |
+ c6 | integer | | plain | |
+ c7 | integer | not null | plain | |
+ c8 | integer | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ c4 | integer | | | plain | |
+ c5 | integer | default 0 | | plain | |
+ c6 | integer | | | plain | |
+ c7 | integer | not null | | plain | |
+ c8 | integer | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- alter attributes recursively
+ ALTER TABLE pt1 ALTER COLUMN c4 SET DEFAULT 0;
+ ALTER TABLE pt1 ALTER COLUMN c5 DROP DEFAULT;
+ ALTER TABLE pt1 ALTER COLUMN c6 SET NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN c7 DROP NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN c8 TYPE char(10) USING '0'; -- ERROR
+ ERROR: "ft2" is not a table
+ ALTER TABLE pt1 ALTER COLUMN c8 TYPE char(10);
+ ALTER TABLE pt1 ALTER COLUMN c8 SET DATA TYPE text;
+ ALTER TABLE pt1 ALTER COLUMN c1 SET STATISTICS 10000;
+ ALTER TABLE pt1 ALTER COLUMN c1 SET (n_distinct = 100);
+ ALTER TABLE pt1 ALTER COLUMN c8 SET STATISTICS -1;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ c4 | integer | default 0 | plain | |
+ c5 | integer | | plain | |
+ c6 | integer | not null | plain | |
+ c7 | integer | | plain | |
+ c8 | text | | extended | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | 10000 |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ c4 | integer | default 0 | | plain | |
+ c5 | integer | | | plain | |
+ c6 | integer | not null | | plain | |
+ c7 | integer | | | plain | |
+ c8 | text | | | extended | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- drop attributes recursively
+ ALTER TABLE pt1 DROP COLUMN c4;
+ ALTER TABLE pt1 DROP COLUMN c5;
+ ALTER TABLE pt1 DROP COLUMN c6;
+ ALTER TABLE pt1 DROP COLUMN c7;
+ ALTER TABLE pt1 DROP COLUMN c8;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | 10000 |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- cannot change storage mode for an attribute of foreign tables
+ ALTER TABLE pt1 ALTER COLUMN c2 SET STORAGE EXTERNAL;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | external | |
+ c3 | date | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | 10000 |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ DROP FOREIGN TABLE ft2;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | external | |
+ c3 | date | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ ALTER TABLE pt1 ALTER COLUMN c2 SET STORAGE EXTENDED;
+ -- add constraints recursively
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk1 CHECK (c1 > 0) NO INHERIT;
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '');
+ -- connoinherit should be true for NO INHERIT constraint
+ SELECT pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pgc.connoinherit FROM pg_class AS pc INNER JOIN pg_constraint AS pgc ON (pgc.conrelid = pc.oid) WHERE pc.relname = 'pt1' order by 1,2;
+ relname | conname | contype | conislocal | coninhcount | connoinherit
+ ---------+---------+---------+------------+-------------+--------------
+ pt1 | pt1chk1 | c | t | 0 | t
+ pt1 | pt1chk2 | c | t | 0 | f
+ (2 rows)
+
+ DROP FOREIGN TABLE ft2;
+ CREATE FOREIGN TABLE ft2 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ -- child must have parent's INHERIT constraints
+ ALTER FOREIGN TABLE ft2 INHERIT pt1; -- ERROR
+ ERROR: child table is missing constraint "pt1chk2"
+ ALTER FOREIGN TABLE ft2 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '');
+ ALTER FOREIGN TABLE ft2 INHERIT pt1;
+ -- child does not inherit NO INHERIT constraints
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Check constraints:
+ "pt1chk1" CHECK (c1 > 0) NO INHERIT
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- drop constraints recursively
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk1 CASCADE;
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk2 CASCADE;
+ -- NOT VALID should be ignored for foreign tables
+ INSERT INTO pt1 VALUES (1, 'pt1'::text, '1994-01-01'::date);
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '') NOT VALID;
+ NOTICE: merging constraint "pt1chk2" with inherited definition
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text) NOT VALID
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- VALIDATE CONSTRAINT should work by ignoring foreign tables
+ ALTER TABLE pt1 VALIDATE CONSTRAINT pt1chk2;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- cannot add an OID system column to foreign tables
+ ALTER TABLE pt1 SET WITH OIDS; -- ERROR
+ ERROR: cannot add OID column to foreign table "ft2"
+ DROP FOREIGN TABLE ft2;
+ ALTER TABLE pt1 SET WITH OIDS;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
+ ERROR: cannot inherit from relation with OIDs
+ ALTER TABLE pt1 SET WITHOUT OIDS;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ -- changes name of an attribute recursively
+ ALTER TABLE pt1 RENAME COLUMN c1 TO f1;
+ ALTER TABLE pt1 RENAME COLUMN c2 TO f2;
+ ALTER TABLE pt1 RENAME COLUMN c3 TO f3;
+ -- changes name of a constraint recursively
+ ALTER TABLE pt1 RENAME CONSTRAINT pt1chk2 TO f2_check;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ f1 | integer | not null | plain | 10000 |
+ f2 | text | | extended | |
+ f3 | date | | plain | |
+ Check constraints:
+ "f2_check" CHECK (f2 <> ''::text)
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ f1 | integer | not null | | plain | |
+ f2 | text | | | extended | |
+ f3 | date | | | plain | |
+ Check constraints:
+ "f2_check" CHECK (f2 <> ''::text)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- TRUNCATE should work by ignoring foreign tables
+ TRUNCATE pt1;
+ TRUNCATE ft2; -- ERROR
+ ERROR: "ft2" is not a table
+ DROP TABLE pt1 CASCADE;
+ NOTICE: drop cascades to foreign table ft2
-- IMPORT FOREIGN SCHEMA
IMPORT FOREIGN SCHEMA s1 FROM SERVER s9 INTO public; -- ERROR
ERROR: foreign-data wrapper "foo" has no handler
*** a/src/test/regress/sql/foreign_data.sql
--- b/src/test/regress/sql/foreign_data.sql
***************
*** 522,527 **** DROP TRIGGER trigtest_after_row ON foreign_schema.foreign_table_1;
--- 522,650 ----
DROP FUNCTION dummy_trigger();
+ -- Table inheritance
+ CREATE TABLE pt1 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ );
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ ALTER FOREIGN TABLE ft2 NO INHERIT pt1;
+ DROP FOREIGN TABLE ft2;
+ CREATE FOREIGN TABLE ft2 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ ALTER FOREIGN TABLE ft2 INHERIT pt1;
+ CREATE VIEW v1 (c1, c2, c3) AS SELECT 1, 'v1'::text, '1994-01-01'::date;
+ ALTER TABLE v1 INHERIT pt1; -- ERROR
+ DROP VIEW v1;
+ \d+ pt1
+ \d+ ft2
+
+ -- add attributes recursively
+ ALTER TABLE pt1 ADD COLUMN c4 integer;
+ ALTER TABLE pt1 ADD COLUMN c5 integer DEFAULT 0;
+ ALTER TABLE pt1 ADD COLUMN c6 integer;
+ ALTER TABLE pt1 ADD COLUMN c7 integer NOT NULL;
+ ALTER TABLE pt1 ADD COLUMN c8 integer;
+ \d+ pt1
+ \d+ ft2
+
+ -- alter attributes recursively
+ ALTER TABLE pt1 ALTER COLUMN c4 SET DEFAULT 0;
+ ALTER TABLE pt1 ALTER COLUMN c5 DROP DEFAULT;
+ ALTER TABLE pt1 ALTER COLUMN c6 SET NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN c7 DROP NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN c8 TYPE char(10) USING '0'; -- ERROR
+ ALTER TABLE pt1 ALTER COLUMN c8 TYPE char(10);
+ ALTER TABLE pt1 ALTER COLUMN c8 SET DATA TYPE text;
+ ALTER TABLE pt1 ALTER COLUMN c1 SET STATISTICS 10000;
+ ALTER TABLE pt1 ALTER COLUMN c1 SET (n_distinct = 100);
+ ALTER TABLE pt1 ALTER COLUMN c8 SET STATISTICS -1;
+ \d+ pt1
+ \d+ ft2
+
+ -- drop attributes recursively
+ ALTER TABLE pt1 DROP COLUMN c4;
+ ALTER TABLE pt1 DROP COLUMN c5;
+ ALTER TABLE pt1 DROP COLUMN c6;
+ ALTER TABLE pt1 DROP COLUMN c7;
+ ALTER TABLE pt1 DROP COLUMN c8;
+ \d+ pt1
+ \d+ ft2
+
+ -- cannot change storage mode for an attribute of foreign tables
+ ALTER TABLE pt1 ALTER COLUMN c2 SET STORAGE EXTERNAL;
+ \d+ pt1
+ \d+ ft2
+ DROP FOREIGN TABLE ft2;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ \d+ ft2
+ ALTER TABLE pt1 ALTER COLUMN c2 SET STORAGE EXTENDED;
+
+ -- add constraints recursively
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk1 CHECK (c1 > 0) NO INHERIT;
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '');
+ -- connoinherit should be true for NO INHERIT constraint
+ SELECT pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pgc.connoinherit FROM pg_class AS pc INNER JOIN pg_constraint AS pgc ON (pgc.conrelid = pc.oid) WHERE pc.relname = 'pt1' order by 1,2;
+ DROP FOREIGN TABLE ft2;
+ CREATE FOREIGN TABLE ft2 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ -- child must have parent's INHERIT constraints
+ ALTER FOREIGN TABLE ft2 INHERIT pt1; -- ERROR
+ ALTER FOREIGN TABLE ft2 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '');
+ ALTER FOREIGN TABLE ft2 INHERIT pt1;
+ -- child does not inherit NO INHERIT constraints
+ \d+ pt1
+ \d+ ft2
+
+ -- drop constraints recursively
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk1 CASCADE;
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk2 CASCADE;
+
+ -- NOT VALID should be ignored for foreign tables
+ INSERT INTO pt1 VALUES (1, 'pt1'::text, '1994-01-01'::date);
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '') NOT VALID;
+ \d+ pt1
+ \d+ ft2
+ -- VALIDATE CONSTRAINT should work by ignoring foreign tables
+ ALTER TABLE pt1 VALIDATE CONSTRAINT pt1chk2;
+ \d+ pt1
+ \d+ ft2
+
+ -- cannot add an OID system column to foreign tables
+ ALTER TABLE pt1 SET WITH OIDS; -- ERROR
+ DROP FOREIGN TABLE ft2;
+ ALTER TABLE pt1 SET WITH OIDS;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
+ ALTER TABLE pt1 SET WITHOUT OIDS;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+
+ -- changes name of an attribute recursively
+ ALTER TABLE pt1 RENAME COLUMN c1 TO f1;
+ ALTER TABLE pt1 RENAME COLUMN c2 TO f2;
+ ALTER TABLE pt1 RENAME COLUMN c3 TO f3;
+ -- changes name of a constraint recursively
+ ALTER TABLE pt1 RENAME CONSTRAINT pt1chk2 TO f2_check;
+ \d+ pt1
+ \d+ ft2
+
+ -- TRUNCATE should work by ignoring foreign tables
+ TRUNCATE pt1;
+ TRUNCATE ft2; -- ERROR
+
+ DROP TABLE pt1 CASCADE;
+
-- IMPORT FOREIGN SCHEMA
IMPORT FOREIGN SCHEMA s1 FROM SERVER s9 INTO public; -- ERROR
IMPORT FOREIGN SCHEMA s1 LIMIT TO (t1) FROM SERVER s9 INTO public; --ERROR
[text/x-patch] fdw-chk-3.patch (25.1K, ../../545CB4A2.1030303@lab.ntt.co.jp/3-fdw-chk-3.patch)
download | inline diff:
*** a/contrib/file_fdw/input/file_fdw.source
--- b/contrib/file_fdw/input/file_fdw.source
***************
*** 134,139 **** DELETE FROM agg_csv WHERE a = 100;
--- 134,149 ----
-- but this should be ignored
SELECT * FROM agg_csv FOR UPDATE;
+ -- constraint exclusion tests
+ ALTER FOREIGN TABLE agg_csv ADD CONSTRAINT agg_csv_a_check CHECK (a >= 0);
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
+ SET constraint_exclusion = 'on';
+ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
+ SET constraint_exclusion = 'partition';
+ \t off
+ ALTER FOREIGN TABLE agg_csv DROP CONSTRAINT agg_csv_a_check;
+
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/file_fdw/output/file_fdw.source
--- b/contrib/file_fdw/output/file_fdw.source
***************
*** 219,224 **** SELECT * FROM agg_csv FOR UPDATE;
--- 219,242 ----
42 | 324.78
(3 rows)
+ -- constraint exclusion tests
+ ALTER FOREIGN TABLE agg_csv ADD CONSTRAINT agg_csv_a_check CHECK (a >= 0);
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
+ Foreign Scan on public.agg_csv
+ Output: a, b
+ Filter: (agg_csv.a < 0)
+ Foreign File: @abs_srcdir@/data/agg.csv
+
+ SET constraint_exclusion = 'on';
+ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
+ Result
+ Output: a, b
+ One-Time Filter: false
+
+ SET constraint_exclusion = 'partition';
+ \t off
+ ALTER FOREIGN TABLE agg_csv DROP CONSTRAINT agg_csv_a_check;
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 2488,2493 **** select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
--- 2488,2515 ----
(13 rows)
-- ===================================================================
+ -- test constraint exclusion stuff
+ -- ===================================================================
+ ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0);
+ EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 WHERE c2 < 0;
+ QUERY PLAN
+ ------------------------------------------------------------------------------------------
+ Foreign Scan on public.ft1
+ Output: c1, c2, c3, c4, c5, c6, c7, c8
+ Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c2 < 0))
+ (3 rows)
+
+ SET constraint_exclusion = 'on';
+ EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 WHERE c2 < 0;
+ QUERY PLAN
+ ------------------------------------------
+ Result
+ Output: c1, c2, c3, c4, c5, c6, c7, c8
+ One-Time Filter: false
+ (3 rows)
+
+ SET constraint_exclusion = 'partition';
+ -- ===================================================================
-- test serial columns (ie, sequence-based defaults)
-- ===================================================================
create table loc1 (f1 serial, f2 text);
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 387,392 **** select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
--- 387,401 ----
select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
-- ===================================================================
+ -- test constraint exclusion stuff
+ -- ===================================================================
+ ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0);
+ EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 WHERE c2 < 0;
+ SET constraint_exclusion = 'on';
+ EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 WHERE c2 < 0;
+ SET constraint_exclusion = 'partition';
+
+ -- ===================================================================
-- test serial columns (ie, sequence-based defaults)
-- ===================================================================
create table loc1 (f1 serial, f2 text);
*** 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 ] <replaceable class="PARAMETER">name</replaceab
--- 42,49 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ ADD <replaceable class="PARAMETER">table_constraint</replaceable>
+ DROP CONSTRAINT [ IF EXISTS ] <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
DISABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
ENABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
ENABLE REPLICA TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
***************
*** 152,157 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 154,183 ----
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds a new constraint to a table using the same syntax as
+ <xref linkend="SQL-CREATEFOREIGNTABLE">.
+ 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.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DROP CONSTRAINT [ IF EXISTS ]</literal></term>
+ <listitem>
+ <para>
+ This form drops the specified constraint on a table.
+ If <literal>IF EXISTS</literal> is specified and the constraint
+ does not exist, no error is thrown. In this case a notice is issued instead.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>DISABLE</literal>/<literal>ENABLE [ REPLICA | ALWAYS ] TRIGGER</literal></term>
<listitem>
***************
*** 284,289 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 310,333 ----
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="PARAMETER">table_constraint</replaceable></term>
+ <listitem>
+ <para>
+ New table constraint for the table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">constraint_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing constraint to drop.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>CASCADE</literal></term>
<listitem>
***************
*** 365,374 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint is
! added, or a column type is changed with <literal>SET DATA TYPE</>. It is
! the user's responsibility to ensure that the table definition matches the
! remote side.
</para>
<para>
--- 409,418 ----
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint or
! <literal>CHECK</> constraint is added, or a column type is changed with
! <literal>SET DATA TYPE</>. It is the user's responsibility to ensure that
! the table definition matches the remote side.
</para>
<para>
*** a/doc/src/sgml/ref/create_foreign_table.sgml
--- b/doc/src/sgml/ref/create_foreign_table.sgml
***************
*** 19,25 ****
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
SERVER <replaceable class="parameter">server_name</replaceable>
--- 19,26 ----
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
! | <replaceable>table_constraint</replaceable> }
[, ... ]
] )
SERVER <replaceable class="parameter">server_name</replaceable>
***************
*** 30,36 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 31,43 ----
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ NOT NULL |
NULL |
+ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) |
DEFAULT <replaceable>default_expr</replaceable> }
+
+ <phrase>and <replaceable class="PARAMETER">table_constraint</replaceable> is:</phrase>
+
+ [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
+ { CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) }
</synopsis>
</refsynopsisdiv>
***************
*** 138,143 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 145,171 ----
</varlistentry>
<varlistentry>
+ <term><literal>CHECK ( <replaceable class="PARAMETER">expression</replaceable> )</literal></term>
+ <listitem>
+ <para>
+ The <literal>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.
+ </para>
+
+ <para>
+ Currently, <literal>CHECK</literal> expressions cannot contain
+ subqueries nor refer to variables other than columns of the
+ current row. The system column <literal>tableoid</literal>
+ may be referenced, but not any other system column.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>DEFAULT
<replaceable>default_expr</replaceable></literal></term>
<listitem>
***************
*** 187,192 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 215,230 ----
</refsect1>
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ Constraints on foreign tables are not enforced on insert or update.
+ Those definitions simply declare the constraints hold for all rows
+ in the foreign tables. It is the user's responsibility to ensure
+ that those definitions match the remote side.
+ </para>
+ </refsect1>
<refsect1 id="SQL-CREATEFOREIGNTABLE-examples">
<title>Examples</title>
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 2222,2227 **** AddRelationNewConstraints(Relation rel,
--- 2222,2241 ----
if (cdef->contype != CONSTR_CHECK)
continue;
+ /* Don't allow NO INHERIT for foreign tables */
+ if (cdef->is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("CHECK constraints on foreign tables cannot be marked NO INHERIT")));
+
+ /* Don't allow NOT VALID for foreign tables */
+ if (cdef->skip_validation &&
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("NOT VALID is not supported for CHECK constraints on foreign tables")));
+
if (cdef->raw_expr != NULL)
{
Assert(cdef->cooked_expr == NULL);
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 478,487 **** 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,
--- 478,483 ----
***************
*** 3143,3149 **** 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)
--- 3139,3145 ----
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)
***************
*** 3157,3163 **** 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)
--- 3153,3159 ----
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)
*** 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("unique or primary key 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("unique, primary key, exclusion, or foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
*** a/src/test/regress/expected/foreign_data.out
--- b/src/test/regress/expected/foreign_data.out
***************
*** 670,676 **** LINE 1: CREATE FOREIGN TABLE ft1 () SERVER s0 WITH OIDS;
^
CREATE FOREIGN TABLE ft1 (
c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
! c2 text OPTIONS (param2 'val2', param3 'val3'),
c3 date
) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
COMMENT ON FOREIGN TABLE ft1 IS 'ft1';
--- 670,682 ----
^
CREATE FOREIGN TABLE ft1 (
c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
! c2 text OPTIONS (param2 'val2', param3 'val3') CHECK (c2 <> '') NO INHERIT,
! c3 date
! ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
! ERROR: CHECK constraints on foreign tables cannot be marked NO INHERIT
! CREATE FOREIGN TABLE ft1 (
! c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
! c2 text OPTIONS (param2 'val2', param3 'val3') CHECK (c2 <> ''),
c3 date
) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
COMMENT ON FOREIGN TABLE ft1 IS 'ft1';
***************
*** 682,687 **** COMMENT ON COLUMN ft1.c1 IS 'ft1.c1';
--- 688,695 ----
c1 | integer | not null | ("param 1" 'val1') | plain | | ft1.c1
c2 | text | | (param2 'val2', param3 'val3') | extended | |
c3 | date | | | plain | |
+ Check constraints:
+ "ft1_c2_check" CHECK (c2 <> ''::text)
Server: s0
FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
***************
*** 740,745 **** ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET STATISTICS -1;
--- 748,755 ----
c8 | text | | (p2 'V2') | extended | |
c9 | integer | | | plain | |
c10 | integer | | (p1 'v1') | plain | |
+ Check constraints:
+ "ft1_c2_check" CHECK (c2 <> ''::text)
Server: s0
FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
***************
*** 748,763 **** 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;
--- 758,775 ----
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) NO INHERIT; -- ERROR
! ERROR: CHECK constraints on foreign tables cannot be marked NO INHERIT
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0) NOT VALID; -- ERROR
! ERROR: NOT VALID is not supported for CHECK constraints on foreign tables
! 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_c1_check;
! ERROR: constraint "ft1_c1_check" of relation "ft1" does not exist
! 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;
***************
*** 785,790 **** ALTER FOREIGN TABLE foreign_schema.ft1 RENAME TO foreign_table_1;
--- 797,804 ----
c7 | integer | | (p1 'v1', p2 'v2')
c8 | text | | (p2 'V2')
c10 | integer | | (p1 'v1')
+ Check constraints:
+ "ft1_c2_check" CHECK (c2 <> ''::text)
Server: s0
FDW Options: (quote '~', "be quoted" 'value', escape '@')
*** a/src/test/regress/sql/foreign_data.sql
--- b/src/test/regress/sql/foreign_data.sql
***************
*** 270,276 **** CREATE FOREIGN TABLE ft1 () SERVER no_server; -- ERROR
CREATE FOREIGN TABLE ft1 () SERVER s0 WITH OIDS; -- ERROR
CREATE FOREIGN TABLE ft1 (
c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
! c2 text OPTIONS (param2 'val2', param3 'val3'),
c3 date
) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
COMMENT ON FOREIGN TABLE ft1 IS 'ft1';
--- 270,281 ----
CREATE FOREIGN TABLE ft1 () SERVER s0 WITH OIDS; -- ERROR
CREATE FOREIGN TABLE ft1 (
c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
! c2 text OPTIONS (param2 'val2', param3 'val3') CHECK (c2 <> '') NO INHERIT,
! c3 date
! ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
! CREATE FOREIGN TABLE ft1 (
! c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
! c2 text OPTIONS (param2 'val2', param3 'val3') CHECK (c2 <> ''),
c3 date
) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
COMMENT ON FOREIGN TABLE ft1 IS 'ft1';
***************
*** 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 '@');
--- 319,331 ----
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) NO INHERIT; -- ERROR
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0) NOT VALID; -- 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 '@');
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-11-12 11:04 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: Ashutosh Bapat @ 2014-11-12 11:04 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
Hi Fujita-san,
I reviewed fdw-chk-3 patch. Here are my comments
Sanity
--------
1. The patch applies on the latest master using "patch but not by git apply
2. it compiles clean
3. Regression run is clean, including the contrib module regressions
Tests
-------
1. The tests added in file_fdw module look good. We should add tests for
CREATE TABLE with CHECK CONSTRAINT also.
2. For postgres_fdw we need tests to check the behaviour in case the
constraints mismatch between the remote table and its local foreign table
declaration in case of INSERT, UPDATE and SELECT.
3. In the testcases for postgres_fdw it seems that you have forgotten to
add statement after SET constraint_exclusion to 'partition'
4. In test foreign_data there are changes to fix the diffs caused by these
changes like below
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
+NOTICE: constraint "no_const" of relation "ft1" does not exist, skipping
ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c1_check;
-ERROR: "ft1" is not a table
+ERROR: constraint "ft1_c1_check" of relation "ft1" does not exist
Earlier when constraints were not supported for FOREIGN TABLE, these tests
made sure the same functionality. So, even though the corresponding
constraints were not created on the table (in fact it didn't allow the
creation as well). Now that the constraints are allowed, I think the tests
for "no_const" (without IF EXISTS) and "ft1_c1_check" are duplicating the
same testcase. May be we should review this set of statement in the light
of new functionality.
Code and implementation
----------------------------------
The usage of NO INHERIT and NOT VALID with CONSTRAINT on foreign table is
blocked, but corresponding documentation entry doesn't mention so. Since
foreign tables can not be inherited NO INHERIT option isn't applicable to
foreign tables and the constraints on the foreign tables are declarative,
hence NOT VALID option is also not applicable. So, I agree with what the
code is doing, but that should be reflected in documentation with this
explanation.
Rest of the code modifies the condition checks for CHECK CONSTRAINTs on
foreign tables, and it looks good to me.
On Fri, Nov 7, 2014 at 5:31 PM, Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
wrote:
> (2014/11/07 14:57), Kyotaro HORIGUCHI wrote:
>
>> Here are separated patches.
>>>>>
>>>>> fdw-chk.patch - CHECK constraints on foreign tables
>>>>> fdw-inh.patch - table inheritance with foreign tables
>>>>>
>>>>> The latter has been created on top of [1].
>>>>>
>>>>
>>>> [1]
>>>>> http://www.postgresql.org/message-id/540DA168.3040407@lab.ntt.co.jp
>>>>>
>>>>
>>> To be exact, it has been created on top of [1] and fdw-chk.patch.
>>>>
>>>
>> I tried both patches on the current head, the newly added
>> parameter to analyze_rel() hampered them from applying but it is
>> easy to fix seemingly and almost all the other part was applied
>> cleanly.
>>
>
> Thanks for the review!
>
> By the way, are these the result of simply splitting of your last
>> patch, foreign_inherit-v15.patch?
>>
>> http://www.postgresql.org/message-id/53FEEF94.6040101@lab.ntt.co.jp
>>
>
> The answer is "no".
>
> The result of apllying whole-in-one version and this splitted
>> version seem to have many differences. Did you added even other
>> changes? Or do I understand this patch wrongly?
>>
>
> As I said before, I splitted the whole-in-one version into three: 1) CHECK
> constraint patch (ie fdw-chk.patch), 2) table inheritance patch (ie
> fdw-inh.patch) and 3) path reparameterization patch (not posted). In
> addition to that, I slightly modified #1 and #2.
>
> IIUC, #3 would be useful not only for the inheritance cases but for union
> all cases. So, I plan to propose it independently in the next CF.
>
> I noticed that the latter disallows TRUNCATE on inheritance trees that
>>>> contain at least one child foreign table. But I think it would be
>>>> better to allow it, with the semantics that we quietly ignore the
>>>> child
>>>> foreign tables and apply the operation to the child plain tables,
>>>> which
>>>> is the same semantics as ALTER COLUMN SET STORAGE on such inheritance
>>>> trees. Comments welcome.
>>>>
>>>
>>> Done. And I've also a bit revised regression tests for both
>>> patches. Patches attached.
>>>
>>
> I rebased the patches to the latest head. Here are updated patches.
>
> Other changes:
>
> * fdw-chk-3.patch: the updated patch revises some ereport messages a
> little bit.
>
> * fdw-inh-3.patch: I noticed that there is a doc bug in the previous
> patch. The updated patch fixes that, adds a bit more docs, and revises
> regression tests in foreign_data.sql a bit further.
>
>
> Thanks,
>
> Best regards,
> Etsuro Fujita
>
>
> --
> Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-hackers
>
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-11-13 06:23 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: Ashutosh Bapat @ 2014-11-13 06:23 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
Hi Fujita-san,
I tried to apply fdw-inh-3.patch on the latest head from master branch. It
failed to apply using both patch and git apply.
"patch" failed to apply because of rejections in
contrib/file_fdw/output/file_fdw.source and
doc/src/sgml/ref/create_foreign_table.sgml
On Fri, Nov 7, 2014 at 5:31 PM, Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
wrote:
> (2014/11/07 14:57), Kyotaro HORIGUCHI wrote:
>
>> Here are separated patches.
>>>>>
>>>>> fdw-chk.patch - CHECK constraints on foreign tables
>>>>> fdw-inh.patch - table inheritance with foreign tables
>>>>>
>>>>> The latter has been created on top of [1].
>>>>>
>>>>
>>>> [1]
>>>>> http://www.postgresql.org/message-id/540DA168.3040407@lab.ntt.co.jp
>>>>>
>>>>
>>> To be exact, it has been created on top of [1] and fdw-chk.patch.
>>>>
>>>
>> I tried both patches on the current head, the newly added
>> parameter to analyze_rel() hampered them from applying but it is
>> easy to fix seemingly and almost all the other part was applied
>> cleanly.
>>
>
> Thanks for the review!
>
> By the way, are these the result of simply splitting of your last
>> patch, foreign_inherit-v15.patch?
>>
>> http://www.postgresql.org/message-id/53FEEF94.6040101@lab.ntt.co.jp
>>
>
> The answer is "no".
>
> The result of apllying whole-in-one version and this splitted
>> version seem to have many differences. Did you added even other
>> changes? Or do I understand this patch wrongly?
>>
>
> As I said before, I splitted the whole-in-one version into three: 1) CHECK
> constraint patch (ie fdw-chk.patch), 2) table inheritance patch (ie
> fdw-inh.patch) and 3) path reparameterization patch (not posted). In
> addition to that, I slightly modified #1 and #2.
>
> IIUC, #3 would be useful not only for the inheritance cases but for union
> all cases. So, I plan to propose it independently in the next CF.
>
> I noticed that the latter disallows TRUNCATE on inheritance trees that
>>>> contain at least one child foreign table. But I think it would be
>>>> better to allow it, with the semantics that we quietly ignore the
>>>> child
>>>> foreign tables and apply the operation to the child plain tables,
>>>> which
>>>> is the same semantics as ALTER COLUMN SET STORAGE on such inheritance
>>>> trees. Comments welcome.
>>>>
>>>
>>> Done. And I've also a bit revised regression tests for both
>>> patches. Patches attached.
>>>
>>
> I rebased the patches to the latest head. Here are updated patches.
>
> Other changes:
>
> * fdw-chk-3.patch: the updated patch revises some ereport messages a
> little bit.
>
> * fdw-inh-3.patch: I noticed that there is a doc bug in the previous
> patch. The updated patch fixes that, adds a bit more docs, and revises
> regression tests in foreign_data.sql a bit further.
>
>
> Thanks,
>
> Best regards,
> Etsuro Fujita
>
>
> --
> Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-hackers
>
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-11-13 06:50 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-11-13 06:50 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
Hi Ashutosh,
Thanks for the review!
(2014/11/13 15:23), Ashutosh Bapat wrote:
> I tried to apply fdw-inh-3.patch on the latest head from master branch.
> It failed to apply using both patch and git apply.
>
> "patch" failed to apply because of rejections in
> contrib/file_fdw/output/file_fdw.source and
> doc/src/sgml/ref/create_foreign_table.sgml
As I said upthread, fdw-inh-3.patch has been created on top of [1] and
fdw-chk-3.patch. Did you apply these patche first?
[1] https://commitfest.postgresql.org/action/patch_view?id=1599
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-11-13 06:51 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Ashutosh Bapat @ 2014-11-13 06:51 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On Thu, Nov 13, 2014 at 12:20 PM, Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp
> wrote:
> Hi Ashutosh,
>
> Thanks for the review!
>
> (2014/11/13 15:23), Ashutosh Bapat wrote:
>
>> I tried to apply fdw-inh-3.patch on the latest head from master branch.
>> It failed to apply using both patch and git apply.
>>
>> "patch" failed to apply because of rejections in
>> contrib/file_fdw/output/file_fdw.source and
>> doc/src/sgml/ref/create_foreign_table.sgml
>>
>
> As I said upthread, fdw-inh-3.patch has been created on top of [1] and
> fdw-chk-3.patch. Did you apply these patche first?
>
> Oh, sorry, I didn't pay attention to that. I will apply both the patches
and review the inheritance patch. Thanks for pointing that out.
> [1] https://commitfest.postgresql.org/action/patch_view?id=1599
>
> Best regards,
> Etsuro Fujita
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-11-17 07:55 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-11-17 07:55 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
(2014/11/12 20:04), Ashutosh Bapat wrote:
> I reviewed fdw-chk-3 patch. Here are my comments
Thanks for the review!
> Tests
> -------
> 1. The tests added in file_fdw module look good. We should add tests for
> CREATE TABLE with CHECK CONSTRAINT also.
Agreed. I added the tests, and also changed the proposed tests a bit.
> 2. For postgres_fdw we need tests to check the behaviour in case the
> constraints mismatch between the remote table and its local foreign
> table declaration in case of INSERT, UPDATE and SELECT.
Done.
> 3. In the testcases for postgres_fdw it seems that you have forgotten to
> add statement after SET constraint_exclusion to 'partition'
I added the statement.
> 4. In test foreign_data there are changes to fix the diffs caused by
> these changes like below
> 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
> +NOTICE: constraint "no_const" of relation "ft1" does not exist, skipping
> ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c1_check;
> -ERROR: "ft1" is not a table
> +ERROR: constraint "ft1_c1_check" of relation "ft1" does not exist
> Earlier when constraints were not supported for FOREIGN TABLE, these
> tests made sure the same functionality. So, even though the
> corresponding constraints were not created on the table (in fact it
> didn't allow the creation as well). Now that the constraints are
> allowed, I think the tests for "no_const" (without IF EXISTS) and
> "ft1_c1_check" are duplicating the same testcase. May be we should
> review this set of statement in the light of new functionality.
Agreed. I removed the "DROP CONSTRAINT ft1_c1_check" test, and added a
new test for ALTER CONSTRAINT.
> Code and implementation
> ----------------------------------
> The usage of NO INHERIT and NOT VALID with CONSTRAINT on foreign table
> is blocked, but corresponding documentation entry doesn't mention so.
> Since foreign tables can not be inherited NO INHERIT option isn't
> applicable to foreign tables and the constraints on the foreign tables
> are declarative, hence NOT VALID option is also not applicable. So, I
> agree with what the code is doing, but that should be reflected in
> documentation with this explanation.
> Rest of the code modifies the condition checks for CHECK CONSTRAINTs on
> foreign tables, and it looks good to me.
Done.
Other changes:
* Modified one error message that I added in AddRelationNewConstraints,
to match the other message there.
* Revised other docs a little bit.
Attached is an updated version of the patch.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] fdw-chk-4.patch (31.0K, ../../5469A9FA.60804@lab.ntt.co.jp/2-fdw-chk-4.patch)
download | inline diff:
*** a/contrib/file_fdw/input/file_fdw.source
--- b/contrib/file_fdw/input/file_fdw.source
***************
*** 62,68 **** CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', null '
CREATE FOREIGN TABLE tbl () SERVER file_server; -- ERROR
CREATE FOREIGN TABLE agg_text (
! a int2,
b float4
) SERVER file_server
OPTIONS (format 'text', filename '@abs_srcdir@/data/agg.data', delimiter ' ', null '\N');
--- 62,68 ----
CREATE FOREIGN TABLE tbl () SERVER file_server; -- ERROR
CREATE FOREIGN TABLE agg_text (
! a int2 CHECK (a >= 0),
b float4
) SERVER file_server
OPTIONS (format 'text', filename '@abs_srcdir@/data/agg.data', delimiter ' ', null '\N');
***************
*** 72,82 **** CREATE FOREIGN TABLE agg_csv (
--- 72,84 ----
b float4
) SERVER file_server
OPTIONS (format 'csv', filename '@abs_srcdir@/data/agg.csv', header 'true', delimiter ';', quote '@', escape '"', null '');
+ ALTER FOREIGN TABLE agg_csv ADD CHECK (a >= 0);
CREATE FOREIGN TABLE agg_bad (
a int2,
b float4
) SERVER file_server
OPTIONS (format 'csv', filename '@abs_srcdir@/data/agg.bad', header 'true', delimiter ';', quote '@', escape '"', null '');
+ ALTER FOREIGN TABLE agg_bad ADD CHECK (a >= 0);
-- per-column options tests
CREATE FOREIGN TABLE text_csv (
***************
*** 134,139 **** DELETE FROM agg_csv WHERE a = 100;
--- 136,153 ----
-- but this should be ignored
SELECT * FROM agg_csv FOR UPDATE;
+ -- constraint exclusion tests
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
+ \t off
+ SELECT * FROM agg_csv WHERE a < 0;
+ SET constraint_exclusion = 'on';
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
+ \t off
+ SELECT * FROM agg_csv WHERE a < 0;
+ SET constraint_exclusion = 'partition';
+
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/file_fdw/output/file_fdw.source
--- b/contrib/file_fdw/output/file_fdw.source
***************
*** 78,84 **** ERROR: COPY null representation cannot use newline or carriage return
CREATE FOREIGN TABLE tbl () SERVER file_server; -- ERROR
ERROR: filename is required for file_fdw foreign tables
CREATE FOREIGN TABLE agg_text (
! a int2,
b float4
) SERVER file_server
OPTIONS (format 'text', filename '@abs_srcdir@/data/agg.data', delimiter ' ', null '\N');
--- 78,84 ----
CREATE FOREIGN TABLE tbl () SERVER file_server; -- ERROR
ERROR: filename is required for file_fdw foreign tables
CREATE FOREIGN TABLE agg_text (
! a int2 CHECK (a >= 0),
b float4
) SERVER file_server
OPTIONS (format 'text', filename '@abs_srcdir@/data/agg.data', delimiter ' ', null '\N');
***************
*** 88,98 **** CREATE FOREIGN TABLE agg_csv (
--- 88,100 ----
b float4
) SERVER file_server
OPTIONS (format 'csv', filename '@abs_srcdir@/data/agg.csv', header 'true', delimiter ';', quote '@', escape '"', null '');
+ ALTER FOREIGN TABLE agg_csv ADD CHECK (a >= 0);
CREATE FOREIGN TABLE agg_bad (
a int2,
b float4
) SERVER file_server
OPTIONS (format 'csv', filename '@abs_srcdir@/data/agg.bad', header 'true', delimiter ';', quote '@', escape '"', null '');
+ ALTER FOREIGN TABLE agg_bad ADD CHECK (a >= 0);
-- per-column options tests
CREATE FOREIGN TABLE text_csv (
word1 text OPTIONS (force_not_null 'true'),
***************
*** 219,224 **** SELECT * FROM agg_csv FOR UPDATE;
--- 221,254 ----
42 | 324.78
(3 rows)
+ -- constraint exclusion tests
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
+ Foreign Scan on public.agg_csv
+ Output: a, b
+ Filter: (agg_csv.a < 0)
+ Foreign File: @abs_srcdir@/data/agg.csv
+
+ \t off
+ SELECT * FROM agg_csv WHERE a < 0;
+ a | b
+ ---+---
+ (0 rows)
+
+ SET constraint_exclusion = 'on';
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
+ Result
+ Output: a, b
+ One-Time Filter: false
+
+ \t off
+ SELECT * FROM agg_csv WHERE a < 0;
+ a | b
+ ---+---
+ (0 rows)
+
+ SET constraint_exclusion = 'partition';
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 2488,2493 **** select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
--- 2488,2578 ----
(13 rows)
-- ===================================================================
+ -- test check constraints
+ -- ===================================================================
+ -- Consistent check constraints provide consistent results
+ ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0);
+ EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 < 0;
+ QUERY PLAN
+ -------------------------------------------------------------------
+ Aggregate
+ Output: count(*)
+ -> Foreign Scan on public.ft1
+ Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE ((c2 < 0))
+ (4 rows)
+
+ SELECT count(*) FROM ft1 WHERE c2 < 0;
+ count
+ -------
+ 0
+ (1 row)
+
+ SET constraint_exclusion = 'on';
+ EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 < 0;
+ QUERY PLAN
+ --------------------------------
+ Aggregate
+ Output: count(*)
+ -> Result
+ One-Time Filter: false
+ (4 rows)
+
+ SELECT count(*) FROM ft1 WHERE c2 < 0;
+ count
+ -------
+ 0
+ (1 row)
+
+ SET constraint_exclusion = 'partition';
+ -- Can throw errors on remote side during update
+ INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive
+ ERROR: new row for relation "T 1" violates check constraint "c2positive"
+ DETAIL: Failing row contains (1111, -2, null, null, null, null, ft1 , null).
+ CONTEXT: Remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
+ UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive
+ ERROR: new row for relation "T 1" violates check constraint "c2positive"
+ DETAIL: Failing row contains (1, -1, 00001_trig_update, 1970-01-02 08:00:00+00, 1970-01-02 00:00:00, 1, 1 , foo).
+ CONTEXT: Remote SQL command: UPDATE "S 1"."T 1" SET c2 = $2 WHERE ctid = $1
+ ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2positive;
+ -- But inconsistent check constraints provide inconsistent results
+ ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2negative CHECK (c2 < 0);
+ EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 >= 0;
+ QUERY PLAN
+ --------------------------------------------------------------------
+ Aggregate
+ Output: count(*)
+ -> Foreign Scan on public.ft1
+ Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE ((c2 >= 0))
+ (4 rows)
+
+ SELECT count(*) FROM ft1 WHERE c2 >= 0;
+ count
+ -------
+ 821
+ (1 row)
+
+ SET constraint_exclusion = 'on';
+ EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 >= 0;
+ QUERY PLAN
+ --------------------------------
+ Aggregate
+ Output: count(*)
+ -> Result
+ One-Time Filter: false
+ (4 rows)
+
+ SELECT count(*) FROM ft1 WHERE c2 >= 0;
+ count
+ -------
+ 0
+ (1 row)
+
+ SET constraint_exclusion = 'partition';
+ -- Can't throw errors on remote side during update
+ INSERT INTO ft1(c1, c2) VALUES(1111, 2);
+ UPDATE ft1 SET c2 = c2 + 1 WHERE c1 = 1;
+ ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2negative;
+ -- ===================================================================
-- test serial columns (ie, sequence-based defaults)
-- ===================================================================
create table loc1 (f1 serial, f2 text);
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 387,392 **** select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
--- 387,422 ----
select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
-- ===================================================================
+ -- test check constraints
+ -- ===================================================================
+
+ -- Consistent check constraints provide consistent results
+ ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0);
+ EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 < 0;
+ SELECT count(*) FROM ft1 WHERE c2 < 0;
+ SET constraint_exclusion = 'on';
+ EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 < 0;
+ SELECT count(*) FROM ft1 WHERE c2 < 0;
+ SET constraint_exclusion = 'partition';
+ -- Can throw errors on remote side during update
+ INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive
+ UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive
+ ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2positive;
+
+ -- But inconsistent check constraints provide inconsistent results
+ ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2negative CHECK (c2 < 0);
+ EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 >= 0;
+ SELECT count(*) FROM ft1 WHERE c2 >= 0;
+ SET constraint_exclusion = 'on';
+ EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 >= 0;
+ SELECT count(*) FROM ft1 WHERE c2 >= 0;
+ SET constraint_exclusion = 'partition';
+ -- Can't throw errors on remote side during update
+ INSERT INTO ft1(c1, c2) VALUES(1111, 2);
+ UPDATE ft1 SET c2 = c2 + 1 WHERE c1 = 1;
+ ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2negative;
+
+ -- ===================================================================
-- test serial columns (ie, sequence-based defaults)
-- ===================================================================
create table loc1 (f1 serial, f2 text);
*** 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 ] <replaceable class="PARAMETER">name</replaceab
--- 42,49 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ ADD <replaceable class="PARAMETER">table_constraint</replaceable>
+ DROP CONSTRAINT [ IF EXISTS ] <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
DISABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
ENABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
ENABLE REPLICA TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
***************
*** 152,157 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 154,188 ----
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds a new constraint to a foreign table, using the same syntax as
+ <xref linkend="SQL-CREATEFOREIGNTABLE">.
+ 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.
+ </para>
+
+ <para>
+ Note that constraints on foreign tables cannot be marked
+ <literal>NOT VALID</> since those constraints are simply declarative.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DROP CONSTRAINT [ IF EXISTS ]</literal></term>
+ <listitem>
+ <para>
+ This form drops the specified constraint on a foreign table.
+ If <literal>IF EXISTS</literal> is specified and the constraint
+ does not exist, no error is thrown. In this case a notice is issued instead.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>DISABLE</literal>/<literal>ENABLE [ REPLICA | ALWAYS ] TRIGGER</literal></term>
<listitem>
***************
*** 284,289 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 315,338 ----
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="PARAMETER">table_constraint</replaceable></term>
+ <listitem>
+ <para>
+ New table constraint for the table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">constraint_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing constraint to drop.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>CASCADE</literal></term>
<listitem>
***************
*** 365,374 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint is
! added, or a column type is changed with <literal>SET DATA TYPE</>. It is
! the user's responsibility to ensure that the table definition matches the
! remote side.
</para>
<para>
--- 414,423 ----
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> or <literal>CHECK</>
! constraint is added, or a column type is changed with <literal>SET DATA TYPE</>.
! It is the user's responsibility to ensure that the table definition matches
! the remote side.
</para>
<para>
*** a/doc/src/sgml/ref/create_foreign_table.sgml
--- b/doc/src/sgml/ref/create_foreign_table.sgml
***************
*** 19,25 ****
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
SERVER <replaceable class="parameter">server_name</replaceable>
--- 19,26 ----
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
! | <replaceable>table_constraint</replaceable> }
[, ... ]
] )
SERVER <replaceable class="parameter">server_name</replaceable>
***************
*** 30,36 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 31,43 ----
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ NOT NULL |
NULL |
+ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) |
DEFAULT <replaceable>default_expr</replaceable> }
+
+ <phrase>and <replaceable class="PARAMETER">table_constraint</replaceable> is:</phrase>
+
+ [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
+ { CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) }
</synopsis>
</refsynopsisdiv>
***************
*** 138,143 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 145,176 ----
</varlistentry>
<varlistentry>
+ <term><literal>CHECK ( <replaceable class="PARAMETER">expression</replaceable> )</literal></term>
+ <listitem>
+ <para>
+ The <literal>CHECK</> clause specifies an expression producing a
+ Boolean result which each row must satisfy.
+ 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.
+ </para>
+
+ <para>
+ Currently, <literal>CHECK</literal> expressions cannot contain
+ subqueries nor refer to variables other than columns of the
+ current row. The system column <literal>tableoid</literal>
+ may be referenced, but not any other system column.
+ </para>
+
+ <para>
+ Note that check constraints on foreign tables cannot be marked
+ <literal>NO INHERIT</> since those tables are not allowd to be
+ inherited.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>DEFAULT
<replaceable>default_expr</replaceable></literal></term>
<listitem>
***************
*** 187,192 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 220,235 ----
</refsect1>
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ Constraints on foreign tables are not enforced on insert or update.
+ Those definitions simply declare the constraints hold for all rows
+ in the foreign tables. It is the user's responsibility to ensure
+ that those definitions match the remote side.
+ </para>
+ </refsect1>
<refsect1 id="SQL-CREATEFOREIGNTABLE-examples">
<title>Examples</title>
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 2222,2227 **** AddRelationNewConstraints(Relation rel,
--- 2222,2241 ----
if (cdef->contype != CONSTR_CHECK)
continue;
+ /* Don't allow NO INHERIT for foreign tables */
+ if (cdef->is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("CHECK constraints on foreign tables cannot be marked NO INHERIT")));
+
+ /* Don't allow NOT VALID for foreign tables */
+ if (cdef->skip_validation &&
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("CHECK constraints on foreign tables cannot be marked NOT VALID")));
+
if (cdef->raw_expr != NULL)
{
Assert(cdef->cooked_expr == NULL);
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 477,486 **** 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,
--- 477,482 ----
***************
*** 3142,3148 **** 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)
--- 3138,3144 ----
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)
***************
*** 3156,3162 **** 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)
--- 3152,3158 ----
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)
*** 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("unique or primary key 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("unique, primary key, exclusion, or foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
*** a/src/test/regress/expected/foreign_data.out
--- b/src/test/regress/expected/foreign_data.out
***************
*** 670,676 **** LINE 1: CREATE FOREIGN TABLE ft1 () SERVER s0 WITH OIDS;
^
CREATE FOREIGN TABLE ft1 (
c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
! c2 text OPTIONS (param2 'val2', param3 'val3'),
c3 date
) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
COMMENT ON FOREIGN TABLE ft1 IS 'ft1';
--- 670,682 ----
^
CREATE FOREIGN TABLE ft1 (
c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
! c2 text OPTIONS (param2 'val2', param3 'val3') CHECK (c2 <> '') NO INHERIT,
! c3 date
! ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
! ERROR: CHECK constraints on foreign tables cannot be marked NO INHERIT
! CREATE FOREIGN TABLE ft1 (
! c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
! c2 text OPTIONS (param2 'val2', param3 'val3') CHECK (c2 <> ''),
c3 date
) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
COMMENT ON FOREIGN TABLE ft1 IS 'ft1';
***************
*** 682,687 **** COMMENT ON COLUMN ft1.c1 IS 'ft1.c1';
--- 688,695 ----
c1 | integer | not null | ("param 1" 'val1') | plain | | ft1.c1
c2 | text | | (param2 'val2', param3 'val3') | extended | |
c3 | date | | | plain | |
+ Check constraints:
+ "ft1_c2_check" CHECK (c2 <> ''::text)
Server: s0
FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
***************
*** 740,745 **** ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET STATISTICS -1;
--- 748,755 ----
c8 | text | | (p2 'V2') | extended | |
c9 | integer | | | plain | |
c10 | integer | | (p1 'v1') | plain | |
+ Check constraints:
+ "ft1_c2_check" CHECK (c2 <> ''::text)
Server: s0
FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
***************
*** 748,763 **** 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;
--- 758,775 ----
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) NO INHERIT; -- ERROR
! ERROR: CHECK constraints on foreign tables cannot be marked NO INHERIT
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0) NOT VALID; -- ERROR
! ERROR: CHECK constraints on foreign tables cannot be marked NOT VALID
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0);
! ALTER FOREIGN TABLE ft1 ALTER CONSTRAINT ft1_c9_check DEFERRABLE; -- ERROR
ERROR: "ft1" is not a table
+ ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c9_check;
+ 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 SET WITH OIDS; -- ERROR
ERROR: "ft1" is not a table
ALTER FOREIGN TABLE ft1 OWNER TO regress_test_role;
***************
*** 785,790 **** ALTER FOREIGN TABLE foreign_schema.ft1 RENAME TO foreign_table_1;
--- 797,804 ----
c7 | integer | | (p1 'v1', p2 'v2')
c8 | text | | (p2 'V2')
c10 | integer | | (p1 'v1')
+ Check constraints:
+ "ft1_c2_check" CHECK (c2 <> ''::text)
Server: s0
FDW Options: (quote '~', "be quoted" 'value', escape '@')
*** a/src/test/regress/sql/foreign_data.sql
--- b/src/test/regress/sql/foreign_data.sql
***************
*** 270,276 **** CREATE FOREIGN TABLE ft1 () SERVER no_server; -- ERROR
CREATE FOREIGN TABLE ft1 () SERVER s0 WITH OIDS; -- ERROR
CREATE FOREIGN TABLE ft1 (
c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
! c2 text OPTIONS (param2 'val2', param3 'val3'),
c3 date
) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
COMMENT ON FOREIGN TABLE ft1 IS 'ft1';
--- 270,281 ----
CREATE FOREIGN TABLE ft1 () SERVER s0 WITH OIDS; -- ERROR
CREATE FOREIGN TABLE ft1 (
c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
! c2 text OPTIONS (param2 'val2', param3 'val3') CHECK (c2 <> '') NO INHERIT,
! c3 date
! ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
! CREATE FOREIGN TABLE ft1 (
! c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
! c2 text OPTIONS (param2 'val2', param3 'val3') CHECK (c2 <> ''),
c3 date
) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
COMMENT ON FOREIGN TABLE ft1 IS 'ft1';
***************
*** 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 '@');
--- 319,331 ----
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) NO INHERIT; -- ERROR
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0) NOT VALID; -- ERROR
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0);
! ALTER FOREIGN TABLE ft1 ALTER CONSTRAINT ft1_c9_check DEFERRABLE; -- ERROR
! ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c9_check;
ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const; -- ERROR
ALTER FOREIGN TABLE ft1 DROP CONSTRAINT IF EXISTS no_const;
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 '@');
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-11-17 08:55 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 179+ messages in thread
From: Ashutosh Bapat @ 2014-11-17 08:55 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
Hi Fujita-san,
Here are my review comments for patch fdw-inh-3.patch.
Sanity
--------
1. The patch applies cleanly
2. It compiles cleanly.
3. The server regression tests and tests in contrib modules pass.
Tests
-------
1. It seems like you have copied from testcase inherit.sql to postgres_fdw
testcase. That's a good thing, but it makes the test quite long. May be we
should have two tests in postgres_fdw contrib module, one for simple cases,
and other for inheritance. What do you say?
Documentation
--------------------
1. The change in ddl.sgml
- We will refer to the child tables as partitions, though they
- are in every way normal <productname>PostgreSQL</> tables.
+ We will refer to the child tables as partitions, though we assume
+ that they are normal <productname>PostgreSQL</> tables.
adds phrase "we assume that", which confuses the intention behind the
sentence. The original text is intended to highlight the equivalence
between "partition" and "normal table", where as the addition esp. the word
"assume" weakens that equivalence. Instead now we have to highlight the
equivalence between "partition" and "normal or foreign table". The wording
similar to "though they are either normal or foreign tables" should be used
there.
2. The wording "some kind of optimization" gives vague picture. May be it
can be worded as "Since the constraints are assumed to be true, they are
used in constraint-based query optimization like constraint exclusion for
partitioned tables.".
+ Those constraints are used in some kind of query optimization such
+ as constraint exclusion for partitioned tables (see
+ <xref linkend="ddl-partitioning">).
Code
-------
1. In the following change
+/*
* acquire_inherited_sample_rows -- acquire sample rows from inheritance
tree
*
* This has the same API as acquire_sample_rows, except that rows are
* collected from all inheritance children as well as the specified table.
- * We fail and return zero if there are no inheritance children.
+ * We fail and return zero if there are no inheritance children or there
are
+ * inheritance children that foreign tables.
The addition should be "there are inheritance children that *are all *foreign
tables. Note the addition "are all".
2. The function has_foreign() be better named has_foreign_child()? This
function loops through all the tableoids passed even after it has found a
foreign table. Why can't we return true immediately after finding the first
foreign table, unless the side effects of heap_open() on all the table are
required. But I don't see that to be the case, since these tables are
locked already through a previous call to heap_open(). In the same function
instead of argument name parentrelId may be we should use name parent_oid,
so that we use same notation for parent and child table OIDs.
3. Regarding enum VacuumMode - it's being used only in case of
acquire_inherited_sample_rows() and that too, to check only a single value
of the three defined there. May be we should just infer that value inside
acquire_inherited_sample_rows() or pass a boolean true or false from
do_analyse_rel() based on the VacuumStmt. I do not see need for a separate
three value enum of which only one value is used and also to pass it down
from vacuum() by changing signatures of the minion functions.
4. In postgresGetForeignPlan(), the code added by this patch is required to
handle the case when the row mark is placed on a parent table and hence is
required to be applied on the child table. We need a comment explaining
this. Otherwise, the three step process to get the row mark information
isn't clear for a reader.
5. In expand_inherited_rtentry() why do you need a separate variable
hasForeign? Instead of using that variable, you can actually set/reset
rte->hasForeign since existence of even a single foreign child would mark
that member as true. - After typing this, I got the answer when I looked at
the function code. Every child's RTE is initially a copy of parent's RTE
and hence hasForeign status would be inherited by every child after the
first foreign child. I think, this reasoning should be added as comment
before assignment to rte->hasForeign at the end of the function.
6. The tests in foreign_data.sql are pretty extensive. Thanks for that. I
think, we should also check the effect of each of the following command
using \d on appropriate tables.
+CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ALTER FOREIGN TABLE ft2 NO INHERIT pt1;
+DROP FOREIGN TABLE ft2;
+CREATE FOREIGN TABLE ft2 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ALTER FOREIGN TABLE ft2 INHERIT pt1;
Rest of the changes look good.
On Thu, Nov 13, 2014 at 12:21 PM, Ashutosh Bapat <
ashutosh.bapat@enterprisedb.com> wrote:
>
>
> On Thu, Nov 13, 2014 at 12:20 PM, Etsuro Fujita <
> fujita.etsuro@lab.ntt.co.jp> wrote:
>
>> Hi Ashutosh,
>>
>> Thanks for the review!
>>
>> (2014/11/13 15:23), Ashutosh Bapat wrote:
>>
>>> I tried to apply fdw-inh-3.patch on the latest head from master branch.
>>> It failed to apply using both patch and git apply.
>>>
>>> "patch" failed to apply because of rejections in
>>> contrib/file_fdw/output/file_fdw.source and
>>> doc/src/sgml/ref/create_foreign_table.sgml
>>>
>>
>> As I said upthread, fdw-inh-3.patch has been created on top of [1] and
>> fdw-chk-3.patch. Did you apply these patche first?
>>
>> Oh, sorry, I didn't pay attention to that. I will apply both the patches
> and review the inheritance patch. Thanks for pointing that out.
>
>
>> [1] https://commitfest.postgresql.org/action/patch_view?id=1599
>>
>> Best regards,
>> Etsuro Fujita
>>
>
>
>
> --
> Best Wishes,
> Ashutosh Bapat
> EnterpriseDB Corporation
> The Postgres Database Company
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-11-18 09:09 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Ashutosh Bapat @ 2014-11-18 09:09 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On Mon, Nov 17, 2014 at 1:25 PM, Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
wrote:
> (2014/11/12 20:04), Ashutosh Bapat wrote:
>
>> I reviewed fdw-chk-3 patch. Here are my comments
>>
>
> Thanks for the review!
>
> Tests
>> -------
>> 1. The tests added in file_fdw module look good. We should add tests for
>> CREATE TABLE with CHECK CONSTRAINT also.
>>
>
> Agreed. I added the tests, and also changed the proposed tests a bit.
>
> 2. For postgres_fdw we need tests to check the behaviour in case the
>> constraints mismatch between the remote table and its local foreign
>> table declaration in case of INSERT, UPDATE and SELECT.
>>
>
> Done.
>
> 3. In the testcases for postgres_fdw it seems that you have forgotten to
>> add statement after SET constraint_exclusion to 'partition'
>>
>
> I added the statement.
>
> 4. In test foreign_data there are changes to fix the diffs caused by
>> these changes like below
>> 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
>> +NOTICE: constraint "no_const" of relation "ft1" does not exist, skipping
>> ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c1_check;
>> -ERROR: "ft1" is not a table
>> +ERROR: constraint "ft1_c1_check" of relation "ft1" does not exist
>>
>
> Earlier when constraints were not supported for FOREIGN TABLE, these
>> tests made sure the same functionality. So, even though the
>> corresponding constraints were not created on the table (in fact it
>> didn't allow the creation as well). Now that the constraints are
>> allowed, I think the tests for "no_const" (without IF EXISTS) and
>> "ft1_c1_check" are duplicating the same testcase. May be we should
>> review this set of statement in the light of new functionality.
>>
>
> Agreed. I removed the "DROP CONSTRAINT ft1_c1_check" test, and added a
> new test for ALTER CONSTRAINT.
>
> Code and implementation
>> ----------------------------------
>> The usage of NO INHERIT and NOT VALID with CONSTRAINT on foreign table
>> is blocked, but corresponding documentation entry doesn't mention so.
>> Since foreign tables can not be inherited NO INHERIT option isn't
>> applicable to foreign tables and the constraints on the foreign tables
>> are declarative, hence NOT VALID option is also not applicable. So, I
>> agree with what the code is doing, but that should be reflected in
>> documentation with this explanation.
>> Rest of the code modifies the condition checks for CHECK CONSTRAINTs on
>> foreign tables, and it looks good to me.
>>
>
> Done.
>
> Other changes:
> * Modified one error message that I added in AddRelationNewConstraints, to
> match the other message there.
> * Revised other docs a little bit.
>
> Attached is an updated version of the patch.
>
>
I looked at the patch. It looks good now. Once we have the inh patch ready,
we can mark this item as ready for commiter.
>
> Thanks,
>
> Best regards,
> Etsuro Fujita
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-11-19 05:44 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 0 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-11-19 05:44 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
(2014/11/18 18:09), Ashutosh Bapat wrote:
> I looked at the patch. It looks good now. Once we have the inh patch
> ready, we can mark this item as ready for commiter.
Thanks for the review!
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-11-27 10:22 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-11-27 10:22 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
(2014/11/17 17:55), Ashutosh Bapat wrote:
> Here are my review comments for patch fdw-inh-3.patch.
Thanks for the review!
> Tests
> -------
> 1. It seems like you have copied from testcase inherit.sql to
> postgres_fdw testcase. That's a good thing, but it makes the test quite
> long. May be we should have two tests in postgres_fdw contrib module,
> one for simple cases, and other for inheritance. What do you say?
IMO, the test is not so time-consuming, so it doesn't seem worth
splitting it into two.
> Documentation
> --------------------
> 1. The change in ddl.sgml
> - We will refer to the child tables as partitions, though they
> - are in every way normal <productname>PostgreSQL</> tables.
> + We will refer to the child tables as partitions, though we assume
> + that they are normal <productname>PostgreSQL</> tables.
>
> adds phrase "we assume that", which confuses the intention behind the
> sentence. The original text is intended to highlight the equivalence
> between "partition" and "normal table", where as the addition esp. the
> word "assume" weakens that equivalence. Instead now we have to highlight
> the equivalence between "partition" and "normal or foreign table". The
> wording similar to "though they are either normal or foreign tables"
> should be used there.
You are right, but I feel that there is something out of place in saying
that there (5.10.2. Implementing Partitioning) because the procedure
there has been written based on normal tables only. Put another way, if
we say that, I think we'd need to add more docs, describing the syntax
and/or the corresponding examples for foreign-table cases. But I'd like
to leave that for another patch. So, how about the wording "we assume
*here* that", instead of "we assume that", as I added the following
notice in the previous section (5.10.1. Overview)?
@@ -2650,7 +2669,10 @@ VALUES ('Albany', NULL, NULL, 'NY');
table of a single parent table. The parent table itself is normally
empty; it exists just to represent the entire data set. You should be
familiar with inheritance (see <xref linkend="ddl-inherit">) before
- attempting to set up partitioning.
+ attempting to set up partitioning. (The setup and management of
+ partitioned tables illustrated in this section assume that each
+ partition is a normal table. However, you can do that in a similar way
+ for cases where some or all partitions are foreign tables.)
> 2. The wording "some kind of optimization" gives vague picture. May be
> it can be worded as "Since the constraints are assumed to be true, they
> are used in constraint-based query optimization like constraint
> exclusion for partitioned tables.".
> + Those constraints are used in some kind of query optimization such
> + as constraint exclusion for partitioned tables (see
> + <xref linkend="ddl-partitioning">).
Will follow your revision.
> Code
> -------
> 1. In the following change
> +/*
> * acquire_inherited_sample_rows -- acquire sample rows from
> inheritance tree
> *
> * This has the same API as acquire_sample_rows, except that rows are
> * collected from all inheritance children as well as the specified table.
> - * We fail and return zero if there are no inheritance children.
> + * We fail and return zero if there are no inheritance children or
> there are
> + * inheritance children that foreign tables.
>
> The addition should be "there are inheritance children that *are all
> *foreign tables. Note the addition "are all".
Sorry, I incorrectly wrote the comment. What I tried to write is "We
fail and return zero if there are no inheritance children or if we are
not in VAC_MODE_SINGLE case and inheritance tree contains at least one
foreign table.".
> 2. The function has_foreign() be better named has_foreign_child()? This
How about "has_foreign_table"?
> function loops through all the tableoids passed even after it has found
> a foreign table. Why can't we return true immediately after finding the
> first foreign table, unless the side effects of heap_open() on all the
> table are required. But I don't see that to be the case, since these
> tables are locked already through a previous call to heap_open(). In the
Good catch! Will fix.
> same function instead of argument name parentrelId may be we should use
> name parent_oid, so that we use same notation for parent and child table
> OIDs.
Will fix.
> 3. Regarding enum VacuumMode - it's being used only in case of
> acquire_inherited_sample_rows() and that too, to check only a single
> value of the three defined there. May be we should just infer that value
> inside acquire_inherited_sample_rows() or pass a boolean true or false
> from do_analyse_rel() based on the VacuumStmt. I do not see need for a
> separate three value enum of which only one value is used and also to
> pass it down from vacuum() by changing signatures of the minion functions.
I introduced that for possible future use. See the discussion in [1].
> 4. In postgresGetForeignPlan(), the code added by this patch is required
> to handle the case when the row mark is placed on a parent table and
> hence is required to be applied on the child table. We need a comment
> explaining this. Otherwise, the three step process to get the row mark
> information isn't clear for a reader.
Will add the comment.
> 5. In expand_inherited_rtentry() why do you need a separate variable
> hasForeign? Instead of using that variable, you can actually set/reset
> rte->hasForeign since existence of even a single foreign child would
> mark that member as true. - After typing this, I got the answer when I
> looked at the function code. Every child's RTE is initially a copy of
> parent's RTE and hence hasForeign status would be inherited by every
> child after the first foreign child. I think, this reasoning should be
> added as comment before assignment to rte->hasForeign at the end of the
> function.
As you mentioned, I think we could set rte->hasForeign directly during
the scan for the inheritance set, without the separate variable,
hasForeign. But ISTM that it'd improve code readability to set
rte->hasForeign using the separate variable at the end of the function
because rte->hasForeign has its meaning only when rte->inh is true and
because we know whether rte->inh is true, at the end of the function.
> 6. The tests in foreign_data.sql are pretty extensive. Thanks for that.
> I think, we should also check the effect of each of the following
> command using \d on appropriate tables.
> +CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
> + SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
> +ALTER FOREIGN TABLE ft2 NO INHERIT pt1;
> +DROP FOREIGN TABLE ft2;
> +CREATE FOREIGN TABLE ft2 (
> + c1 integer NOT NULL,
> + c2 text,
> + c3 date
> +) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
> +ALTER FOREIGN TABLE ft2 INHERIT pt1;
Will fix.
Apart from the above, I noticed that the patch doesn't consider to call
ExplainForeignModify during EXPLAIN for an inherited UPDATE/DELETE, as
shown below (note that there are no UPDATE remote queries displayed):
postgres=# explain verbose update parent set a = a * 2 where a = 5;
QUERY PLAN
-------------------------------------------------------------------------------------
Update on public.parent (cost=0.00..280.77 rows=25 width=10)
-> Seq Scan on public.parent (cost=0.00..0.00 rows=1 width=10)
Output: (parent.a * 2), parent.ctid
Filter: (parent.a = 5)
-> Foreign Scan on public.ft1 (cost=100.00..140.38 rows=12 width=10)
Output: (ft1.a * 2), ft1.ctid
Remote SQL: SELECT a, ctid FROM public.mytable_1 WHERE ((a =
5)) FOR UPDATE
-> Foreign Scan on public.ft2 (cost=100.00..140.38 rows=12 width=10)
Output: (ft2.a * 2), ft2.ctid
Remote SQL: SELECT a, ctid FROM public.mytable_2 WHERE ((a =
5)) FOR UPDATE
(10 rows)
So, I'd like to modify explain.c to show those queries like this:
postgres=# explain verbose update parent set a = a * 2 where a = 5;
QUERY PLAN
-------------------------------------------------------------------------------------
Update on public.parent (cost=0.00..280.77 rows=25 width=10)
-> Seq Scan on public.parent (cost=0.00..0.00 rows=1 width=10)
Output: (parent.a * 2), parent.ctid
Filter: (parent.a = 5)
Remote SQL: UPDATE public.mytable_1 SET a = $2 WHERE ctid = $1
-> Foreign Scan on public.ft1 (cost=100.00..140.38 rows=12 width=10)
Output: (ft1.a * 2), ft1.ctid
Remote SQL: SELECT a, ctid FROM public.mytable_1 WHERE ((a =
5)) FOR UPDATE
Remote SQL: UPDATE public.mytable_2 SET a = $2 WHERE ctid = $1
-> Foreign Scan on public.ft2 (cost=100.00..140.38 rows=12 width=10)
Output: (ft2.a * 2), ft2.ctid
Remote SQL: SELECT a, ctid FROM public.mytable_2 WHERE ((a =
5)) FOR UPDATE
(12 rows)
What do you say?
Sorry for the delay.
[1] http://www.postgresql.org/message-id/1316566782-sup-2678@alvh.no-ip.org
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-11-28 09:14 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 2 replies; 179+ messages in thread
From: Ashutosh Bapat @ 2014-11-28 09:14 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On Thu, Nov 27, 2014 at 3:52 PM, Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
wrote:
> (2014/11/17 17:55), Ashutosh Bapat wrote:
>
>> Here are my review comments for patch fdw-inh-3.patch.
>>
>
> Thanks for the review!
>
> Tests
>> -------
>> 1. It seems like you have copied from testcase inherit.sql to
>> postgres_fdw testcase. That's a good thing, but it makes the test quite
>> long. May be we should have two tests in postgres_fdw contrib module,
>> one for simple cases, and other for inheritance. What do you say?
>>
>
> IMO, the test is not so time-consuming, so it doesn't seem worth splitting
> it into two.
>
I am not worried about the timing but I am worried about the length of the
file and hence ease of debugging in case we find any issues there. We will
leave that for the commiter to decide.
>
> Documentation
>> --------------------
>> 1. The change in ddl.sgml
>> - We will refer to the child tables as partitions, though they
>> - are in every way normal <productname>PostgreSQL</> tables.
>> + We will refer to the child tables as partitions, though we assume
>> + that they are normal <productname>PostgreSQL</> tables.
>>
>> adds phrase "we assume that", which confuses the intention behind the
>> sentence. The original text is intended to highlight the equivalence
>> between "partition" and "normal table", where as the addition esp. the
>> word "assume" weakens that equivalence. Instead now we have to highlight
>> the equivalence between "partition" and "normal or foreign table". The
>> wording similar to "though they are either normal or foreign tables"
>> should be used there.
>>
>
> You are right, but I feel that there is something out of place in saying
> that there (5.10.2. Implementing Partitioning) because the procedure there
> has been written based on normal tables only. Put another way, if we say
> that, I think we'd need to add more docs, describing the syntax and/or the
> corresponding examples for foreign-table cases. But I'd like to leave that
> for another patch. So, how about the wording "we assume *here* that",
> instead of "we assume that", as I added the following notice in the
> previous section (5.10.1. Overview)?
>
> @@ -2650,7 +2669,10 @@ VALUES ('Albany', NULL, NULL, 'NY');
> table of a single parent table. The parent table itself is normally
> empty; it exists just to represent the entire data set. You should be
> familiar with inheritance (see <xref linkend="ddl-inherit">) before
> - attempting to set up partitioning.
> + attempting to set up partitioning. (The setup and management of
> + partitioned tables illustrated in this section assume that each
> + partition is a normal table. However, you can do that in a similar
> way
> + for cases where some or all partitions are foreign tables.)
>
>
This looks ok, though, I would like to see final version of the document.
But I think, we will leave that for committer to handle.
> 2. The wording "some kind of optimization" gives vague picture. May be
>> it can be worded as "Since the constraints are assumed to be true, they
>> are used in constraint-based query optimization like constraint
>> exclusion for partitioned tables.".
>> + Those constraints are used in some kind of query optimization such
>> + as constraint exclusion for partitioned tables (see
>> + <xref linkend="ddl-partitioning">).
>>
>
> Will follow your revision.
>
Thanks.
>
> Code
>> -------
>> 1. In the following change
>> +/*
>> * acquire_inherited_sample_rows -- acquire sample rows from
>> inheritance tree
>> *
>> * This has the same API as acquire_sample_rows, except that rows are
>> * collected from all inheritance children as well as the specified
>> table.
>> - * We fail and return zero if there are no inheritance children.
>> + * We fail and return zero if there are no inheritance children or
>> there are
>> + * inheritance children that foreign tables.
>>
>> The addition should be "there are inheritance children that *are all
>> *foreign tables. Note the addition "are all".
>>
>
> Sorry, I incorrectly wrote the comment. What I tried to write is "We fail
> and return zero if there are no inheritance children or if we are not in
> VAC_MODE_SINGLE case and inheritance tree contains at least one foreign
> table.".
>
>
You might want to use "English" description of VAC_MODE_SINGLE instead of
that macro in the comment, so that reader doesn't have to look up
VAC_MODE_SINGLE. But I think, we will leave this for the committer.
> 2. The function has_foreign() be better named has_foreign_child()? This
>>
>
> How about "has_foreign_table"?
>
has_foreign_child() would be better, since these are "children" in the
inheritance hierarchy and not mere "table"s.
>
> function loops through all the tableoids passed even after it has found
>> a foreign table. Why can't we return true immediately after finding the
>> first foreign table, unless the side effects of heap_open() on all the
>> table are required. But I don't see that to be the case, since these
>> tables are locked already through a previous call to heap_open(). In the
>>
>
> Good catch! Will fix.
>
> same function instead of argument name parentrelId may be we should use
>> name parent_oid, so that we use same notation for parent and child table
>> OIDs.
>>
>
> Will fix.
>
Thanks.
>
> 3. Regarding enum VacuumMode - it's being used only in case of
>> acquire_inherited_sample_rows() and that too, to check only a single
>> value of the three defined there. May be we should just infer that value
>> inside acquire_inherited_sample_rows() or pass a boolean true or false
>> from do_analyse_rel() based on the VacuumStmt. I do not see need for a
>> separate three value enum of which only one value is used and also to
>> pass it down from vacuum() by changing signatures of the minion functions.
>>
>
> I introduced that for possible future use. See the discussion in [1].
>
Will leave it for the commiter to decide.
>
> 4. In postgresGetForeignPlan(), the code added by this patch is required
>> to handle the case when the row mark is placed on a parent table and
>> hence is required to be applied on the child table. We need a comment
>> explaining this. Otherwise, the three step process to get the row mark
>> information isn't clear for a reader.
>>
>
> Will add the comment.
>
> 5. In expand_inherited_rtentry() why do you need a separate variable
>> hasForeign? Instead of using that variable, you can actually set/reset
>> rte->hasForeign since existence of even a single foreign child would
>> mark that member as true. - After typing this, I got the answer when I
>> looked at the function code. Every child's RTE is initially a copy of
>> parent's RTE and hence hasForeign status would be inherited by every
>> child after the first foreign child. I think, this reasoning should be
>> added as comment before assignment to rte->hasForeign at the end of the
>> function.
>>
>
> As you mentioned, I think we could set rte->hasForeign directly during the
> scan for the inheritance set, without the separate variable, hasForeign.
> But ISTM that it'd improve code readability to set rte->hasForeign using
> the separate variable at the end of the function because rte->hasForeign
> has its meaning only when rte->inh is true and because we know whether
> rte->inh is true, at the end of the function.
>
Fine. Please use "hasForeignChild" instead of just "hasForeign" without a
clue as to what is "foreign" here.
>
> 6. The tests in foreign_data.sql are pretty extensive. Thanks for that.
>> I think, we should also check the effect of each of the following
>> command using \d on appropriate tables.
>> +CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
>> + SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
>> +ALTER FOREIGN TABLE ft2 NO INHERIT pt1;
>> +DROP FOREIGN TABLE ft2;
>> +CREATE FOREIGN TABLE ft2 (
>> + c1 integer NOT NULL,
>> + c2 text,
>> + c3 date
>> +) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
>> +ALTER FOREIGN TABLE ft2 INHERIT pt1;
>>
>
> Will fix.
>
> Apart from the above, I noticed that the patch doesn't consider to call
> ExplainForeignModify during EXPLAIN for an inherited UPDATE/DELETE, as
> shown below (note that there are no UPDATE remote queries displayed):
>
> postgres=# explain verbose update parent set a = a * 2 where a = 5;
> QUERY PLAN
> ------------------------------------------------------------
> -------------------------
> Update on public.parent (cost=0.00..280.77 rows=25 width=10)
> -> Seq Scan on public.parent (cost=0.00..0.00 rows=1 width=10)
> Output: (parent.a * 2), parent.ctid
> Filter: (parent.a = 5)
> -> Foreign Scan on public.ft1 (cost=100.00..140.38 rows=12 width=10)
> Output: (ft1.a * 2), ft1.ctid
> Remote SQL: SELECT a, ctid FROM public.mytable_1 WHERE ((a = 5))
> FOR UPDATE
> -> Foreign Scan on public.ft2 (cost=100.00..140.38 rows=12 width=10)
> Output: (ft2.a * 2), ft2.ctid
> Remote SQL: SELECT a, ctid FROM public.mytable_2 WHERE ((a = 5))
> FOR UPDATE
> (10 rows)
>
> So, I'd like to modify explain.c to show those queries like this:
>
> postgres=# explain verbose update parent set a = a * 2 where a = 5;
> QUERY PLAN
> ------------------------------------------------------------
> -------------------------
> Update on public.parent (cost=0.00..280.77 rows=25 width=10)
> -> Seq Scan on public.parent (cost=0.00..0.00 rows=1 width=10)
> Output: (parent.a * 2), parent.ctid
> Filter: (parent.a = 5)
> Remote SQL: UPDATE public.mytable_1 SET a = $2 WHERE ctid = $1
> -> Foreign Scan on public.ft1 (cost=100.00..140.38 rows=12 width=10)
> Output: (ft1.a * 2), ft1.ctid
> Remote SQL: SELECT a, ctid FROM public.mytable_1 WHERE ((a = 5))
> FOR UPDATE
> Remote SQL: UPDATE public.mytable_2 SET a = $2 WHERE ctid = $1
> -> Foreign Scan on public.ft2 (cost=100.00..140.38 rows=12 width=10)
> Output: (ft2.a * 2), ft2.ctid
> Remote SQL: SELECT a, ctid FROM public.mytable_2 WHERE ((a = 5))
> FOR UPDATE
> (12 rows)
>
> What do you say?
>
Two "remote SQL" under a single node would be confusing. Also the node is
labelled as "Foreign Scan". It would be confusing to show an "UPDATE"
command under this "scan" node.
BTW, I was experimenting with DMLs being executed on multiple FDW server
under same transaction and found that the transactions may not be atomic
(and may be inconsistent), if one or more of the server fails to commit
while rest of them commit the transaction. The reason for this is, we do
not "rollback" the already "committed" changes to the foreign server, if
one or more of them fail to "commit" a transaction. With foreign tables
under inheritance hierarchy a single DML can span across multiple servers
and the result may not be atomic (and may be inconsistent). So, either we
have to disable DMLs on an inheritance hierarchy which spans multiple
servers. OR make sure that such transactions follow 2PC norms.
>
> Sorry for the delay.
>
> [1] http://www.postgresql.org/message-id/1316566782-sup-
> 2678@alvh.no-ip.org
>
> Best regards,
> Etsuro Fujita
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-12-02 02:59 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
1 sibling, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-12-02 02:59 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
(2014/11/28 18:14), Ashutosh Bapat wrote:
> On Thu, Nov 27, 2014 at 3:52 PM, Etsuro Fujita
> <fujita.etsuro@lab.ntt.co.jp <mailto:fujita.etsuro@lab.ntt.co.jp>> wrote:
> Apart from the above, I noticed that the patch doesn't consider to
> call ExplainForeignModify during EXPLAIN for an inherited
> UPDATE/DELETE, as shown below (note that there are no UPDATE remote
> queries displayed):
> So, I'd like to modify explain.c to show those queries like this:
> postgres=# explain verbose update parent set a = a * 2 where a = 5;
> QUERY PLAN
> ------------------------------__------------------------------__-------------------------
> Update on public.parent (cost=0.00..280.77 rows=25 width=10)
> -> Seq Scan on public.parent (cost=0.00..0.00 rows=1 width=10)
> Output: (parent.a * 2), parent.ctid
> Filter: (parent.a = 5)
> Remote SQL: UPDATE public.mytable_1 SET a = $2 WHERE ctid = $1
> -> Foreign Scan on public.ft1 (cost=100.00..140.38 rows=12
> width=10)
> Output: (ft1.a * 2), ft1.ctid
> Remote SQL: SELECT a, ctid FROM public.mytable_1 WHERE ((a
> = 5)) FOR UPDATE
> Remote SQL: UPDATE public.mytable_2 SET a = $2 WHERE ctid = $1
> -> Foreign Scan on public.ft2 (cost=100.00..140.38 rows=12
> width=10)
> Output: (ft2.a * 2), ft2.ctid
> Remote SQL: SELECT a, ctid FROM public.mytable_2 WHERE ((a
> = 5)) FOR UPDATE
> (12 rows)
> Two "remote SQL" under a single node would be confusing. Also the node
> is labelled as "Foreign Scan". It would be confusing to show an "UPDATE"
> command under this "scan" node.
I thought this as an extention of the existing (ie, non-inherited) case
(see the below example) to the inherited case.
postgres=# explain verbose update ft1 set a = a * 2 where a = 5;
QUERY PLAN
-------------------------------------------------------------------------------------
Update on public.ft1 (cost=100.00..140.38 rows=12 width=10)
Remote SQL: UPDATE public.mytable_1 SET a = $2 WHERE ctid = $1
-> Foreign Scan on public.ft1 (cost=100.00..140.38 rows=12 width=10)
Output: (a * 2), ctid
Remote SQL: SELECT a, ctid FROM public.mytable_1 WHERE ((a =
5)) FOR UPDATE
(5 rows)
I think we should show update commands somewhere for the inherited case
as for the non-inherited case. Alternatives to this are welcome.
> BTW, I was experimenting with DMLs being executed on multiple FDW server
> under same transaction and found that the transactions may not be atomic
> (and may be inconsistent), if one or more of the server fails to commit
> while rest of them commit the transaction. The reason for this is, we do
> not "rollback" the already "committed" changes to the foreign server, if
> one or more of them fail to "commit" a transaction. With foreign tables
> under inheritance hierarchy a single DML can span across multiple
> servers and the result may not be atomic (and may be inconsistent). So,
IIUC, even the transactions over the local and the *single* remote
server are not guaranteed to be executed atomically in the current form.
It is possible that the remote transaction succeeds and the local one
fails, for example, resulting in data inconsistency between the local
and the remote.
> either we have to disable DMLs on an inheritance hierarchy which spans
> multiple servers. OR make sure that such transactions follow 2PC norms.
-1 for disabling update queries on such an inheritance hierarchy because
I think we should leave that to the user's judgment. And I think 2PC is
definitely a separate patch.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-12-03 10:35 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 2 replies; 179+ messages in thread
From: Ashutosh Bapat @ 2014-12-03 10:35 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On Tue, Dec 2, 2014 at 8:29 AM, Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
wrote:
> (2014/11/28 18:14), Ashutosh Bapat wrote:
>
>> On Thu, Nov 27, 2014 at 3:52 PM, Etsuro Fujita
>> <fujita.etsuro@lab.ntt.co.jp <mailto:fujita.etsuro@lab.ntt.co.jp>> wrote:
>> Apart from the above, I noticed that the patch doesn't consider to
>> call ExplainForeignModify during EXPLAIN for an inherited
>> UPDATE/DELETE, as shown below (note that there are no UPDATE remote
>> queries displayed):
>>
>
> So, I'd like to modify explain.c to show those queries like this:
>>
>
> postgres=# explain verbose update parent set a = a * 2 where a = 5;
>> QUERY PLAN
>> ------------------------------__----------------------------
>> --__-------------------------
>> Update on public.parent (cost=0.00..280.77 rows=25 width=10)
>> -> Seq Scan on public.parent (cost=0.00..0.00 rows=1 width=10)
>> Output: (parent.a * 2), parent.ctid
>> Filter: (parent.a = 5)
>> Remote SQL: UPDATE public.mytable_1 SET a = $2 WHERE ctid = $1
>> -> Foreign Scan on public.ft1 (cost=100.00..140.38 rows=12
>> width=10)
>> Output: (ft1.a * 2), ft1.ctid
>> Remote SQL: SELECT a, ctid FROM public.mytable_1 WHERE ((a
>> = 5)) FOR UPDATE
>> Remote SQL: UPDATE public.mytable_2 SET a = $2 WHERE ctid = $1
>> -> Foreign Scan on public.ft2 (cost=100.00..140.38 rows=12
>> width=10)
>> Output: (ft2.a * 2), ft2.ctid
>> Remote SQL: SELECT a, ctid FROM public.mytable_2 WHERE ((a
>> = 5)) FOR UPDATE
>> (12 rows)
>>
>
> Two "remote SQL" under a single node would be confusing. Also the node
>> is labelled as "Foreign Scan". It would be confusing to show an "UPDATE"
>> command under this "scan" node.
>>
>
> I thought this as an extention of the existing (ie, non-inherited) case
> (see the below example) to the inherited case.
>
> postgres=# explain verbose update ft1 set a = a * 2 where a = 5;
> QUERY PLAN
> ------------------------------------------------------------
> -------------------------
> Update on public.ft1 (cost=100.00..140.38 rows=12 width=10)
> Remote SQL: UPDATE public.mytable_1 SET a = $2 WHERE ctid = $1
> -> Foreign Scan on public.ft1 (cost=100.00..140.38 rows=12 width=10)
> Output: (a * 2), ctid
> Remote SQL: SELECT a, ctid FROM public.mytable_1 WHERE ((a = 5))
> FOR UPDATE
> (5 rows)
>
> I think we should show update commands somewhere for the inherited case as
> for the non-inherited case. Alternatives to this are welcome.
>
This is not exactly extension of non-inheritance case. non-inheritance case
doesn't show two remote SQLs under the same plan node. May be you can
rename the label Remote SQL as Remote UPDATE/INSERT/DELETE (or something to
that effect) for the DML command and the Foreign plan node should be
renamed to Foreign access node or something to indicate that it does both
the scan as well as DML. I am not keen about the actual terminology, but I
think a reader of plan shouldn't get confused.
We can leave this for committer's judgement.
>
> BTW, I was experimenting with DMLs being executed on multiple FDW server
>> under same transaction and found that the transactions may not be atomic
>> (and may be inconsistent), if one or more of the server fails to commit
>> while rest of them commit the transaction. The reason for this is, we do
>> not "rollback" the already "committed" changes to the foreign server, if
>> one or more of them fail to "commit" a transaction. With foreign tables
>> under inheritance hierarchy a single DML can span across multiple
>> servers and the result may not be atomic (and may be inconsistent). So,
>>
>
> IIUC, even the transactions over the local and the *single* remote server
> are not guaranteed to be executed atomically in the current form. It is
> possible that the remote transaction succeeds and the local one fails, for
> example, resulting in data inconsistency between the local and the remote.
>
IIUC, while committing transactions involving a single remote server, the
steps taken are as follows
1. the local changes are brought to PRE-COMMIT stage, which means that the
transaction *will* succeed locally after successful completion of this
phase,
2. COMMIT message is sent to the foreign server
3. If step two succeeds, local changes are committed and successful commit
is conveyed to the client
4. if step two fails, local changes are rolled back and abort status is
conveyed to the client
5. If step 1 itself fails, the remote changes are rolled back.
This is as per one phase commit protocol which guarantees ACID for single
foreign data source. So, the changes involving local and a single foreign
server seem to be atomic and consistent.
>
> either we have to disable DMLs on an inheritance hierarchy which spans
>> multiple servers. OR make sure that such transactions follow 2PC norms.
>>
>
> -1 for disabling update queries on such an inheritance hierarchy because I
> think we should leave that to the user's judgment. And I think 2PC is
> definitely a separate patch.
I agree that 2pc is much larger work and is subject for separate patch/es.
But it may not be acceptable that changes made within a single command
violate atomicity and consistency, which can not be controlled or altered
by user intervention. Again, we can leave it for committer's judgement.
Marking this as "ready for committer".
>
>
> Thanks,
>
> Best regards,
> Etsuro Fujita
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-12-03 10:39 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
1 sibling, 0 replies; 179+ messages in thread
From: Ashutosh Bapat @ 2014-12-03 10:39 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On Wed, Dec 3, 2014 at 4:05 PM, Ashutosh Bapat <
ashutosh.bapat@enterprisedb.com> wrote:
>
>
> On Tue, Dec 2, 2014 at 8:29 AM, Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp
> > wrote:
>
>> (2014/11/28 18:14), Ashutosh Bapat wrote:
>>
>>> On Thu, Nov 27, 2014 at 3:52 PM, Etsuro Fujita
>>> <fujita.etsuro@lab.ntt.co.jp <mailto:fujita.etsuro@lab.ntt.co.jp>>
>>> wrote:
>>> Apart from the above, I noticed that the patch doesn't consider to
>>> call ExplainForeignModify during EXPLAIN for an inherited
>>> UPDATE/DELETE, as shown below (note that there are no UPDATE remote
>>> queries displayed):
>>>
>>
>> So, I'd like to modify explain.c to show those queries like this:
>>>
>>
>> postgres=# explain verbose update parent set a = a * 2 where a = 5;
>>> QUERY PLAN
>>> ------------------------------__----------------------------
>>> --__-------------------------
>>> Update on public.parent (cost=0.00..280.77 rows=25 width=10)
>>> -> Seq Scan on public.parent (cost=0.00..0.00 rows=1 width=10)
>>> Output: (parent.a * 2), parent.ctid
>>> Filter: (parent.a = 5)
>>> Remote SQL: UPDATE public.mytable_1 SET a = $2 WHERE ctid = $1
>>> -> Foreign Scan on public.ft1 (cost=100.00..140.38 rows=12
>>> width=10)
>>> Output: (ft1.a * 2), ft1.ctid
>>> Remote SQL: SELECT a, ctid FROM public.mytable_1 WHERE ((a
>>> = 5)) FOR UPDATE
>>> Remote SQL: UPDATE public.mytable_2 SET a = $2 WHERE ctid = $1
>>> -> Foreign Scan on public.ft2 (cost=100.00..140.38 rows=12
>>> width=10)
>>> Output: (ft2.a * 2), ft2.ctid
>>> Remote SQL: SELECT a, ctid FROM public.mytable_2 WHERE ((a
>>> = 5)) FOR UPDATE
>>> (12 rows)
>>>
>>
>> Two "remote SQL" under a single node would be confusing. Also the node
>>> is labelled as "Foreign Scan". It would be confusing to show an "UPDATE"
>>> command under this "scan" node.
>>>
>>
>> I thought this as an extention of the existing (ie, non-inherited) case
>> (see the below example) to the inherited case.
>>
>> postgres=# explain verbose update ft1 set a = a * 2 where a = 5;
>> QUERY PLAN
>> ------------------------------------------------------------
>> -------------------------
>> Update on public.ft1 (cost=100.00..140.38 rows=12 width=10)
>> Remote SQL: UPDATE public.mytable_1 SET a = $2 WHERE ctid = $1
>> -> Foreign Scan on public.ft1 (cost=100.00..140.38 rows=12 width=10)
>> Output: (a * 2), ctid
>> Remote SQL: SELECT a, ctid FROM public.mytable_1 WHERE ((a = 5))
>> FOR UPDATE
>> (5 rows)
>>
>> I think we should show update commands somewhere for the inherited case
>> as for the non-inherited case. Alternatives to this are welcome.
>>
> This is not exactly extension of non-inheritance case. non-inheritance
> case doesn't show two remote SQLs under the same plan node. May be you can
> rename the label Remote SQL as Remote UPDATE/INSERT/DELETE (or something to
> that effect) for the DML command and the Foreign plan node should be
> renamed to Foreign access node or something to indicate that it does both
> the scan as well as DML. I am not keen about the actual terminology, but I
> think a reader of plan shouldn't get confused.
>
> We can leave this for committer's judgement.
>
>>
>> BTW, I was experimenting with DMLs being executed on multiple FDW server
>>> under same transaction and found that the transactions may not be atomic
>>> (and may be inconsistent), if one or more of the server fails to commit
>>> while rest of them commit the transaction. The reason for this is, we do
>>> not "rollback" the already "committed" changes to the foreign server, if
>>> one or more of them fail to "commit" a transaction. With foreign tables
>>> under inheritance hierarchy a single DML can span across multiple
>>> servers and the result may not be atomic (and may be inconsistent). So,
>>>
>>
>> IIUC, even the transactions over the local and the *single* remote server
>> are not guaranteed to be executed atomically in the current form. It is
>> possible that the remote transaction succeeds and the local one fails, for
>> example, resulting in data inconsistency between the local and the remote.
>>
>
> IIUC, while committing transactions involving a single remote server, the
> steps taken are as follows
> 1. the local changes are brought to PRE-COMMIT stage, which means that the
> transaction *will* succeed locally after successful completion of this
> phase,
> 2. COMMIT message is sent to the foreign server
> 3. If step two succeeds, local changes are committed and successful commit
> is conveyed to the client
> 4. if step two fails, local changes are rolled back and abort status is
> conveyed to the client
> 5. If step 1 itself fails, the remote changes are rolled back.
> This is as per one phase commit protocol which guarantees ACID for single
> foreign data source. So, the changes involving local and a single foreign
> server seem to be atomic and consistent.
>
>>
>> either we have to disable DMLs on an inheritance hierarchy which spans
>>> multiple servers. OR make sure that such transactions follow 2PC norms.
>>>
>>
>> -1 for disabling update queries on such an inheritance hierarchy because
>> I think we should leave that to the user's judgment. And I think 2PC is
>> definitely a separate patch.
>
>
> I agree that 2pc is much larger work and is subject for separate patch/es.
> But it may not be acceptable that changes made within a single command
> violate atomicity and consistency, which can not be controlled or altered
> by user intervention. Again, we can leave it for committer's judgement.
>
> Marking this as "ready for committer".
>
Sorry, I noticed in the commitfest app, that there are other reviewers as
well. Let's wait for them to comment.
>
>>
>> Thanks,
>>
>> Best regards,
>> Etsuro Fujita
>>
>
>
>
> --
> Best Wishes,
> Ashutosh Bapat
> EnterpriseDB Corporation
> The Postgres Database Company
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-12-04 03:35 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
1 sibling, 2 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-12-04 03:35 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
(2014/12/03 19:35), Ashutosh Bapat wrote:
> On Tue, Dec 2, 2014 at 8:29 AM, Etsuro Fujita
> <fujita.etsuro@lab.ntt.co.jp <mailto:fujita.etsuro@lab.ntt.co.jp>> wrote:
> This is not exactly extension of non-inheritance case. non-inheritance
> case doesn't show two remote SQLs under the same plan node. May be you
> can rename the label Remote SQL as Remote UPDATE/INSERT/DELETE (or
> something to that effect) for the DML command and the Foreign plan node
> should be renamed to Foreign access node or something to indicate that
> it does both the scan as well as DML. I am not keen about the actual
> terminology, but I think a reader of plan shouldn't get confused.
>
> We can leave this for committer's judgement.
Thanks for the proposal! I think that would be a good idea. But I
think there would be another idea. An example will be shown below. We
show the update commands below the ModifyTable node, not above the
corresponding ForeignScan nodes, so maybe less confusing. If there are
no objections of you and others, I'll update the patch this way.
postgres=# explain verbose update parent set a = a * 2 where a = 5;
QUERY PLAN
-------------------------------------------------------------------------------------
Update on public.parent (cost=0.00..280.77 rows=25 width=10)
On public.ft1
Remote SQL: UPDATE public.mytable_1 SET a = $2 WHERE ctid = $1
On public.ft2
Remote SQL: UPDATE public.mytable_2 SET a = $2 WHERE ctid = $1
-> Seq Scan on public.parent (cost=0.00..0.00 rows=1 width=10)
Output: (parent.a * 2), parent.ctid
Filter: (parent.a = 5)
-> Foreign Scan on public.ft1 (cost=100.00..140.38 rows=12 width=10)
Output: (ft1.a * 2), ft1.ctid
Remote SQL: SELECT a, ctid FROM public.mytable_1 WHERE ((a =
5)) FOR UPDATE
-> Foreign Scan on public.ft2 (cost=100.00..140.38 rows=12 width=10)
Output: (ft2.a * 2), ft2.ctid
Remote SQL: SELECT a, ctid FROM public.mytable_2 WHERE ((a =
5)) FOR UPDATE
(12 rows)
> IIUC, even the transactions over the local and the *single* remote
> server are not guaranteed to be executed atomically in the current
> form. It is possible that the remote transaction succeeds and the
> local one fails, for example, resulting in data inconsistency
> between the local and the remote.
> IIUC, while committing transactions involving a single remote server,
> the steps taken are as follows
> 1. the local changes are brought to PRE-COMMIT stage, which means that
> the transaction *will* succeed locally after successful completion of
> this phase,
> 2. COMMIT message is sent to the foreign server
> 3. If step two succeeds, local changes are committed and successful
> commit is conveyed to the client
> 4. if step two fails, local changes are rolled back and abort status is
> conveyed to the client
> 5. If step 1 itself fails, the remote changes are rolled back.
> This is as per one phase commit protocol which guarantees ACID for
> single foreign data source. So, the changes involving local and a single
> foreign server seem to be atomic and consistent.
Really? Maybe I'm missing something, but I don't think the current
implementation for committing transactions has such a mechanism stated
in step 1. So, I think it's possible that the local transaction fails
in step3 while the remote transaction succeeds, as mentioned above.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-12-04 04:30 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 2 replies; 179+ messages in thread
From: Ashutosh Bapat @ 2014-12-04 04:30 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On Thu, Dec 4, 2014 at 9:05 AM, Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
wrote:
> (2014/12/03 19:35), Ashutosh Bapat wrote:
>
>> On Tue, Dec 2, 2014 at 8:29 AM, Etsuro Fujita
>> <fujita.etsuro@lab.ntt.co.jp <mailto:fujita.etsuro@lab.ntt.co.jp>> wrote:
>>
>
> This is not exactly extension of non-inheritance case. non-inheritance
>> case doesn't show two remote SQLs under the same plan node. May be you
>> can rename the label Remote SQL as Remote UPDATE/INSERT/DELETE (or
>> something to that effect) for the DML command and the Foreign plan node
>> should be renamed to Foreign access node or something to indicate that
>> it does both the scan as well as DML. I am not keen about the actual
>> terminology, but I think a reader of plan shouldn't get confused.
>>
>> We can leave this for committer's judgement.
>>
>
> Thanks for the proposal! I think that would be a good idea. But I think
> there would be another idea. An example will be shown below. We show the
> update commands below the ModifyTable node, not above the corresponding
> ForeignScan nodes, so maybe less confusing. If there are no objections of
> you and others, I'll update the patch this way.
>
> postgres=# explain verbose update parent set a = a * 2 where a = 5;
> QUERY PLAN
> ------------------------------------------------------------
> -------------------------
> Update on public.parent (cost=0.00..280.77 rows=25 width=10)
> On public.ft1
> Remote SQL: UPDATE public.mytable_1 SET a = $2 WHERE ctid = $1
> On public.ft2
> Remote SQL: UPDATE public.mytable_2 SET a = $2 WHERE ctid = $1
> -> Seq Scan on public.parent (cost=0.00..0.00 rows=1 width=10)
> Output: (parent.a * 2), parent.ctid
> Filter: (parent.a = 5)
> -> Foreign Scan on public.ft1 (cost=100.00..140.38 rows=12 width=10)
> Output: (ft1.a * 2), ft1.ctid
> Remote SQL: SELECT a, ctid FROM public.mytable_1 WHERE ((a = 5))
> FOR UPDATE
> -> Foreign Scan on public.ft2 (cost=100.00..140.38 rows=12 width=10)
> Output: (ft2.a * 2), ft2.ctid
> Remote SQL: SELECT a, ctid FROM public.mytable_2 WHERE ((a = 5))
> FOR UPDATE
> (12 rows)
>
>
Looks better.
> IIUC, even the transactions over the local and the *single* remote
>> server are not guaranteed to be executed atomically in the current
>> form. It is possible that the remote transaction succeeds and the
>> local one fails, for example, resulting in data inconsistency
>> between the local and the remote.
>>
>
> IIUC, while committing transactions involving a single remote server,
>> the steps taken are as follows
>> 1. the local changes are brought to PRE-COMMIT stage, which means that
>> the transaction *will* succeed locally after successful completion of
>> this phase,
>> 2. COMMIT message is sent to the foreign server
>> 3. If step two succeeds, local changes are committed and successful
>> commit is conveyed to the client
>> 4. if step two fails, local changes are rolled back and abort status is
>> conveyed to the client
>> 5. If step 1 itself fails, the remote changes are rolled back.
>> This is as per one phase commit protocol which guarantees ACID for
>> single foreign data source. So, the changes involving local and a single
>> foreign server seem to be atomic and consistent.
>>
>
> Really? Maybe I'm missing something, but I don't think the current
> implementation for committing transactions has such a mechanism stated in
> step 1. So, I think it's possible that the local transaction fails in
> step3 while the remote transaction succeeds, as mentioned above.
>
>
PFA a script attached which shows this. You may want to check the code in
pgfdw_xact_callback() for actions taken by postgres_fdw on various events.
CommitTransaction() for how those events are generated. The code there
complies with the sequence above.
>
> Thanks,
>
> Best regards,
> Etsuro Fujita
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-12-04 04:48 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
1 sibling, 0 replies; 179+ messages in thread
From: Ashutosh Bapat @ 2014-12-04 04:48 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
Sorry, here's the script.
On Thu, Dec 4, 2014 at 10:00 AM, Ashutosh Bapat <
ashutosh.bapat@enterprisedb.com> wrote:
>
>
> On Thu, Dec 4, 2014 at 9:05 AM, Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp
> > wrote:
>
>> (2014/12/03 19:35), Ashutosh Bapat wrote:
>>
>>> On Tue, Dec 2, 2014 at 8:29 AM, Etsuro Fujita
>>> <fujita.etsuro@lab.ntt.co.jp <mailto:fujita.etsuro@lab.ntt.co.jp>>
>>> wrote:
>>>
>>
>> This is not exactly extension of non-inheritance case. non-inheritance
>>> case doesn't show two remote SQLs under the same plan node. May be you
>>> can rename the label Remote SQL as Remote UPDATE/INSERT/DELETE (or
>>> something to that effect) for the DML command and the Foreign plan node
>>> should be renamed to Foreign access node or something to indicate that
>>> it does both the scan as well as DML. I am not keen about the actual
>>> terminology, but I think a reader of plan shouldn't get confused.
>>>
>>> We can leave this for committer's judgement.
>>>
>>
>> Thanks for the proposal! I think that would be a good idea. But I think
>> there would be another idea. An example will be shown below. We show the
>> update commands below the ModifyTable node, not above the corresponding
>> ForeignScan nodes, so maybe less confusing. If there are no objections of
>> you and others, I'll update the patch this way.
>>
>> postgres=# explain verbose update parent set a = a * 2 where a = 5;
>> QUERY PLAN
>> ------------------------------------------------------------
>> -------------------------
>> Update on public.parent (cost=0.00..280.77 rows=25 width=10)
>> On public.ft1
>> Remote SQL: UPDATE public.mytable_1 SET a = $2 WHERE ctid = $1
>> On public.ft2
>> Remote SQL: UPDATE public.mytable_2 SET a = $2 WHERE ctid = $1
>> -> Seq Scan on public.parent (cost=0.00..0.00 rows=1 width=10)
>> Output: (parent.a * 2), parent.ctid
>> Filter: (parent.a = 5)
>> -> Foreign Scan on public.ft1 (cost=100.00..140.38 rows=12 width=10)
>> Output: (ft1.a * 2), ft1.ctid
>> Remote SQL: SELECT a, ctid FROM public.mytable_1 WHERE ((a = 5))
>> FOR UPDATE
>> -> Foreign Scan on public.ft2 (cost=100.00..140.38 rows=12 width=10)
>> Output: (ft2.a * 2), ft2.ctid
>> Remote SQL: SELECT a, ctid FROM public.mytable_2 WHERE ((a = 5))
>> FOR UPDATE
>> (12 rows)
>>
>>
> Looks better.
>
>
>> IIUC, even the transactions over the local and the *single* remote
>>> server are not guaranteed to be executed atomically in the current
>>> form. It is possible that the remote transaction succeeds and the
>>> local one fails, for example, resulting in data inconsistency
>>> between the local and the remote.
>>>
>>
>> IIUC, while committing transactions involving a single remote server,
>>> the steps taken are as follows
>>> 1. the local changes are brought to PRE-COMMIT stage, which means that
>>> the transaction *will* succeed locally after successful completion of
>>> this phase,
>>> 2. COMMIT message is sent to the foreign server
>>> 3. If step two succeeds, local changes are committed and successful
>>> commit is conveyed to the client
>>> 4. if step two fails, local changes are rolled back and abort status is
>>> conveyed to the client
>>> 5. If step 1 itself fails, the remote changes are rolled back.
>>> This is as per one phase commit protocol which guarantees ACID for
>>> single foreign data source. So, the changes involving local and a single
>>> foreign server seem to be atomic and consistent.
>>>
>>
>> Really? Maybe I'm missing something, but I don't think the current
>> implementation for committing transactions has such a mechanism stated in
>> step 1. So, I think it's possible that the local transaction fails in
>> step3 while the remote transaction succeeds, as mentioned above.
>>
>>
> PFA a script attached which shows this. You may want to check the code in
> pgfdw_xact_callback() for actions taken by postgres_fdw on various events.
> CommitTransaction() for how those events are generated. The code there
> complies with the sequence above.
>
>
>>
>> Thanks,
>>
>> Best regards,
>> Etsuro Fujita
>>
>
>
>
> --
> Best Wishes,
> Ashutosh Bapat
> EnterpriseDB Corporation
> The Postgres Database Company
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[application/octet-stream] tran_inconsistency.sql (3.1K, ../../CAFjFpReNPoTYcoRJk_1CDJVe53o40v1FxG=PVK-_Bk3YP=kP-Q@mail.gmail.com/3-tran_inconsistency.sql)
download
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-12-06 15:51 Noah Misch <noah@leadboat.com>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
1 sibling, 1 reply; 179+ messages in thread
From: Noah Misch @ 2014-12-06 15:51 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On Thu, Dec 04, 2014 at 10:00:14AM +0530, Ashutosh Bapat wrote:
> On Thu, Dec 4, 2014 at 9:05 AM, Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp> wrote:
> > (2014/12/03 19:35), Ashutosh Bapat wrote:
> >> On Tue, Dec 2, 2014 at 8:29 AM, Etsuro Fujita
> >> <fujita.etsuro@lab.ntt.co.jp <mailto:fujita.etsuro@lab.ntt.co.jp>> wrote:
> > IIUC, even the transactions over the local and the *single* remote
> >> server are not guaranteed to be executed atomically in the current
> >> form. It is possible that the remote transaction succeeds and the
> >> local one fails, for example, resulting in data inconsistency
> >> between the local and the remote.
> >>
> >
> > IIUC, while committing transactions involving a single remote server,
> >> the steps taken are as follows
> >> 1. the local changes are brought to PRE-COMMIT stage, which means that
> >> the transaction *will* succeed locally after successful completion of
> >> this phase,
> >> 2. COMMIT message is sent to the foreign server
> >> 3. If step two succeeds, local changes are committed and successful
> >> commit is conveyed to the client
> >> 4. if step two fails, local changes are rolled back and abort status is
> >> conveyed to the client
> >> 5. If step 1 itself fails, the remote changes are rolled back.
> >> This is as per one phase commit protocol which guarantees ACID for
> >> single foreign data source. So, the changes involving local and a single
> >> foreign server seem to be atomic and consistent.
> >>
> >
> > Really? Maybe I'm missing something, but I don't think the current
> > implementation for committing transactions has such a mechanism stated in
> > step 1. So, I think it's possible that the local transaction fails in
> > step3 while the remote transaction succeeds, as mentioned above.
> >
> >
> PFA a script attached which shows this. You may want to check the code in
> pgfdw_xact_callback() for actions taken by postgres_fdw on various events.
> CommitTransaction() for how those events are generated. The code there
> complies with the sequence above.
While postgres_fdw delays remote commit to eliminate many causes for having
one server commit while another aborts, other causes remain. The local
transaction could still fail due to WAL I/O problems or a system crash. 2PC
is the reliable answer, but that was explicitly out of scope for the initial
postgres_fdw write support. Does this inheritance patch add any atomicity
problem that goes away when one breaks up the inheritance hierarchy and
UPDATEs each table separately? If not, this limitation is okay.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-12-06 17:02 David Fetter <david@fetter.org>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: David Fetter @ 2014-12-06 17:02 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On Thu, Dec 04, 2014 at 12:35:54PM +0900, Etsuro Fujita wrote:
> (2014/12/03 19:35), Ashutosh Bapat wrote:
> >On Tue, Dec 2, 2014 at 8:29 AM, Etsuro Fujita
> ><fujita.etsuro@lab.ntt.co.jp <mailto:fujita.etsuro@lab.ntt.co.jp>> wrote:
>
> >This is not exactly extension of non-inheritance case. non-inheritance
> >case doesn't show two remote SQLs under the same plan node. May be you
> >can rename the label Remote SQL as Remote UPDATE/INSERT/DELETE (or
> >something to that effect) for the DML command and the Foreign plan node
> >should be renamed to Foreign access node or something to indicate that
> >it does both the scan as well as DML. I am not keen about the actual
> >terminology, but I think a reader of plan shouldn't get confused.
> >
> >We can leave this for committer's judgement.
>
> Thanks for the proposal! I think that would be a good idea. But I think
> there would be another idea. An example will be shown below. We show the
> update commands below the ModifyTable node, not above the corresponding
> ForeignScan nodes, so maybe less confusing. If there are no objections of
> you and others, I'll update the patch this way.
>
> postgres=# explain verbose update parent set a = a * 2 where a = 5;
> QUERY PLAN
> -------------------------------------------------------------------------------------
> Update on public.parent (cost=0.00..280.77 rows=25 width=10)
> On public.ft1
> Remote SQL: UPDATE public.mytable_1 SET a = $2 WHERE ctid = $1
^^^^^^^^^^
It occurs to me that the command generated by the FDW might well not
be SQL at all, as is the case with file_fdw and anything else that
talks to a NoSQL engine.
Would it be reasonable to call this "Remote command" or something
similarly generic?
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-12-08 02:15 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: David Fetter <david@fetter.org>
0 siblings, 0 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-12-08 02:15 UTC (permalink / raw)
To: David Fetter <david@fetter.org>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
(2014/12/07 2:02), David Fetter wrote:
> On Thu, Dec 04, 2014 at 12:35:54PM +0900, Etsuro Fujita wrote:
>> But I think
>> there would be another idea. An example will be shown below. We show the
>> update commands below the ModifyTable node, not above the corresponding
>> ForeignScan nodes, so maybe less confusing. If there are no objections of
>> you and others, I'll update the patch this way.
>>
>> postgres=# explain verbose update parent set a = a * 2 where a = 5;
>> QUERY PLAN
>> -------------------------------------------------------------------------------------
>> Update on public.parent (cost=0.00..280.77 rows=25 width=10)
>> On public.ft1
>> Remote SQL: UPDATE public.mytable_1 SET a = $2 WHERE ctid = $1
> ^^^^^^^^^^
> It occurs to me that the command generated by the FDW might well not
> be SQL at all, as is the case with file_fdw and anything else that
> talks to a NoSQL engine.
>
> Would it be reasonable to call this "Remote command" or something
> similarly generic?
Yeah, but I'd like to propose that this line is shown by the FDW API
(ie, ExplainForeignModify) as in non-inherited update cases, so that the
FDW developer can choose the right name. As for "On public.ft1", I'd
like to propose that the FDW API also show that by calling a function
for that introduced into the PG core (Would it be better to use "For"
rather than "On"?).
Sorry, my explanation was not enough.
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-12-08 06:17 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Noah Misch <noah@leadboat.com>
0 siblings, 1 reply; 179+ messages in thread
From: Ashutosh Bapat @ 2014-12-08 06:17 UTC (permalink / raw)
To: Noah Misch <noah@leadboat.com>; +Cc: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On Sat, Dec 6, 2014 at 9:21 PM, Noah Misch <noah@leadboat.com> wrote:
> On Thu, Dec 04, 2014 at 10:00:14AM +0530, Ashutosh Bapat wrote:
> > On Thu, Dec 4, 2014 at 9:05 AM, Etsuro Fujita <
> fujita.etsuro@lab.ntt.co.jp> wrote:
> > > (2014/12/03 19:35), Ashutosh Bapat wrote:
> > >> On Tue, Dec 2, 2014 at 8:29 AM, Etsuro Fujita
> > >> <fujita.etsuro@lab.ntt.co.jp <mailto:fujita.etsuro@lab.ntt.co.jp>>
> wrote:
> > > IIUC, even the transactions over the local and the *single* remote
> > >> server are not guaranteed to be executed atomically in the current
> > >> form. It is possible that the remote transaction succeeds and the
> > >> local one fails, for example, resulting in data inconsistency
> > >> between the local and the remote.
> > >>
> > >
> > > IIUC, while committing transactions involving a single remote server,
> > >> the steps taken are as follows
> > >> 1. the local changes are brought to PRE-COMMIT stage, which means that
> > >> the transaction *will* succeed locally after successful completion of
> > >> this phase,
> > >> 2. COMMIT message is sent to the foreign server
> > >> 3. If step two succeeds, local changes are committed and successful
> > >> commit is conveyed to the client
> > >> 4. if step two fails, local changes are rolled back and abort status
> is
> > >> conveyed to the client
> > >> 5. If step 1 itself fails, the remote changes are rolled back.
> > >> This is as per one phase commit protocol which guarantees ACID for
> > >> single foreign data source. So, the changes involving local and a
> single
> > >> foreign server seem to be atomic and consistent.
> > >>
> > >
> > > Really? Maybe I'm missing something, but I don't think the current
> > > implementation for committing transactions has such a mechanism stated
> in
> > > step 1. So, I think it's possible that the local transaction fails in
> > > step3 while the remote transaction succeeds, as mentioned above.
> > >
> > >
> > PFA a script attached which shows this. You may want to check the code in
> > pgfdw_xact_callback() for actions taken by postgres_fdw on various
> events.
> > CommitTransaction() for how those events are generated. The code there
> > complies with the sequence above.
>
> While postgres_fdw delays remote commit to eliminate many causes for having
> one server commit while another aborts, other causes remain. The local
> transaction could still fail due to WAL I/O problems or a system crash.
> 2PC
> is the reliable answer, but that was explicitly out of scope for the
> initial
> postgres_fdw write support. Does this inheritance patch add any atomicity
> problem that goes away when one breaks up the inheritance hierarchy and
> UPDATEs each table separately? If not, this limitation is okay.
>
If the UPDATES crafted after breaking up the inheritance hierarchy are
needed to be run within the same transaction (as the UPDATE on inheritance
hierarchy would do), yes, there is atomicity problem.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-12-08 12:10 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 0 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-12-08 12:10 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Noah Misch <noah@leadboat.com>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
(2014/12/08 15:17), Ashutosh Bapat wrote:
> On Sat, Dec 6, 2014 at 9:21 PM, Noah Misch <noah@leadboat.com
> <mailto:noah@leadboat.com>> wrote:
> Does this inheritance patch add any
> atomicity
> problem that goes away when one breaks up the inheritance hierarchy and
> UPDATEs each table separately? If not, this limitation is okay.
> If the UPDATES crafted after breaking up the inheritance hierarchy are
> needed to be run within the same transaction (as the UPDATE on
> inheritance hierarchy would do), yes, there is atomicity problem.
ISTM that your concern would basically a known problem. Consider the
following transaction.
BEGIN TRANSACTION;
UPDATE foo SET a = 100; -- updates on table foo in remote server1
UPDATE bar SET a = 100; -- updates on table bar in remote server2
COMMIT TRANSACTION;
This transaction would cause the atomicity problem if
pgfdw_xact_callback() for XACT_EVENT_PRE_COMMIT for foo succeeded and
then that for bar failed during CommitTransaction().
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-12-10 03:18 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
1 sibling, 2 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-12-10 03:18 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
Hi Ashutosh,
Thanks for the review!
(2014/11/28 18:14), Ashutosh Bapat wrote:
> On Thu, Nov 27, 2014 at 3:52 PM, Etsuro Fujita
> <fujita.etsuro@lab.ntt.co.jp <mailto:fujita.etsuro@lab.ntt.co.jp>> wrote:
> (2014/11/17 17:55), Ashutosh Bapat wrote:
> Here are my review comments for patch fdw-inh-3.patch.
> Tests
> -------
> 1. It seems like you have copied from testcase inherit.sql to
> postgres_fdw testcase. That's a good thing, but it makes the
> test quite
> long. May be we should have two tests in postgres_fdw contrib
> module,
> one for simple cases, and other for inheritance. What do you say?
> IMO, the test is not so time-consuming, so it doesn't seem worth
> splitting it into two.
> I am not worried about the timing but I am worried about the length of
> the file and hence ease of debugging in case we find any issues there.
> We will leave that for the commiter to decide.
OK
> Documentation
> --------------------
> 1. The change in ddl.sgml
> - We will refer to the child tables as partitions, though
> they
> - are in every way normal <productname>PostgreSQL</> tables.
> + We will refer to the child tables as partitions, though
> we assume
> + that they are normal <productname>PostgreSQL</> tables.
>
> adds phrase "we assume that", which confuses the intention
> behind the
> sentence. The original text is intended to highlight the equivalence
> between "partition" and "normal table", where as the addition
> esp. the
> word "assume" weakens that equivalence. Instead now we have to
> highlight
> the equivalence between "partition" and "normal or foreign
> table". The
> wording similar to "though they are either normal or foreign tables"
> should be used there.
> You are right, but I feel that there is something out of place in
> saying that there (5.10.2. Implementing Partitioning) because the
> procedure there has been written based on normal tables only. Put
> another way, if we say that, I think we'd need to add more docs,
> describing the syntax and/or the corresponding examples for
> foreign-table cases. But I'd like to leave that for another patch.
> So, how about the wording "we assume *here* that", instead of "we
> assume that", as I added the following notice in the previous
> section (5.10.1. Overview)?
>
> @@ -2650,7 +2669,10 @@ VALUES ('Albany', NULL, NULL, 'NY');
> table of a single parent table. The parent table itself is
> normally
> empty; it exists just to represent the entire data set. You
> should be
> familiar with inheritance (see <xref linkend="ddl-inherit">)
> before
> - attempting to set up partitioning.
> + attempting to set up partitioning. (The setup and management of
> + partitioned tables illustrated in this section assume that each
> + partition is a normal table. However, you can do that in a
> similar way
> + for cases where some or all partitions are foreign tables.)
> This looks ok, though, I would like to see final version of the
> document. But I think, we will leave that for committer to handle.
OK
> 2. The wording "some kind of optimization" gives vague picture.
> May be
> it can be worded as "Since the constraints are assumed to be
> true, they
> are used in constraint-based query optimization like constraint
> exclusion for partitioned tables.".
> + Those constraints are used in some kind of query
> optimization such
> + as constraint exclusion for partitioned tables (see
> + <xref linkend="ddl-partitioning">).
> Will follow your revision.
Done.
> Code
> -------
> 1. In the following change
> +/*
> * acquire_inherited_sample_rows -- acquire sample rows from
> inheritance tree
> *
> * This has the same API as acquire_sample_rows, except that
> rows are
> * collected from all inheritance children as well as the
> specified table.
> - * We fail and return zero if there are no inheritance children.
> + * We fail and return zero if there are no inheritance children or
> there are
> + * inheritance children that foreign tables.
>
> The addition should be "there are inheritance children that *are all
> *foreign tables. Note the addition "are all".
> Sorry, I incorrectly wrote the comment. What I tried to write is
> "We fail and return zero if there are no inheritance children or if
> we are not in VAC_MODE_SINGLE case and inheritance tree contains at
> least one foreign table.".
> You might want to use "English" description of VAC_MODE_SINGLE instead
> of that macro in the comment, so that reader doesn't have to look up
> VAC_MODE_SINGLE. But I think, we will leave this for the committer.
I corrected the comments and translated the macro into the English
description.
> 2. The function has_foreign() be better named
> has_foreign_child()? This
> How about "has_foreign_table"?
> has_foreign_child() would be better, since these are "children" in the
> inheritance hierarchy and not mere "table"s.
Done. But I renamed it to has_foreign_children() because it sounds more
natural at least to me.
> function loops through all the tableoids passed even after it
> has found
> a foreign table. Why can't we return true immediately after
> finding the
> first foreign table, unless the side effects of heap_open() on
> all the
> table are required. But I don't see that to be the case, since these
> tables are locked already through a previous call to
> heap_open(). In the
> Good catch! Will fix.
> same function instead of argument name parentrelId may be we
> should use
> name parent_oid, so that we use same notation for parent and
> child table
> OIDs.
> Will fix.
Done.
> 3. Regarding enum VacuumMode - it's being used only in case of
> acquire_inherited_sample_rows(__) and that too, to check only a
> single
> value of the three defined there. May be we should just infer
> that value
> inside acquire_inherited_sample_rows(__) or pass a boolean true
> or false
> from do_analyse_rel() based on the VacuumStmt. I do not see need
> for a
> separate three value enum of which only one value is used and
> also to
> pass it down from vacuum() by changing signatures of the minion
> functions.
> I introduced that for possible future use. See the discussion in [1].
> Will leave it for the commiter to decide.
I noticed that the signatures need not to be modified, as you said.
Thanks for pointing that out! So, I revised the patch not to change the
signatures, though I left the enum, renaming it to AnalyzeMode. Let's
have the committer's review.
> 4. In postgresGetForeignPlan(), the code added by this patch is
> required
> to handle the case when the row mark is placed on a parent table and
> hence is required to be applied on the child table. We need a
> comment
> explaining this. Otherwise, the three step process to get the
> row mark
> information isn't clear for a reader.
> Will add the comment.
Done.
> 5. In expand_inherited_rtentry() why do you need a separate variable
> hasForeign? Instead of using that variable, you can actually
> set/reset
> rte->hasForeign since existence of even a single foreign child would
> mark that member as true. - After typing this, I got the answer
> when I
> looked at the function code. Every child's RTE is initially a
> copy of
> parent's RTE and hence hasForeign status would be inherited by every
> child after the first foreign child. I think, this reasoning
> should be
> added as comment before assignment to rte->hasForeign at the end
> of the
> function.
> As you mentioned, I think we could set rte->hasForeign directly
> during the scan for the inheritance set, without the separate
> variable, hasForeign. But ISTM that it'd improve code readability
> to set rte->hasForeign using the separate variable at the end of the
> function because rte->hasForeign has its meaning only when rte->inh
> is true and because we know whether rte->inh is true, at the end of
> the function.
> Fine. Please use "hasForeignChild" instead of just "hasForeign" without
> a clue as to what is "foreign" here.
Done. But I renamed it to "hasForeignChildren".
> 6. The tests in foreign_data.sql are pretty extensive. Thanks
> for that.
> I think, we should also check the effect of each of the following
> command using \d on appropriate tables.
> +CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
> + SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted"
> 'value');
> +ALTER FOREIGN TABLE ft2 NO INHERIT pt1;
> +DROP FOREIGN TABLE ft2;
> +CREATE FOREIGN TABLE ft2 (
> + c1 integer NOT NULL,
> + c2 text,
> + c3 date
> +) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted"
> 'value');
> +ALTER FOREIGN TABLE ft2 INHERIT pt1;
> Will fix.
Done.
> Apart from the above, I noticed that the patch doesn't consider to
> call ExplainForeignModify during EXPLAIN for an inherited
> UPDATE/DELETE, as shown below (note that there are no UPDATE remote
> queries displayed):
Since there seems to be no objections, I updated the patch as proposed
upthread. Here is an example.
postgres=# explain (format text, verbose) update parent as p set a = a *
2 returning *;
QUERY PLAN
--------------------------------------------------------------------------------
Update on public.parent p (cost=0.00..202.33 rows=11 width=10)
Output: p.a
For public.ft1 p_1
Remote SQL: UPDATE public.mytable_1 SET a = $2 WHERE ctid = $1
RETURNING a
For public.ft2 p_2
Remote SQL: UPDATE public.mytable_2 SET a = $2 WHERE ctid = $1
RETURNING a
-> Seq Scan on public.parent p (cost=0.00..0.00 rows=1 width=10)
Output: (p.a * 2), p.ctid
-> Foreign Scan on public.ft1 p_1 (cost=100.00..101.16 rows=5
width=10)
Output: (p_1.a * 2), p_1.ctid
Remote SQL: SELECT a, ctid FROM public.mytable_1 FOR UPDATE
-> Foreign Scan on public.ft2 p_2 (cost=100.00..101.16 rows=5
width=10)
Output: (p_2.a * 2), p_2.ctid
Remote SQL: SELECT a, ctid FROM public.mytable_2 FOR UPDATE
(14 rows)
Other changes:
* revised regression tests for contrib/file_fdw to refer to tableoid.
* revised docs a bit further.
Attached are updated patches. Patch fdw-inh-5.patch has been created on
top of patch fdw-chk-5.patch. Patch fdw-chk-5.patch is basically the
same as the previous one fdw-chk-4.patch, but I slightly modified that
one. The changes are the following.
* added to foreign_data.sql more tests for your comments.
* revised docs on ALTER FOREIGN TABLE a bit further.
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-patch] fdw-chk-5.patch (34.4K, ../../5487BB9D.7070605@lab.ntt.co.jp/2-fdw-chk-5.patch)
download | inline diff:
*** a/contrib/file_fdw/input/file_fdw.source
--- b/contrib/file_fdw/input/file_fdw.source
***************
*** 62,68 **** CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', null '
CREATE FOREIGN TABLE tbl () SERVER file_server; -- ERROR
CREATE FOREIGN TABLE agg_text (
! a int2,
b float4
) SERVER file_server
OPTIONS (format 'text', filename '@abs_srcdir@/data/agg.data', delimiter ' ', null '\N');
--- 62,68 ----
CREATE FOREIGN TABLE tbl () SERVER file_server; -- ERROR
CREATE FOREIGN TABLE agg_text (
! a int2 CHECK (a >= 0),
b float4
) SERVER file_server
OPTIONS (format 'text', filename '@abs_srcdir@/data/agg.data', delimiter ' ', null '\N');
***************
*** 72,82 **** CREATE FOREIGN TABLE agg_csv (
--- 72,84 ----
b float4
) SERVER file_server
OPTIONS (format 'csv', filename '@abs_srcdir@/data/agg.csv', header 'true', delimiter ';', quote '@', escape '"', null '');
+ ALTER FOREIGN TABLE agg_csv ADD CHECK (a >= 0);
CREATE FOREIGN TABLE agg_bad (
a int2,
b float4
) SERVER file_server
OPTIONS (format 'csv', filename '@abs_srcdir@/data/agg.bad', header 'true', delimiter ';', quote '@', escape '"', null '');
+ ALTER FOREIGN TABLE agg_bad ADD CHECK (a >= 0);
-- per-column options tests
CREATE FOREIGN TABLE text_csv (
***************
*** 134,139 **** DELETE FROM agg_csv WHERE a = 100;
--- 136,153 ----
-- but this should be ignored
SELECT * FROM agg_csv FOR UPDATE;
+ -- constraint exclusion tests
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
+ \t off
+ SELECT * FROM agg_csv WHERE a < 0;
+ SET constraint_exclusion = 'on';
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
+ \t off
+ SELECT * FROM agg_csv WHERE a < 0;
+ SET constraint_exclusion = 'partition';
+
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/file_fdw/output/file_fdw.source
--- b/contrib/file_fdw/output/file_fdw.source
***************
*** 78,84 **** ERROR: COPY null representation cannot use newline or carriage return
CREATE FOREIGN TABLE tbl () SERVER file_server; -- ERROR
ERROR: filename is required for file_fdw foreign tables
CREATE FOREIGN TABLE agg_text (
! a int2,
b float4
) SERVER file_server
OPTIONS (format 'text', filename '@abs_srcdir@/data/agg.data', delimiter ' ', null '\N');
--- 78,84 ----
CREATE FOREIGN TABLE tbl () SERVER file_server; -- ERROR
ERROR: filename is required for file_fdw foreign tables
CREATE FOREIGN TABLE agg_text (
! a int2 CHECK (a >= 0),
b float4
) SERVER file_server
OPTIONS (format 'text', filename '@abs_srcdir@/data/agg.data', delimiter ' ', null '\N');
***************
*** 88,98 **** CREATE FOREIGN TABLE agg_csv (
--- 88,100 ----
b float4
) SERVER file_server
OPTIONS (format 'csv', filename '@abs_srcdir@/data/agg.csv', header 'true', delimiter ';', quote '@', escape '"', null '');
+ ALTER FOREIGN TABLE agg_csv ADD CHECK (a >= 0);
CREATE FOREIGN TABLE agg_bad (
a int2,
b float4
) SERVER file_server
OPTIONS (format 'csv', filename '@abs_srcdir@/data/agg.bad', header 'true', delimiter ';', quote '@', escape '"', null '');
+ ALTER FOREIGN TABLE agg_bad ADD CHECK (a >= 0);
-- per-column options tests
CREATE FOREIGN TABLE text_csv (
word1 text OPTIONS (force_not_null 'true'),
***************
*** 219,224 **** SELECT * FROM agg_csv FOR UPDATE;
--- 221,254 ----
42 | 324.78
(3 rows)
+ -- constraint exclusion tests
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
+ Foreign Scan on public.agg_csv
+ Output: a, b
+ Filter: (agg_csv.a < 0)
+ Foreign File: @abs_srcdir@/data/agg.csv
+
+ \t off
+ SELECT * FROM agg_csv WHERE a < 0;
+ a | b
+ ---+---
+ (0 rows)
+
+ SET constraint_exclusion = 'on';
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
+ Result
+ Output: a, b
+ One-Time Filter: false
+
+ \t off
+ SELECT * FROM agg_csv WHERE a < 0;
+ a | b
+ ---+---
+ (0 rows)
+
+ SET constraint_exclusion = 'partition';
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 2556,2561 **** select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
--- 2556,2646 ----
(13 rows)
-- ===================================================================
+ -- test check constraints
+ -- ===================================================================
+ -- Consistent check constraints provide consistent results
+ ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0);
+ EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 < 0;
+ QUERY PLAN
+ -------------------------------------------------------------------
+ Aggregate
+ Output: count(*)
+ -> Foreign Scan on public.ft1
+ Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE ((c2 < 0))
+ (4 rows)
+
+ SELECT count(*) FROM ft1 WHERE c2 < 0;
+ count
+ -------
+ 0
+ (1 row)
+
+ SET constraint_exclusion = 'on';
+ EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 < 0;
+ QUERY PLAN
+ --------------------------------
+ Aggregate
+ Output: count(*)
+ -> Result
+ One-Time Filter: false
+ (4 rows)
+
+ SELECT count(*) FROM ft1 WHERE c2 < 0;
+ count
+ -------
+ 0
+ (1 row)
+
+ SET constraint_exclusion = 'partition';
+ -- Can throw errors on remote side during update
+ INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive
+ ERROR: new row for relation "T 1" violates check constraint "c2positive"
+ DETAIL: Failing row contains (1111, -2, null, null, null, null, ft1 , null).
+ CONTEXT: Remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
+ UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive
+ ERROR: new row for relation "T 1" violates check constraint "c2positive"
+ DETAIL: Failing row contains (1, -1, 00001_trig_update, 1970-01-02 08:00:00+00, 1970-01-02 00:00:00, 1, 1 , foo).
+ CONTEXT: Remote SQL command: UPDATE "S 1"."T 1" SET c2 = $2 WHERE ctid = $1
+ ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2positive;
+ -- But inconsistent check constraints provide inconsistent results
+ ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2negative CHECK (c2 < 0);
+ EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 >= 0;
+ QUERY PLAN
+ --------------------------------------------------------------------
+ Aggregate
+ Output: count(*)
+ -> Foreign Scan on public.ft1
+ Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE ((c2 >= 0))
+ (4 rows)
+
+ SELECT count(*) FROM ft1 WHERE c2 >= 0;
+ count
+ -------
+ 821
+ (1 row)
+
+ SET constraint_exclusion = 'on';
+ EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 >= 0;
+ QUERY PLAN
+ --------------------------------
+ Aggregate
+ Output: count(*)
+ -> Result
+ One-Time Filter: false
+ (4 rows)
+
+ SELECT count(*) FROM ft1 WHERE c2 >= 0;
+ count
+ -------
+ 0
+ (1 row)
+
+ SET constraint_exclusion = 'partition';
+ -- Can't throw errors on remote side during update
+ INSERT INTO ft1(c1, c2) VALUES(1111, 2);
+ UPDATE ft1 SET c2 = c2 + 1 WHERE c1 = 1;
+ ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2negative;
+ -- ===================================================================
-- test serial columns (ie, sequence-based defaults)
-- ===================================================================
create table loc1 (f1 serial, f2 text);
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 401,406 **** select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
--- 401,436 ----
select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
-- ===================================================================
+ -- test check constraints
+ -- ===================================================================
+
+ -- Consistent check constraints provide consistent results
+ ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0);
+ EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 < 0;
+ SELECT count(*) FROM ft1 WHERE c2 < 0;
+ SET constraint_exclusion = 'on';
+ EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 < 0;
+ SELECT count(*) FROM ft1 WHERE c2 < 0;
+ SET constraint_exclusion = 'partition';
+ -- Can throw errors on remote side during update
+ INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive
+ UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive
+ ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2positive;
+
+ -- But inconsistent check constraints provide inconsistent results
+ ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2negative CHECK (c2 < 0);
+ EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 >= 0;
+ SELECT count(*) FROM ft1 WHERE c2 >= 0;
+ SET constraint_exclusion = 'on';
+ EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 >= 0;
+ SELECT count(*) FROM ft1 WHERE c2 >= 0;
+ SET constraint_exclusion = 'partition';
+ -- Can't throw errors on remote side during update
+ INSERT INTO ft1(c1, c2) VALUES(1111, 2);
+ UPDATE ft1 SET c2 = c2 + 1 WHERE c1 = 1;
+ ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2negative;
+
+ -- ===================================================================
-- test serial columns (ie, sequence-based defaults)
-- ===================================================================
create table loc1 (f1 serial, f2 text);
*** 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 ] <replaceable class="PARAMETER">name</replaceab
--- 42,49 ----
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+ ADD <replaceable class="PARAMETER">table_constraint</replaceable>
+ DROP CONSTRAINT [ IF EXISTS ] <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
DISABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
ENABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
ENABLE REPLICA TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
***************
*** 153,158 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 155,189 ----
</varlistentry>
<varlistentry>
+ <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds a new constraint to a foreign table, using the same syntax as
+ <xref linkend="SQL-CREATEFOREIGNTABLE">.
+ 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.
+ </para>
+
+ <para>
+ Note that constraints on foreign tables cannot be marked
+ <literal>NOT VALID</> since those constraints are simply declarative.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DROP CONSTRAINT [ IF EXISTS ]</literal></term>
+ <listitem>
+ <para>
+ This form drops the specified constraint on a foreign table.
+ If <literal>IF EXISTS</literal> is specified and the constraint
+ does not exist, no error is thrown. In this case a notice is issued instead.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>DISABLE</literal>/<literal>ENABLE [ REPLICA | ALWAYS ] TRIGGER</literal></term>
<listitem>
<para>
***************
*** 285,295 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
</varlistentry>
<varlistentry>
<term><literal>CASCADE</literal></term>
<listitem>
<para>
Automatically drop objects that depend on the dropped column
! (for example, views referencing the column).
</para>
</listitem>
</varlistentry>
--- 316,344 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">table_constraint</replaceable></term>
+ <listitem>
+ <para>
+ New table constraint for the foreign table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">constraint_name</replaceable></term>
+ <listitem>
+ <para>
+ Name of an existing constraint to drop.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>CASCADE</literal></term>
<listitem>
<para>
Automatically drop objects that depend on the dropped column
! or constraint (for example, views referencing the column).
</para>
</listitem>
</varlistentry>
***************
*** 298,304 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
<term><literal>RESTRICT</literal></term>
<listitem>
<para>
! Refuse to drop the column if there are any dependent
objects. This is the default behavior.
</para>
</listitem>
--- 347,353 ----
<term><literal>RESTRICT</literal></term>
<listitem>
<para>
! Refuse to drop the column or constraint if there are any dependent
objects. This is the default behavior.
</para>
</listitem>
***************
*** 365,374 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint is
! added, or a column type is changed with <literal>SET DATA TYPE</>. It is
! the user's responsibility to ensure that the table definition matches the
! remote side.
</para>
<para>
--- 414,423 ----
<para>
Consistency with the foreign server is not checked when a column is added
or removed with <literal>ADD COLUMN</literal> or
! <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> or <literal>CHECK</>
! constraint is added, or a column type is changed with <literal>SET DATA TYPE</>.
! It is the user's responsibility to ensure that the table definition matches
! the remote side.
</para>
<para>
*** a/doc/src/sgml/ref/create_foreign_table.sgml
--- b/doc/src/sgml/ref/create_foreign_table.sgml
***************
*** 19,25 ****
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
[, ... ]
] )
SERVER <replaceable class="parameter">server_name</replaceable>
--- 19,26 ----
<refsynopsisdiv>
<synopsis>
CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
! { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
! | <replaceable>table_constraint</replaceable> }
[, ... ]
] )
SERVER <replaceable class="parameter">server_name</replaceable>
***************
*** 30,36 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 31,43 ----
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ NOT NULL |
NULL |
+ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) |
DEFAULT <replaceable>default_expr</replaceable> }
+
+ <phrase>and <replaceable class="PARAMETER">table_constraint</replaceable> is:</phrase>
+
+ [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
+ { CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) }
</synopsis>
</refsynopsisdiv>
***************
*** 138,143 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 145,176 ----
</varlistentry>
<varlistentry>
+ <term><literal>CHECK ( <replaceable class="PARAMETER">expression</replaceable> )</literal></term>
+ <listitem>
+ <para>
+ The <literal>CHECK</> clause specifies an expression producing a
+ Boolean result which each row in the foreign table must satisfy.
+ 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.
+ </para>
+
+ <para>
+ Currently, <literal>CHECK</literal> expressions cannot contain
+ subqueries nor refer to variables other than columns of the
+ current row. The system column <literal>tableoid</literal>
+ may be referenced, but not any other system column.
+ </para>
+
+ <para>
+ Note that check constraints on foreign tables cannot be marked
+ <literal>NO INHERIT</> since those tables are not allowd to be
+ inherited.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>DEFAULT
<replaceable>default_expr</replaceable></literal></term>
<listitem>
***************
*** 187,192 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 220,236 ----
</refsect1>
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ Constraints on foreign tables are not enforced on insert or update.
+ The definition of a constraint on a foreign table simply declares
+ the constraint holds for all rows in the foreign table. It is
+ the user's responsibility to ensure that the constraint definition
+ matches the remote side.
+ </para>
+ </refsect1>
<refsect1 id="SQL-CREATEFOREIGNTABLE-examples">
<title>Examples</title>
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 2222,2227 **** AddRelationNewConstraints(Relation rel,
--- 2222,2241 ----
if (cdef->contype != CONSTR_CHECK)
continue;
+ /* Don't allow NO INHERIT for foreign tables */
+ if (cdef->is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("CHECK constraints on foreign tables cannot be marked NO INHERIT")));
+
+ /* Don't allow NOT VALID for foreign tables */
+ if (cdef->skip_validation &&
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("CHECK constraints on foreign tables cannot be marked NOT VALID")));
+
if (cdef->raw_expr != NULL)
{
Assert(cdef->cooked_expr == NULL);
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 479,488 **** 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,
--- 479,484 ----
***************
*** 3154,3160 **** 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)
--- 3150,3156 ----
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)
***************
*** 3168,3174 **** 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)
--- 3164,3170 ----
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)
*** 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("unique or primary key 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("unique, primary key, exclusion, or foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
*** a/src/test/regress/expected/foreign_data.out
--- b/src/test/regress/expected/foreign_data.out
***************
*** 669,677 **** ERROR: syntax error at or near "WITH OIDS"
--- 669,711 ----
LINE 1: CREATE FOREIGN TABLE ft1 () SERVER s0 WITH OIDS;
^
CREATE FOREIGN TABLE ft1 (
+ c1 integer OPTIONS ("param 1" 'val1') PRIMARY KEY,
+ c2 text OPTIONS (param2 'val2', param3 'val3'),
+ c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
+ ERROR: unique or primary key constraints are not supported on foreign tables
+ LINE 2: c1 integer OPTIONS ("param 1" 'val1') PRIMARY KEY,
+ ^
+ CREATE TABLE ref_table (id integer PRIMARY KEY);
+ CREATE FOREIGN TABLE ft1 (
+ c1 integer OPTIONS ("param 1" 'val1') REFERENCES ref_table (id),
+ c2 text OPTIONS (param2 'val2', param3 'val3'),
+ c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
+ ERROR: foreign key constraints are not supported on foreign tables
+ LINE 2: c1 integer OPTIONS ("param 1" 'val1') REFERENCES ref_table ...
+ ^
+ DROP TABLE ref_table;
+ CREATE FOREIGN TABLE ft1 (
c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
c2 text OPTIONS (param2 'val2', param3 'val3'),
+ c3 date,
+ UNIQUE (c3)
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
+ ERROR: unique, primary key, exclusion, or foreign key constraints are not supported on foreign tables
+ LINE 5: UNIQUE (c3)
+ ^
+ CREATE FOREIGN TABLE ft1 (
+ c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
+ c2 text OPTIONS (param2 'val2', param3 'val3') CHECK (c2 <> '') NO INHERIT,
c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
+ ERROR: CHECK constraints on foreign tables cannot be marked NO INHERIT
+ CREATE FOREIGN TABLE ft1 (
+ c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
+ c2 text OPTIONS (param2 'val2', param3 'val3') CHECK (c2 <> ''),
+ c3 date,
+ CHECK (c3 BETWEEN '1994-01-01'::date AND '1994-01-31'::date)
) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
COMMENT ON FOREIGN TABLE ft1 IS 'ft1';
COMMENT ON COLUMN ft1.c1 IS 'ft1.c1';
***************
*** 682,687 **** COMMENT ON COLUMN ft1.c1 IS 'ft1.c1';
--- 716,724 ----
c1 | integer | not null | ("param 1" 'val1') | plain | | ft1.c1
c2 | text | | (param2 'val2', param3 'val3') | extended | |
c3 | date | | | plain | |
+ Check constraints:
+ "ft1_c2_check" CHECK (c2 <> ''::text)
+ "ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
Server: s0
FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
***************
*** 740,745 **** ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET STATISTICS -1;
--- 777,785 ----
c8 | text | | (p2 'V2') | extended | |
c9 | integer | | | plain | |
c10 | integer | | (p1 'v1') | plain | |
+ Check constraints:
+ "ft1_c2_check" CHECK (c2 <> ''::text)
+ "ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
Server: s0
FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
***************
*** 748,763 **** 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;
--- 788,809 ----
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 PRIMARY KEY (c7); -- ERROR
! ERROR: unique, primary key, exclusion, or foreign key constraints are not supported on foreign tables
! LINE 1: ALTER FOREIGN TABLE ft1 ADD PRIMARY KEY (c7);
^
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0) NO INHERIT; -- ERROR
! ERROR: CHECK constraints on foreign tables cannot be marked NO INHERIT
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0) NOT VALID; -- ERROR
! ERROR: CHECK constraints on foreign tables cannot be marked NOT VALID
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0);
! ALTER FOREIGN TABLE ft1 ALTER CONSTRAINT ft1_c9_check DEFERRABLE; -- ERROR
ERROR: "ft1" is not a table
+ ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c9_check;
+ 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 SET WITH OIDS; -- ERROR
ERROR: "ft1" is not a table
ALTER FOREIGN TABLE ft1 OWNER TO regress_test_role;
***************
*** 785,790 **** ALTER FOREIGN TABLE foreign_schema.ft1 RENAME TO foreign_table_1;
--- 831,839 ----
c7 | integer | | (p1 'v1', p2 'v2')
c8 | text | | (p2 'V2')
c10 | integer | | (p1 'v1')
+ Check constraints:
+ "ft1_c2_check" CHECK (c2 <> ''::text)
+ "ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
Server: s0
FDW Options: (quote '~', "be quoted" 'value', escape '@')
*** a/src/test/regress/sql/foreign_data.sql
--- b/src/test/regress/sql/foreign_data.sql
***************
*** 269,277 **** CREATE FOREIGN TABLE ft1 (); -- ERROR
--- 269,301 ----
CREATE FOREIGN TABLE ft1 () SERVER no_server; -- ERROR
CREATE FOREIGN TABLE ft1 () SERVER s0 WITH OIDS; -- ERROR
CREATE FOREIGN TABLE ft1 (
+ c1 integer OPTIONS ("param 1" 'val1') PRIMARY KEY,
+ c2 text OPTIONS (param2 'val2', param3 'val3'),
+ c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
+ CREATE TABLE ref_table (id integer PRIMARY KEY);
+ CREATE FOREIGN TABLE ft1 (
+ c1 integer OPTIONS ("param 1" 'val1') REFERENCES ref_table (id),
+ c2 text OPTIONS (param2 'val2', param3 'val3'),
+ c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
+ DROP TABLE ref_table;
+ CREATE FOREIGN TABLE ft1 (
c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
c2 text OPTIONS (param2 'val2', param3 'val3'),
+ c3 date,
+ UNIQUE (c3)
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
+ CREATE FOREIGN TABLE ft1 (
+ c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
+ c2 text OPTIONS (param2 'val2', param3 'val3') CHECK (c2 <> '') NO INHERIT,
c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
+ CREATE FOREIGN TABLE ft1 (
+ c1 integer OPTIONS ("param 1" 'val1') NOT NULL,
+ c2 text OPTIONS (param2 'val2', param3 'val3') CHECK (c2 <> ''),
+ c3 date,
+ CHECK (c3 BETWEEN '1994-01-01'::date AND '1994-01-31'::date)
) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
COMMENT ON FOREIGN TABLE ft1 IS 'ft1';
COMMENT ON COLUMN ft1.c1 IS 'ft1.c1';
***************
*** 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 '@');
--- 338,351 ----
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 PRIMARY KEY (c7); -- ERROR
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0) NO INHERIT; -- ERROR
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0) NOT VALID; -- ERROR
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0);
! ALTER FOREIGN TABLE ft1 ALTER CONSTRAINT ft1_c9_check DEFERRABLE; -- ERROR
! ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c9_check;
ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const; -- ERROR
ALTER FOREIGN TABLE ft1 DROP CONSTRAINT IF EXISTS no_const;
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 '@');
[text/x-patch] fdw-inh-5.patch (97.2K, ../../5487BB9D.7070605@lab.ntt.co.jp/3-fdw-inh-5.patch)
download | inline diff:
*** a/contrib/file_fdw/input/file_fdw.source
--- b/contrib/file_fdw/input/file_fdw.source
***************
*** 148,153 **** EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
--- 148,167 ----
SELECT * FROM agg_csv WHERE a < 0;
SET constraint_exclusion = 'partition';
+ -- table inheritance tests
+ CREATE TABLE agg (a int2, b float4);
+ ALTER FOREIGN TABLE agg_csv INHERIT agg;
+ SELECT tableoid::regclass, * FROM agg;
+ SELECT tableoid::regclass, * FROM agg_csv;
+ SELECT tableoid::regclass, * FROM ONLY agg;
+ -- updates aren't supported
+ UPDATE agg SET a = 1;
+ DELETE FROM agg WHERE a = 100;
+ -- but this should be ignored
+ SELECT tableoid::regclass, * FROM agg FOR UPDATE;
+ ALTER FOREIGN TABLE agg_csv NO INHERIT agg;
+ DROP TABLE agg;
+
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/file_fdw/output/file_fdw.source
--- b/contrib/file_fdw/output/file_fdw.source
***************
*** 249,254 **** SELECT * FROM agg_csv WHERE a < 0;
--- 249,294 ----
(0 rows)
SET constraint_exclusion = 'partition';
+ -- table inheritance tests
+ CREATE TABLE agg (a int2, b float4);
+ ALTER FOREIGN TABLE agg_csv INHERIT agg;
+ SELECT tableoid::regclass, * FROM agg;
+ tableoid | a | b
+ ----------+-----+---------
+ agg_csv | 100 | 99.097
+ agg_csv | 0 | 0.09561
+ agg_csv | 42 | 324.78
+ (3 rows)
+
+ SELECT tableoid::regclass, * FROM agg_csv;
+ tableoid | a | b
+ ----------+-----+---------
+ agg_csv | 100 | 99.097
+ agg_csv | 0 | 0.09561
+ agg_csv | 42 | 324.78
+ (3 rows)
+
+ SELECT tableoid::regclass, * FROM ONLY agg;
+ tableoid | a | b
+ ----------+---+---
+ (0 rows)
+
+ -- updates aren't supported
+ UPDATE agg SET a = 1;
+ ERROR: cannot update foreign table "agg_csv"
+ DELETE FROM agg WHERE a = 100;
+ ERROR: cannot delete from foreign table "agg_csv"
+ -- but this should be ignored
+ SELECT tableoid::regclass, * FROM agg FOR UPDATE;
+ tableoid | a | b
+ ----------+-----+---------
+ agg_csv | 100 | 99.097
+ agg_csv | 0 | 0.09561
+ agg_csv | 42 | 324.78
+ (3 rows)
+
+ ALTER FOREIGN TABLE agg_csv NO INHERIT agg;
+ DROP TABLE agg;
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 2994,2999 **** NOTICE: NEW: (13,"test triggered !")
--- 2994,3511 ----
(1 row)
-- ===================================================================
+ -- test inheritance features
+ -- ===================================================================
+ CREATE TABLE a (aa TEXT);
+ CREATE TABLE loct (aa TEXT, bb TEXT);
+ CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a)
+ SERVER loopback OPTIONS (table_name 'loct');
+ INSERT INTO a(aa) VALUES('aaa');
+ INSERT INTO a(aa) VALUES('aaaa');
+ INSERT INTO a(aa) VALUES('aaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaaa');
+ INSERT INTO b(aa) VALUES('bbb');
+ INSERT INTO b(aa) VALUES('bbbb');
+ INSERT INTO b(aa) VALUES('bbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbbb');
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | aaa
+ a | aaaa
+ a | aaaaa
+ a | aaaaaa
+ a | aaaaaaa
+ a | aaaaaaaa
+ b | bbb
+ b | bbbb
+ b | bbbbb
+ b | bbbbbb
+ b | bbbbbbb
+ b | bbbbbbbb
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----------+----
+ b | bbb |
+ b | bbbb |
+ b | bbbbb |
+ b | bbbbbb |
+ b | bbbbbbb |
+ b | bbbbbbbb |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | aaa
+ a | aaaa
+ a | aaaaa
+ a | aaaaaa
+ a | aaaaaaa
+ a | aaaaaaaa
+ (6 rows)
+
+ UPDATE a SET aa='zzzzzz' WHERE aa LIKE 'aaa%';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ b | bbb
+ b | bbbb
+ b | bbbbb
+ b | bbbbbb
+ b | bbbbbbb
+ b | bbbbbbbb
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----------+----
+ b | bbb |
+ b | bbbb |
+ b | bbbbb |
+ b | bbbbbb |
+ b | bbbbbbb |
+ b | bbbbbbbb |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ (6 rows)
+
+ UPDATE b SET aa='new';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+-----+----
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ (6 rows)
+
+ UPDATE a SET aa='new';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+-----
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+-----+----
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+-----
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ (6 rows)
+
+ DELETE FROM a;
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----
+ (0 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----+----
+ (0 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----
+ (0 rows)
+
+ DROP TABLE a CASCADE;
+ NOTICE: drop cascades to foreign table b
+ DROP TABLE loct;
+ -- Check SELECT FOR UPDATE/SHARE with an inherited source table
+ create table loct1 (f1 int, f2 int, f3 int);
+ create table loct2 (f1 int, f2 int, f3 int);
+ create table foo (f1 int, f2 int);
+ create foreign table foo2 (f3 int) inherits (foo)
+ server loopback options (table_name 'loct1');
+ create table bar (f1 int, f2 int);
+ create foreign table bar2 (f3 int) inherits (bar)
+ server loopback options (table_name 'loct2');
+ insert into foo values(1,1);
+ insert into foo values(3,3);
+ insert into foo2 values(2,2,2);
+ insert into foo2 values(3,3,3);
+ insert into bar values(1,1);
+ insert into bar values(2,2);
+ insert into bar values(3,3);
+ insert into bar values(4,4);
+ insert into bar2 values(1,1,1);
+ insert into bar2 values(2,2,2);
+ insert into bar2 values(3,3,3);
+ insert into bar2 values(4,4,4);
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for update;
+ QUERY PLAN
+ ----------------------------------------------------------------------------------------------
+ LockRows
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ -> Hash Join
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Append
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.ctid, bar2.tableoid, bar2.*
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (22 rows)
+
+ select * from bar where f1 in (select f1 from foo) for update;
+ f1 | f2
+ ----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ (6 rows)
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for share;
+ QUERY PLAN
+ ----------------------------------------------------------------------------------------------
+ LockRows
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ -> Hash Join
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Append
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.ctid, bar2.tableoid, bar2.*
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (22 rows)
+
+ select * from bar where f1 in (select f1 from foo) for share;
+ f1 | f2
+ ----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ (6 rows)
+
+ -- Check UPDATE with inherited target and an inherited source table
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ QUERY PLAN
+ ---------------------------------------------------------------------------------------------
+ Update on public.bar
+ For public.bar2
+ Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
+ -> Hash Join
+ Output: bar.f1, (bar.f2 + 100), bar.ctid, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ -> Hash Join
+ Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar2.f1 = foo.f1)
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (36 rows)
+
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+ relname | f1 | f2
+ ---------+----+-----
+ bar | 1 | 101
+ bar | 2 | 102
+ bar | 3 | 103
+ bar | 4 | 4
+ bar2 | 1 | 101
+ bar2 | 2 | 102
+ bar2 | 3 | 103
+ bar2 | 4 | 4
+ (8 rows)
+
+ -- Check UPDATE with inherited target and an appendrel subquery
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ QUERY PLAN
+ --------------------------------------------------------------------------------------
+ Update on public.bar
+ For public.bar2
+ Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
+ -> Hash Join
+ Output: bar.f1, (bar.f2 + 100), bar.ctid, (ROW(foo.f1))
+ Hash Cond: (foo.f1 = bar.f1)
+ -> Append
+ -> Seq Scan on public.foo
+ Output: ROW(foo.f1), foo.f1
+ -> Foreign Scan on public.foo2
+ Output: ROW(foo2.f1), foo2.f1
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Seq Scan on public.foo foo_1
+ Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3)
+ -> Foreign Scan on public.foo2 foo2_1
+ Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3)
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Hash
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Merge Join
+ Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, (ROW(foo.f1))
+ Merge Cond: (bar2.f1 = foo.f1)
+ -> Sort
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Sort Key: bar2.f1
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Sort
+ Output: (ROW(foo.f1)), foo.f1
+ Sort Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: ROW(foo.f1), foo.f1
+ -> Foreign Scan on public.foo2
+ Output: ROW(foo2.f1), foo2.f1
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Seq Scan on public.foo foo_1
+ Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3)
+ -> Foreign Scan on public.foo2 foo2_1
+ Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3)
+ Remote SQL: SELECT f1 FROM public.loct1
+ (44 rows)
+
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+ relname | f1 | f2
+ ---------+----+-----
+ bar | 1 | 201
+ bar | 2 | 202
+ bar | 3 | 203
+ bar | 4 | 104
+ bar2 | 1 | 201
+ bar2 | 2 | 202
+ bar2 | 3 | 203
+ bar2 | 4 | 104
+ (8 rows)
+
+ drop table foo cascade;
+ NOTICE: drop cascades to foreign table foo2
+ drop table bar cascade;
+ NOTICE: drop cascades to foreign table bar2
+ drop table loct1;
+ drop table loct2;
+ -- Test that WHERE CURRENT OF is not supported
+ create table ltbl (a int, b text);
+ create table ptbl (a int, b text);
+ create table locc () inherits (ptbl);
+ insert into locc values(1, 'foo');
+ create foreign table remc () inherits (ptbl)
+ server loopback options (table_name 'ltbl');
+ insert into remc values(2, 'bar');
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'foo';
+ fetch from c;
+ ?column?
+ ----------
+ 1
+ (1 row)
+
+ update ptbl set b = null where current of c;
+ ERROR: WHERE CURRENT OF is not supported for this table type
+ rollback;
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'bar';
+ fetch from c;
+ ?column?
+ ----------
+ 1
+ (1 row)
+
+ update ptbl set b = null where current of c;
+ ERROR: WHERE CURRENT OF is not supported for this table type
+ rollback;
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ -- Test TRUNCATE
+ truncate ptbl;
+ select * from ptbl;
+ a | b
+ ---+-----
+ 2 | bar
+ (1 row)
+
+ select * from only ptbl;
+ a | b
+ ---+---
+ (0 rows)
+
+ select * from locc;
+ a | b
+ ---+---
+ (0 rows)
+
+ select * from remc;
+ a | b
+ ---+-----
+ 2 | bar
+ (1 row)
+
+ truncate remc; -- ERROR
+ ERROR: "remc" is not a table
+ drop table ptbl cascade;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to table locc
+ drop cascades to foreign table remc
+ drop table ltbl;
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
CREATE SCHEMA import_source;
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 824,834 **** postgresGetForeignPlan(PlannerInfo *root,
{
RowMarkClause *rc = get_parse_rowmark(root->parse, baserel->relid);
if (rc)
{
/*
! * Relation is specified as a FOR UPDATE/SHARE target, so handle
! * that.
*
* For now, just ignore any [NO] KEY specification, since (a) it's
* not clear what that means for a remote table that we don't have
--- 824,847 ----
{
RowMarkClause *rc = get_parse_rowmark(root->parse, baserel->relid);
+ /*
+ * It's possible that relation is contained in an inheritance set and
+ * that parent relation is selected FOR UPDATE/SHARE. If so, get the
+ * RowMarkClause for parent relation.
+ */
+ if (rc == NULL)
+ {
+ PlanRowMark *prm = get_plan_rowmark(root->rowMarks, baserel->relid);
+
+ if (prm && prm->rti != prm->prti)
+ rc = get_parse_rowmark(root->parse, prm->prti);
+ }
+
if (rc)
{
/*
! * Relation or parent relation is specified as a FOR UPDATE/SHARE
! * target, so handle that.
*
* For now, just ignore any [NO] KEY specification, since (a) it's
* not clear what that means for a remote table that we don't have
***************
*** 1680,1690 **** postgresExplainForeignModify(ModifyTableState *mtstate,
--- 1693,1717 ----
int subplan_index,
ExplainState *es)
{
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+
if (es->verbose)
{
char *sql = strVal(list_nth(fdw_private,
FdwModifyPrivateUpdateSql));
+ /*
+ * If we are in an inherited UPDATE/DELETE case, show the SQL statement
+ * in a group of the target relation information.
+ */
+ if (subplan_index > 0)
+ {
+ ExplainOpenForeignModifyGroup(plan, subplan_index, es);
+ ExplainPropertyText("Remote SQL", sql, es);
+ ExplainCloseForeignModifyGroup(plan, subplan_index, es);
+ return;
+ }
+
ExplainPropertyText("Remote SQL", sql, es);
}
}
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 661,666 **** UPDATE rem1 SET f2 = 'testo';
--- 661,817 ----
INSERT INTO rem1(f2) VALUES ('test') RETURNING ctid;
-- ===================================================================
+ -- test inheritance features
+ -- ===================================================================
+
+ CREATE TABLE a (aa TEXT);
+ CREATE TABLE loct (aa TEXT, bb TEXT);
+ CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a)
+ SERVER loopback OPTIONS (table_name 'loct');
+
+ INSERT INTO a(aa) VALUES('aaa');
+ INSERT INTO a(aa) VALUES('aaaa');
+ INSERT INTO a(aa) VALUES('aaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaaa');
+
+ INSERT INTO b(aa) VALUES('bbb');
+ INSERT INTO b(aa) VALUES('bbbb');
+ INSERT INTO b(aa) VALUES('bbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbbb');
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE a SET aa='zzzzzz' WHERE aa LIKE 'aaa%';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE b SET aa='new';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE a SET aa='new';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ DELETE FROM a;
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ DROP TABLE a CASCADE;
+ DROP TABLE loct;
+
+ -- Check SELECT FOR UPDATE/SHARE with an inherited source table
+ create table loct1 (f1 int, f2 int, f3 int);
+ create table loct2 (f1 int, f2 int, f3 int);
+
+ create table foo (f1 int, f2 int);
+ create foreign table foo2 (f3 int) inherits (foo)
+ server loopback options (table_name 'loct1');
+ create table bar (f1 int, f2 int);
+ create foreign table bar2 (f3 int) inherits (bar)
+ server loopback options (table_name 'loct2');
+
+ insert into foo values(1,1);
+ insert into foo values(3,3);
+ insert into foo2 values(2,2,2);
+ insert into foo2 values(3,3,3);
+ insert into bar values(1,1);
+ insert into bar values(2,2);
+ insert into bar values(3,3);
+ insert into bar values(4,4);
+ insert into bar2 values(1,1,1);
+ insert into bar2 values(2,2,2);
+ insert into bar2 values(3,3,3);
+ insert into bar2 values(4,4,4);
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for update;
+ select * from bar where f1 in (select f1 from foo) for update;
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for share;
+ select * from bar where f1 in (select f1 from foo) for share;
+
+ -- Check UPDATE with inherited target and an inherited source table
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+
+ -- Check UPDATE with inherited target and an appendrel subquery
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+
+ drop table foo cascade;
+ drop table bar cascade;
+ drop table loct1;
+ drop table loct2;
+
+ -- Test that WHERE CURRENT OF is not supported
+ create table ltbl (a int, b text);
+
+ create table ptbl (a int, b text);
+ create table locc () inherits (ptbl);
+ insert into locc values(1, 'foo');
+
+ create foreign table remc () inherits (ptbl)
+ server loopback options (table_name 'ltbl');
+ insert into remc values(2, 'bar');
+
+ select * from ptbl;
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'foo';
+ fetch from c;
+ update ptbl set b = null where current of c;
+ rollback;
+
+ select * from ptbl;
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'bar';
+ fetch from c;
+ update ptbl set b = null where current of c;
+ rollback;
+
+ select * from ptbl;
+
+ -- Test TRUNCATE
+ truncate ptbl;
+ select * from ptbl;
+ select * from only ptbl;
+ select * from locc;
+ select * from remc;
+ truncate remc; -- ERROR
+
+ drop table ptbl cascade;
+ drop table ltbl;
+
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
*** a/doc/src/sgml/ddl.sgml
--- b/doc/src/sgml/ddl.sgml
***************
*** 2503,2508 **** VALUES ('Albany', NULL, NULL, 'NY');
--- 2503,2526 ----
further privileges to be granted.
</para>
+ <para>
+ Note that a foreign table can also inherit from more than one parent
+ table (see <xref linkend="ddl-foreign-data"> for an overview of
+ foreign tables).
+ Like normal tables, table inheritance is typically established when
+ the foreign table is created, using the <literal>INHERITS</> clause
+ of the <xref linkend="sql-createforeigntable"> statement.
+ Alternatively, a foreign table which is already defined
+ in a compatible way can have a new parent relationship added, using
+ the <literal>INHERIT</literal> variant of
+ <xref linkend="sql-alterforeigntable">.
+ Similarly an inheritance link can be removed from a child using the
+ <literal>NO INHERIT</literal> variant of <command>ALTER FOREIGN TABLE</>.
+ <command>CREATE FOREIGN TABLE</> and <command>ALTER FOREIGN TABLE</>
+ follow the same rules for duplicate column merging and rejection as
+ <command>CREATE TABLE</> and <command>ALTER TABLE</>, respectively.
+ </para>
+
<sect2 id="ddl-inherit-caveats">
<title>Caveats</title>
***************
*** 2650,2656 **** VALUES ('Albany', NULL, NULL, 'NY');
table of a single parent table. The parent table itself is normally
empty; it exists just to represent the entire data set. You should be
familiar with inheritance (see <xref linkend="ddl-inherit">) before
! attempting to set up partitioning.
</para>
<para>
--- 2668,2677 ----
table of a single parent table. The parent table itself is normally
empty; it exists just to represent the entire data set. You should be
familiar with inheritance (see <xref linkend="ddl-inherit">) before
! attempting to set up partitioning. (The setup and management of
! partitioned tables illustrated in this section assume that each
! partition is a normal table. However, you can do that in a similar way
! for cases where some or all partitions are foreign tables.)
</para>
<para>
***************
*** 2713,2720 **** VALUES ('Albany', NULL, NULL, 'NY');
</para>
<para>
! We will refer to the child tables as partitions, though they
! are in every way normal <productname>PostgreSQL</> tables.
</para>
</listitem>
--- 2734,2741 ----
</para>
<para>
! We will refer to the child tables as partitions, though we assume
! here that they are normal <productname>PostgreSQL</> tables.
</para>
</listitem>
*** a/doc/src/sgml/ref/alter_foreign_table.sgml
--- b/doc/src/sgml/ref/alter_foreign_table.sgml
***************
*** 48,53 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 48,55 ----
ENABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
ENABLE REPLICA TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
ENABLE ALWAYS TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 195,200 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 197,222 ----
</varlistentry>
<varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OWNER</literal></term>
<listitem>
<para>
***************
*** 385,390 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 407,422 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">new_owner</replaceable></term>
<listitem>
<para>
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 1010,1015 **** ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
--- 1010,1028 ----
</para>
<para>
+ A recursive <literal>SET STORAGE</literal> operation will make
+ the storage mode unchanged for any column of descendant tables
+ that are foreign.
+ </para>
+
+ <para>
+ A recursive <literal>SET WITH OIDS</literal> operation will be
+ rejected if any of descendant tables is foreign, since it is
+ not permitted to add an <literal>oid</literal> system column to
+ foreign tables.
+ </para>
+
+ <para>
The <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>,
and <literal>TABLESPACE</> actions never recurse to descendant tables;
that is, they always act as though <literal>ONLY</> were specified.
***************
*** 1018,1023 **** ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
--- 1031,1044 ----
</para>
<para>
+ When adding a <literal>CHECK</> constraint with the <literal>NOT VALID
+ </literal> option recursively, the inherited constraint on a descendant
+ table that is foreign will be marked valid without checking consistency
+ with the foreign server. It is the user's resposibility to ensure that
+ the constraint definition matches the remote side.
+ </para>
+
+ <para>
Changing any part of a system catalog table is not permitted.
</para>
*** a/doc/src/sgml/ref/analyze.sgml
--- b/doc/src/sgml/ref/analyze.sgml
***************
*** 200,205 **** ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
--- 200,212 ----
</para>
<para>
+ The inheritance statistics for a parent table that contains one or more
+ children that are foreign tables are collected only when explicitly
+ selected. If any of the foreign table's wrapper does not support
+ <command>ANALYZE</command>, the command prints a warning and does nothing.
+ </para>
+
+ <para>
If the table being analyzed is completely empty, <command>ANALYZE</command>
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
***************
*** 23,28 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 23,29 ----
| <replaceable>table_constraint</replaceable> }
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 121,126 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 122,146 ----
</varlistentry>
<varlistentry>
+ <term><literal>INHERITS ( <replaceable>parent_table</replaceable> [, ... ] )</literal></term>
+ <listitem>
+ <para>
+ The optional <literal>INHERITS</> clause specifies a list of
+ tables from which the new foreign table automatically inherits
+ all columns. See the similar form of
+ <xref linkend="sql-createtable"> for more details.
+ </para>
+
+ <para>
+ Note that unlike <command>CREATE TABLE</>, column
+ <literal>STORAGE</> settings are not copied from parent tables,
+ resulting in the copied columns in the new foreign table having
+ type-specific default settings.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>NOT NULL</></term>
<listitem>
<para>
***************
*** 192,197 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 212,229 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for
+ more information.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
***************
*** 228,234 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
The definition of a constraint on a foreign table simply declares
the constraint holds for all rows in the foreign table. It is
the user's responsibility to ensure that the constraint definition
! matches the remote side.
</para>
</refsect1>
--- 260,275 ----
The definition of a constraint on a foreign table simply declares
the constraint holds for all rows in the foreign table. It is
the user's responsibility to ensure that the constraint definition
! matches the remote side. Since the constraints are assumed to be
! true, they are used in constraint-based query optimization like
! constraint exclusion for partitioned tables (see
! <xref linkend="ddl-partitioning">).
! </para>
!
! <para>
! Since it is not permitted to add an <literal>oid</> system column to
! foreign tables, the command will be rejected if any of parent tables
! has an <literal>oid</> system column.
</para>
</refsect1>
*** a/doc/src/sgml/ref/truncate.sgml
--- b/doc/src/sgml/ref/truncate.sgml
***************
*** 179,184 **** TRUNCATE [ TABLE ] [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [
--- 179,189 ----
This is similar to the usual behavior of <function>currval()</> after
a failed transaction.
</para>
+
+ <para>
+ A recursive <command>TRUNCATE</> ignores any descendant tables that are
+ foreign, and applies the operation to descendant tables that are normal.
+ </para>
</refsect1>
<refsect1>
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 56,61 ****
--- 56,69 ----
#include "utils/tqual.h"
+ /* Possible modes for ANALYZE */
+ typedef enum
+ {
+ ANL_MODE_ALL, /* Analyze all relations */
+ ANL_MODE_SINGLE, /* Analyze a specific relation */
+ ANL_MODE_AUTOVACUUM /* Autovacuum worker */
+ } AnalyzeMode;
+
/* Data structure for Algorithm S from Knuth 3.4.2 */
typedef struct
{
***************
*** 81,86 **** typedef struct AnlIndexData
--- 89,95 ----
int default_statistics_target = 100;
/* A few variables that don't seem worth passing around as parameters */
+ static AnalyzeMode anl_mode;
static MemoryContext anl_context = NULL;
static BufferAccessStrategy vac_strategy;
***************
*** 102,107 **** static int acquire_sample_rows(Relation onerel, int elevel,
--- 111,117 ----
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
static int compare_rows(const void *a, const void *b);
+ static bool has_foreign_children(Oid parentOID, List *tableOIDs);
static int acquire_inherited_sample_rows(Relation onerel, int elevel,
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
***************
*** 130,135 **** analyze_rel(Oid relid, VacuumStmt *vacstmt,
--- 140,154 ----
elevel = DEBUG2;
/* Set up static variables */
+ if (IsAutoVacuumWorkerProcess())
+ anl_mode = ANL_MODE_AUTOVACUUM;
+ else
+ {
+ if (!vacstmt->relation)
+ anl_mode = ANL_MODE_ALL;
+ else
+ anl_mode = ANL_MODE_SINGLE;
+ }
vac_strategy = bstrategy;
/*
***************
*** 297,305 **** analyze_rel(Oid relid, VacuumStmt *vacstmt,
* do_analyze_rel() -- analyze one relation, recursively or not
*
* Note that "acquirefunc" is only relevant for the non-inherited case.
! * If we supported foreign tables in inheritance trees,
! * acquire_inherited_sample_rows would need to determine the appropriate
! * acquirefunc for each child table.
*/
static void
do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
--- 316,323 ----
* do_analyze_rel() -- analyze one relation, recursively or not
*
* Note that "acquirefunc" is only relevant for the non-inherited case.
! * For the inherited case, acquire_inherited_sample_rows determines the
! * appropriate acquirefunc for each child table.
*/
static void
do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
***************
*** 1444,1454 **** compare_rows(const void *a, const void *b)
/*
* acquire_inherited_sample_rows -- acquire sample rows from inheritance tree
*
* This has the same API as acquire_sample_rows, except that rows are
* collected from all inheritance children as well as the specified table.
! * We fail and return zero if there are no inheritance children.
*/
static int
acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1462,1512 ----
/*
+ * Detect wether the inheritance tree contains any foreign tables
+ */
+ static bool
+ has_foreign_children(Oid parentOID, List *tableOIDs)
+ {
+ bool found;
+ ListCell *lc;
+
+ /* There are no children */
+ if (list_length(tableOIDs) < 2)
+ return false;
+
+ found = false;
+ foreach(lc, tableOIDs)
+ {
+ Oid childOID = lfirst_oid(lc);
+ Relation childrel;
+
+ /* Parent should not be foreign */
+ if (childOID == parentOID)
+ continue;
+
+ /* We already got the needed lock */
+ childrel = heap_open(childOID, NoLock);
+ if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /* Found it */
+ found = true;
+ }
+ heap_close(childrel, NoLock);
+
+ if (found)
+ return true;
+ }
+ return false;
+ }
+
+ /*
* acquire_inherited_sample_rows -- acquire sample rows from inheritance tree
*
* This has the same API as acquire_sample_rows, except that rows are
* collected from all inheritance children as well as the specified table.
! * We fail and return zero if there are no inheritance children, or if
! * inheritance tree contains any foreign tables and we are doing an
! * ANALYZE-with-no-parameter or in an autovacuum process.
*/
static int
acquire_inherited_sample_rows(Relation onerel, int elevel,
***************
*** 1457,1462 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1515,1521 ----
{
List *tableOIDs;
Relation *rels;
+ AcquireSampleRowsFunc *acquirefunc;
double *relblocks;
double totalblocks;
int numrows,
***************
*** 1491,1500 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1550,1580 ----
}
/*
+ * If we are doing an ANALYZE-with-no-parameter or in an autovacuum process
+ * and inheritance tree contains any foreign tables, then fail.
+ */
+ if (anl_mode != ANL_MODE_SINGLE)
+ {
+ if (has_foreign_children(RelationGetRelid(onerel), tableOIDs))
+ {
+ ereport(elevel,
+ (errmsg("skipping analyze of \"%s.%s\" inheritance tree --- this inheritance tree contains foreign tables",
+ get_namespace_name(RelationGetNamespace(onerel)),
+ RelationGetRelationName(onerel)),
+ errhint("Try ANALYZE \"%s.%s\" for the inheritance statistics.",
+ get_namespace_name(RelationGetNamespace(onerel)),
+ RelationGetRelationName(onerel))));
+ return 0;
+ }
+ }
+
+ /*
* Count the blocks in all the relations. The result could overflow
* 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;
***************
*** 1516,1522 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
}
rels[nrels] = childrel;
! relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
totalblocks += relblocks[nrels];
nrels++;
}
--- 1596,1631 ----
}
rels[nrels] = childrel;
!
! if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
! {
! FdwRoutine *fdwroutine;
! BlockNumber relpages = 0;
! bool ok = false;
!
! /* 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++;
}
***************
*** 1534,1539 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1643,1649 ----
{
Relation childrel = rels[i];
double childblocks = relblocks[i];
+ AcquireSampleRowsFunc childacquirefunc = acquirefunc[i];
if (childblocks > 0)
{
***************
*** 1549,1560 **** 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 &&
--- 1659,1670 ----
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/explain.c
--- b/src/backend/commands/explain.c
***************
*** 2230,2259 **** ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
}
/*
* Show extra information for a ModifyTable node
*/
static void
show_modifytable_info(ModifyTableState *mtstate, ExplainState *es)
{
! FdwRoutine *fdwroutine = mtstate->resultRelInfo->ri_FdwRoutine;
! /*
! * If the first target relation is a foreign table, call its FDW to
! * display whatever additional fields it wants to. For now, we ignore the
! * possibility of other targets being foreign tables, although the API for
! * ExplainForeignModify is designed to allow them to be processed.
! */
! if (fdwroutine != NULL &&
! fdwroutine->ExplainForeignModify != NULL)
{
! ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
! List *fdw_private = (List *) linitial(node->fdwPrivLists);
! fdwroutine->ExplainForeignModify(mtstate,
! mtstate->resultRelInfo,
! fdw_private,
! 0,
! es);
}
}
--- 2230,2327 ----
}
/*
+ * Open a group for foreign modification
+ */
+ void
+ ExplainOpenForeignModifyGroup(ModifyTable *plan, int subplan_index,
+ ExplainState *es)
+ {
+ char *objectname;
+ char *namespace;
+ char *refname;
+ Index rti;
+ RangeTblEntry *rte;
+
+ Assert(subplan_index > 0);
+
+ /* Open a group */
+ ExplainOpenGroup("Foreign Modification", "Foreign Modification", true, es);
+
+ /* Show the target relation information */
+ Assert(plan->resultRelations != NIL);
+ rti = list_nth_int(plan->resultRelations, subplan_index);
+ rte = rt_fetch(rti, es->rtable);
+ /* Assert it's on a real relation */
+ Assert(rte->rtekind == RTE_RELATION);
+ objectname = get_rel_name(rte->relid);
+ namespace = get_namespace_name(get_rel_namespace(rte->relid));
+ refname = (char *) list_nth(es->rtable_names, rti - 1);
+
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ appendStringInfoSpaces(es->str, es->indent * 2);
+ appendStringInfoString(es->str, "For");
+ appendStringInfo(es->str, " %s.%s", quote_identifier(namespace),
+ quote_identifier(objectname));
+ if (strcmp(refname, objectname) != 0)
+ appendStringInfo(es->str, " %s", quote_identifier(refname));
+ appendStringInfoChar(es->str, '\n');
+ es->indent++;
+ }
+ else
+ {
+ ExplainPropertyText("Relation Name", objectname, es);
+ ExplainPropertyText("Schema", namespace, es);
+ ExplainPropertyText("Alias", refname, es);
+ }
+ }
+
+ /*
+ * Close a group for foreign modification
+ */
+ void
+ ExplainCloseForeignModifyGroup(ModifyTable *plan, int subplan_index,
+ ExplainState *es)
+ {
+ Assert(subplan_index > 0);
+
+ /* Undo the indentation we added in text format */
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ es->indent--;
+
+ /* Close a group */
+ ExplainCloseGroup("Foreign Modification", "Foreign Modification", true, es);
+ }
+
+ /*
* Show extra information for a ModifyTable node
*/
static void
show_modifytable_info(ModifyTableState *mtstate, ExplainState *es)
{
! ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
! int j;
! for (j = 0; j < mtstate->mt_nplans; j++)
{
! ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
! FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
! /*
! * If the target relation is a foreign table, call its FDW to display
! * whatever additional fields it wants to.
! */
! if (fdwroutine != NULL &&
! fdwroutine->ExplainForeignModify != NULL)
! {
! List *fdw_private = (List *) list_nth(plan->fdwPrivLists, j);
!
! fdwroutine->ExplainForeignModify(mtstate,
! resultRelInfo,
! fdw_private,
! j,
! es);
! }
}
}
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 318,324 **** 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,
--- 318,325 ----
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,
***************
*** 568,573 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
--- 569,598 ----
stmt->relation->relpersistence,
&inheritOids, &old_constraints, &parentOidCount);
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * Don't allow a foreign table to inherit from parents that have OID
+ * system columns.
+ */
+ if (parentOidCount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot inherit from relation with OIDs")));
+
+ /*
+ * Reset the storage parameter for inherited attributes that have
+ * non-default values.
+ */
+ foreach(listptr, schema)
+ {
+ ColumnDef *colDef = lfirst(listptr);
+
+ if (colDef->storage != 0)
+ colDef->storage = 0;
+ }
+ }
+
/*
* Create a tuple descriptor from the relation schema. Note that this
* deals with column names, types, and NOT NULL constraints, but not
***************
*** 1025,1030 **** ExecuteTruncate(TruncateStmt *stmt)
--- 1050,1061 ----
/* find_all_inheritors already got lock */
rel = heap_open(childrelid, NoLock);
+ /* ignore if the rel is a foreign table */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ heap_close(rel, NoLock);
+ continue;
+ }
truncate_check_rel(rel);
rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
***************
*** 3102,3125 **** 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;
--- 3133,3160 ----
* 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;
***************
*** 3132,3138 **** 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;
--- 3167,3174 ----
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;
***************
*** 3254,3264 **** 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;
--- 3290,3306 ----
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);
pass = AT_PASS_MISC;
***************
*** 3292,3298 **** 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 */
case AT_EnableRowSecurity:
--- 3334,3339 ----
***************
*** 4292,4298 **** 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
--- 4333,4340 ----
*/
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
***************
*** 4320,4327 **** 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);
}
}
--- 4362,4373 ----
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);
}
}
***************
*** 4611,4617 **** 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);
--- 4657,4663 ----
/* 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);
***************
*** 4907,4912 **** ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4953,4963 ----
/* 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 */
***************
*** 5507,5513 **** 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
--- 5558,5564 ----
/* 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
***************
*** 5899,5905 **** 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
--- 5950,5956 ----
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/*
* Call AddRelationNewConstraints to do the work, making sure it works on
***************
*** 5910,5918 **** 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 */
--- 5961,5977 ----
* 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 */
***************
*** 7399,7405 **** 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);
--- 7458,7464 ----
/* 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);
***************
*** 7734,7740 **** 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,
--- 7793,7802 ----
* 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/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 2307,2312 **** EvalPlanQualFetchRowMarks(EPQState *epqstate)
--- 2307,2332 ----
Assert(erm->markType == ROW_MARK_COPY);
+ /* if child rel, must check whether it produced this row */
+ if (erm->rti != erm->prti)
+ {
+ Oid tableoid;
+
+ datum = ExecGetJunkAttribute(epqstate->origslot,
+ aerm->toidAttNo,
+ &isNull);
+ /* non-locked rels could be on the inside of outer joins */
+ if (isNull)
+ continue;
+ tableoid = DatumGetObjectId(datum);
+
+ if (tableoid != RelationGetRelid(erm->relation))
+ {
+ /* this child is inactive right now */
+ continue;
+ }
+ }
+
/* fetch the whole-row Var for the relation */
datum = ExecGetJunkAttribute(epqstate->origslot,
aerm->wholeAttNo,
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2024,2029 **** _copyRangeTblEntry(const RangeTblEntry *from)
--- 2024,2030 ----
COPY_NODE_FIELD(eref);
COPY_SCALAR_FIELD(lateral);
COPY_SCALAR_FIELD(inh);
+ COPY_SCALAR_FIELD(hasForeignChildren);
COPY_SCALAR_FIELD(inFromCl);
COPY_SCALAR_FIELD(requiredPerms);
COPY_SCALAR_FIELD(checkAsUser);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 2342,2347 **** _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
--- 2342,2348 ----
COMPARE_NODE_FIELD(eref);
COMPARE_SCALAR_FIELD(lateral);
COMPARE_SCALAR_FIELD(inh);
+ COMPARE_SCALAR_FIELD(hasForeignChildren);
COMPARE_SCALAR_FIELD(inFromCl);
COMPARE_SCALAR_FIELD(requiredPerms);
COMPARE_SCALAR_FIELD(checkAsUser);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 2452,2457 **** _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
--- 2452,2458 ----
WRITE_BOOL_FIELD(lateral);
WRITE_BOOL_FIELD(inh);
+ WRITE_BOOL_FIELD(hasForeignChildren);
WRITE_BOOL_FIELD(inFromCl);
WRITE_UINT_FIELD(requiredPerms);
WRITE_OID_FIELD(checkAsUser);
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 1249,1254 **** _readRangeTblEntry(void)
--- 1249,1255 ----
READ_BOOL_FIELD(lateral);
READ_BOOL_FIELD(inh);
+ READ_BOOL_FIELD(hasForeignChildren);
READ_BOOL_FIELD(inFromCl);
READ_UINT_FIELD(requiredPerms);
READ_OID_FIELD(checkAsUser);
*** a/src/backend/optimizer/prep/preptlist.c
--- b/src/backend/optimizer/prep/preptlist.c
***************
*** 111,116 **** preprocess_targetlist(PlannerInfo *root, List *tlist)
--- 111,118 ----
/* if parent of inheritance tree, need the tableoid too */
if (rc->isParent)
{
+ RangeTblEntry *rte = rt_fetch(rc->rti, parse->rtable);
+
var = makeVar(rc->rti,
TableOidAttributeNumber,
OIDOID,
***************
*** 123,128 **** preprocess_targetlist(PlannerInfo *root, List *tlist)
--- 125,146 ----
pstrdup(resname),
true);
tlist = lappend(tlist, tle);
+
+ /* if containing foreign tables, fetch the whole row too */
+ if (rte->hasForeignChildren)
+ {
+ /* Not a table, so we need the whole row as a junk var */
+ var = makeWholeRowVar(rt_fetch(rc->rti, range_table),
+ rc->rti,
+ 0,
+ false);
+ snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId);
+ tle = makeTargetEntry((Expr *) var,
+ list_length(tlist) + 1,
+ pstrdup(resname),
+ true);
+ tlist = lappend(tlist, tle);
+ }
}
}
else
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 1237,1242 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1237,1243 ----
LOCKMODE lockmode;
List *inhOIDs;
List *appinfos;
+ bool hasForeignChildren;
ListCell *l;
/* Does RT entry allow inheritance? */
***************
*** 1309,1314 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1310,1316 ----
/* Scan the inheritance set and expand it */
appinfos = NIL;
+ hasForeignChildren = false;
foreach(l, inhOIDs)
{
Oid childOID = lfirst_oid(l);
***************
*** 1338,1348 **** 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);
--- 1340,1351 ----
/*
* 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);
***************
*** 1388,1400 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
newrc->rti = childRTindex;
newrc->prti = rti;
newrc->rowmarkId = oldrc->rowmarkId;
! newrc->markType = oldrc->markType;
newrc->waitPolicy = oldrc->waitPolicy;
newrc->isParent = false;
root->rowMarks = lappend(root->rowMarks, newrc);
}
/* Close child relations, but keep locks */
if (childOID != parentOID)
heap_close(newrelation, NoLock);
--- 1391,1409 ----
newrc->rti = childRTindex;
newrc->prti = rti;
newrc->rowmarkId = oldrc->rowmarkId;
! if (childrte->relkind == RELKIND_FOREIGN_TABLE)
! newrc->markType = ROW_MARK_COPY;
! else
! newrc->markType = oldrc->markType;
newrc->waitPolicy = oldrc->waitPolicy;
newrc->isParent = false;
root->rowMarks = lappend(root->rowMarks, newrc);
}
+ if (childrte->relkind == RELKIND_FOREIGN_TABLE)
+ hasForeignChildren = true;
+
/* Close child relations, but keep locks */
if (childOID != parentOID)
heap_close(newrelation, NoLock);
***************
*** 1416,1421 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1425,1433 ----
/* Otherwise, OK to add to root->append_rel_list */
root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+
+ /* And set rte->hasForeignChildren */
+ rte->hasForeignChildren = hasForeignChildren;
}
/*
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 4335,4366 **** 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;
}
;
--- 4335,4366 ----
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/include/commands/explain.h
--- b/src/include/commands/explain.h
***************
*** 91,94 **** extern void ExplainPropertyLong(const char *qlabel, long value,
--- 91,99 ----
extern void ExplainPropertyFloat(const char *qlabel, double value, int ndigits,
ExplainState *es);
+ extern void ExplainOpenForeignModifyGroup(ModifyTable *plan, int subplan_index,
+ ExplainState *es);
+ extern void ExplainCloseForeignModifyGroup(ModifyTable *plan, int subplan_index,
+ ExplainState *es);
+
#endif /* EXPLAIN_H */
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 812,817 **** typedef struct RangeTblEntry
--- 812,819 ----
Alias *eref; /* expanded reference names */
bool lateral; /* subquery, function, or values is LATERAL? */
bool inh; /* inheritance requested? */
+ bool hasForeignChildren; /* does inheritance set include
+ * any foreign tables? */
bool inFromCl; /* present in FROM clause? */
AclMode requiredPerms; /* bitmask of required access permissions */
Oid checkAsUser; /* if valid, check access as this role */
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 850,855 **** typedef enum RowMarkType
--- 850,857 ----
* The tableoid column is only present for an inheritance hierarchy.
* When markType == ROW_MARK_COPY, there is instead a single column named
* wholerow%u whole-row value of relation
+ * The wholerow column is also present for an inheritance hierarchy that
+ * contains foreign tables.
* In all three cases, %u represents the rowmark ID number (rowmarkId).
* This number is unique within a plan tree, except that child relation
* entries copy their parent's rowmarkId. (Assigning unique numbers
*** a/src/test/regress/expected/foreign_data.out
--- b/src/test/regress/expected/foreign_data.out
***************
*** 1242,1247 **** DROP TRIGGER trigtest_before_row ON foreign_schema.foreign_table_1;
--- 1242,1652 ----
DROP TRIGGER trigtest_after_stmt ON foreign_schema.foreign_table_1;
DROP TRIGGER trigtest_after_row ON foreign_schema.foreign_table_1;
DROP FUNCTION dummy_trigger();
+ -- Table inheritance
+ CREATE TABLE pt1 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ );
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ DROP FOREIGN TABLE ft2;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+
+ CREATE FOREIGN TABLE ft2 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+
+ ALTER FOREIGN TABLE ft2 INHERIT pt1;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ CREATE VIEW v1 (c1, c2, c3) AS SELECT 1, 'v1'::text, '1994-01-01'::date;
+ ALTER TABLE v1 INHERIT pt1; -- ERROR
+ ERROR: "v1" is not a table or foreign table
+ DROP VIEW v1;
+ -- add attributes recursively
+ ALTER TABLE pt1 ADD COLUMN c4 integer;
+ ALTER TABLE pt1 ADD COLUMN c5 integer DEFAULT 0;
+ ALTER TABLE pt1 ADD COLUMN c6 integer;
+ ALTER TABLE pt1 ADD COLUMN c7 integer NOT NULL;
+ ALTER TABLE pt1 ADD COLUMN c8 integer;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ c4 | integer | | plain | |
+ c5 | integer | default 0 | plain | |
+ c6 | integer | | plain | |
+ c7 | integer | not null | plain | |
+ c8 | integer | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ c4 | integer | | | plain | |
+ c5 | integer | default 0 | | plain | |
+ c6 | integer | | | plain | |
+ c7 | integer | not null | | plain | |
+ c8 | integer | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- alter attributes recursively
+ ALTER TABLE pt1 ALTER COLUMN c4 SET DEFAULT 0;
+ ALTER TABLE pt1 ALTER COLUMN c5 DROP DEFAULT;
+ ALTER TABLE pt1 ALTER COLUMN c6 SET NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN c7 DROP NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN c8 TYPE char(10) USING '0'; -- ERROR
+ ERROR: "ft2" is not a table
+ ALTER TABLE pt1 ALTER COLUMN c8 TYPE char(10);
+ ALTER TABLE pt1 ALTER COLUMN c8 SET DATA TYPE text;
+ ALTER TABLE pt1 ALTER COLUMN c1 SET STATISTICS 10000;
+ ALTER TABLE pt1 ALTER COLUMN c1 SET (n_distinct = 100);
+ ALTER TABLE pt1 ALTER COLUMN c8 SET STATISTICS -1;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ c4 | integer | default 0 | plain | |
+ c5 | integer | | plain | |
+ c6 | integer | not null | plain | |
+ c7 | integer | | plain | |
+ c8 | text | | extended | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | 10000 |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ c4 | integer | default 0 | | plain | |
+ c5 | integer | | | plain | |
+ c6 | integer | not null | | plain | |
+ c7 | integer | | | plain | |
+ c8 | text | | | extended | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- drop attributes recursively
+ ALTER TABLE pt1 DROP COLUMN c4;
+ ALTER TABLE pt1 DROP COLUMN c5;
+ ALTER TABLE pt1 DROP COLUMN c6;
+ ALTER TABLE pt1 DROP COLUMN c7;
+ ALTER TABLE pt1 DROP COLUMN c8;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | 10000 |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- cannot change storage mode for an attribute of foreign tables
+ ALTER TABLE pt1 ALTER COLUMN c2 SET STORAGE EXTERNAL;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | external | |
+ c3 | date | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | 10000 |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ DROP FOREIGN TABLE ft2;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | external | |
+ c3 | date | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ ALTER TABLE pt1 ALTER COLUMN c2 SET STORAGE EXTENDED;
+ -- add constraints recursively
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk1 CHECK (c1 > 0) NO INHERIT;
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '');
+ -- connoinherit should be true for NO INHERIT constraint
+ SELECT pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pgc.connoinherit FROM pg_class AS pc INNER JOIN pg_constraint AS pgc ON (pgc.conrelid = pc.oid) WHERE pc.relname = 'pt1' order by 1,2;
+ relname | conname | contype | conislocal | coninhcount | connoinherit
+ ---------+---------+---------+------------+-------------+--------------
+ pt1 | pt1chk1 | c | t | 0 | t
+ pt1 | pt1chk2 | c | t | 0 | f
+ (2 rows)
+
+ -- child does not inherit NO INHERIT constraints
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Check constraints:
+ "pt1chk1" CHECK (c1 > 0) NO INHERIT
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ DROP FOREIGN TABLE ft2;
+ CREATE FOREIGN TABLE ft2 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ -- child must have parent's INHERIT constraints
+ ALTER FOREIGN TABLE ft2 INHERIT pt1; -- ERROR
+ ERROR: child table is missing constraint "pt1chk2"
+ ALTER FOREIGN TABLE ft2 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '');
+ ALTER FOREIGN TABLE ft2 INHERIT pt1;
+ -- child does not inherit NO INHERIT constraints
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Check constraints:
+ "pt1chk1" CHECK (c1 > 0) NO INHERIT
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- drop constraints recursively
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk1 CASCADE;
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk2 CASCADE;
+ -- NOT VALID should be ignored for foreign tables
+ INSERT INTO pt1 VALUES (1, 'pt1'::text, '1994-01-01'::date);
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '') NOT VALID;
+ NOTICE: merging constraint "pt1chk2" with inherited definition
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text) NOT VALID
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- VALIDATE CONSTRAINT should work by ignoring foreign tables
+ ALTER TABLE pt1 VALIDATE CONSTRAINT pt1chk2;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- cannot add an OID system column to foreign tables
+ ALTER TABLE pt1 SET WITH OIDS; -- ERROR
+ ERROR: cannot add OID column to foreign table "ft2"
+ DROP FOREIGN TABLE ft2;
+ ALTER TABLE pt1 SET WITH OIDS;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
+ ERROR: cannot inherit from relation with OIDs
+ ALTER TABLE pt1 SET WITHOUT OIDS;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ -- changes name of an attribute recursively
+ ALTER TABLE pt1 RENAME COLUMN c1 TO f1;
+ ALTER TABLE pt1 RENAME COLUMN c2 TO f2;
+ ALTER TABLE pt1 RENAME COLUMN c3 TO f3;
+ -- changes name of a constraint recursively
+ ALTER TABLE pt1 RENAME CONSTRAINT pt1chk2 TO f2_check;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ f1 | integer | not null | plain | 10000 |
+ f2 | text | | extended | |
+ f3 | date | | plain | |
+ Check constraints:
+ "f2_check" CHECK (f2 <> ''::text)
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ f1 | integer | not null | | plain | |
+ f2 | text | | | extended | |
+ f3 | date | | | plain | |
+ Check constraints:
+ "f2_check" CHECK (f2 <> ''::text)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- TRUNCATE should work by ignoring foreign tables
+ TRUNCATE pt1;
+ TRUNCATE ft2; -- ERROR
+ ERROR: "ft2" is not a table
+ DROP TABLE pt1 CASCADE;
+ NOTICE: drop cascades to foreign table ft2
-- IMPORT FOREIGN SCHEMA
IMPORT FOREIGN SCHEMA s1 FROM SERVER s9 INTO public; -- ERROR
ERROR: foreign-data wrapper "foo" has no handler
*** a/src/test/regress/sql/foreign_data.sql
--- b/src/test/regress/sql/foreign_data.sql
***************
*** 542,547 **** DROP TRIGGER trigtest_after_row ON foreign_schema.foreign_table_1;
--- 542,676 ----
DROP FUNCTION dummy_trigger();
+ -- Table inheritance
+ CREATE TABLE pt1 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ );
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ \d+ ft2
+ DROP FOREIGN TABLE ft2;
+ \d+ pt1
+ CREATE FOREIGN TABLE ft2 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ ft2
+ ALTER FOREIGN TABLE ft2 INHERIT pt1;
+ \d+ pt1
+ \d+ ft2
+ CREATE VIEW v1 (c1, c2, c3) AS SELECT 1, 'v1'::text, '1994-01-01'::date;
+ ALTER TABLE v1 INHERIT pt1; -- ERROR
+ DROP VIEW v1;
+
+ -- add attributes recursively
+ ALTER TABLE pt1 ADD COLUMN c4 integer;
+ ALTER TABLE pt1 ADD COLUMN c5 integer DEFAULT 0;
+ ALTER TABLE pt1 ADD COLUMN c6 integer;
+ ALTER TABLE pt1 ADD COLUMN c7 integer NOT NULL;
+ ALTER TABLE pt1 ADD COLUMN c8 integer;
+ \d+ pt1
+ \d+ ft2
+
+ -- alter attributes recursively
+ ALTER TABLE pt1 ALTER COLUMN c4 SET DEFAULT 0;
+ ALTER TABLE pt1 ALTER COLUMN c5 DROP DEFAULT;
+ ALTER TABLE pt1 ALTER COLUMN c6 SET NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN c7 DROP NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN c8 TYPE char(10) USING '0'; -- ERROR
+ ALTER TABLE pt1 ALTER COLUMN c8 TYPE char(10);
+ ALTER TABLE pt1 ALTER COLUMN c8 SET DATA TYPE text;
+ ALTER TABLE pt1 ALTER COLUMN c1 SET STATISTICS 10000;
+ ALTER TABLE pt1 ALTER COLUMN c1 SET (n_distinct = 100);
+ ALTER TABLE pt1 ALTER COLUMN c8 SET STATISTICS -1;
+ \d+ pt1
+ \d+ ft2
+
+ -- drop attributes recursively
+ ALTER TABLE pt1 DROP COLUMN c4;
+ ALTER TABLE pt1 DROP COLUMN c5;
+ ALTER TABLE pt1 DROP COLUMN c6;
+ ALTER TABLE pt1 DROP COLUMN c7;
+ ALTER TABLE pt1 DROP COLUMN c8;
+ \d+ pt1
+ \d+ ft2
+
+ -- cannot change storage mode for an attribute of foreign tables
+ ALTER TABLE pt1 ALTER COLUMN c2 SET STORAGE EXTERNAL;
+ \d+ pt1
+ \d+ ft2
+ DROP FOREIGN TABLE ft2;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ \d+ ft2
+ ALTER TABLE pt1 ALTER COLUMN c2 SET STORAGE EXTENDED;
+
+ -- add constraints recursively
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk1 CHECK (c1 > 0) NO INHERIT;
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '');
+ -- connoinherit should be true for NO INHERIT constraint
+ SELECT pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pgc.connoinherit FROM pg_class AS pc INNER JOIN pg_constraint AS pgc ON (pgc.conrelid = pc.oid) WHERE pc.relname = 'pt1' order by 1,2;
+ -- child does not inherit NO INHERIT constraints
+ \d+ pt1
+ \d+ ft2
+ DROP FOREIGN TABLE ft2;
+ CREATE FOREIGN TABLE ft2 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ -- child must have parent's INHERIT constraints
+ ALTER FOREIGN TABLE ft2 INHERIT pt1; -- ERROR
+ ALTER FOREIGN TABLE ft2 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '');
+ ALTER FOREIGN TABLE ft2 INHERIT pt1;
+ -- child does not inherit NO INHERIT constraints
+ \d+ pt1
+ \d+ ft2
+
+ -- drop constraints recursively
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk1 CASCADE;
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk2 CASCADE;
+
+ -- NOT VALID should be ignored for foreign tables
+ INSERT INTO pt1 VALUES (1, 'pt1'::text, '1994-01-01'::date);
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '') NOT VALID;
+ \d+ pt1
+ \d+ ft2
+ -- VALIDATE CONSTRAINT should work by ignoring foreign tables
+ ALTER TABLE pt1 VALIDATE CONSTRAINT pt1chk2;
+ \d+ pt1
+ \d+ ft2
+
+ -- cannot add an OID system column to foreign tables
+ ALTER TABLE pt1 SET WITH OIDS; -- ERROR
+ DROP FOREIGN TABLE ft2;
+ ALTER TABLE pt1 SET WITH OIDS;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
+ ALTER TABLE pt1 SET WITHOUT OIDS;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+
+ -- changes name of an attribute recursively
+ ALTER TABLE pt1 RENAME COLUMN c1 TO f1;
+ ALTER TABLE pt1 RENAME COLUMN c2 TO f2;
+ ALTER TABLE pt1 RENAME COLUMN c3 TO f3;
+ -- changes name of a constraint recursively
+ ALTER TABLE pt1 RENAME CONSTRAINT pt1chk2 TO f2_check;
+ \d+ pt1
+ \d+ ft2
+
+ -- TRUNCATE should work by ignoring foreign tables
+ TRUNCATE pt1;
+ TRUNCATE ft2; -- ERROR
+
+ DROP TABLE pt1 CASCADE;
+
-- IMPORT FOREIGN SCHEMA
IMPORT FOREIGN SCHEMA s1 FROM SERVER s9 INTO public; -- ERROR
IMPORT FOREIGN SCHEMA s1 LIMIT TO (t1) FROM SERVER s9 INTO public; --ERROR
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-12-10 05:47 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: Ashutosh Bapat @ 2014-12-10 05:47 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
We haven't heard anything from Horiguchi-san and Hanada-san for almost a
week. So, I am fine marking it as "ready for committer". What do you say?
On Wed, Dec 10, 2014 at 8:48 AM, Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
wrote:
> Hi Ashutosh,
>
> Thanks for the review!
>
> (2014/11/28 18:14), Ashutosh Bapat wrote:
>
>> On Thu, Nov 27, 2014 at 3:52 PM, Etsuro Fujita
>> <fujita.etsuro@lab.ntt.co.jp <mailto:fujita.etsuro@lab.ntt.co.jp>> wrote:
>> (2014/11/17 17:55), Ashutosh Bapat wrote:
>> Here are my review comments for patch fdw-inh-3.patch.
>>
>
> Tests
>> -------
>> 1. It seems like you have copied from testcase inherit.sql to
>> postgres_fdw testcase. That's a good thing, but it makes the
>> test quite
>> long. May be we should have two tests in postgres_fdw contrib
>> module,
>> one for simple cases, and other for inheritance. What do you say?
>>
>
> IMO, the test is not so time-consuming, so it doesn't seem worth
>> splitting it into two.
>>
>
> I am not worried about the timing but I am worried about the length of
>> the file and hence ease of debugging in case we find any issues there.
>> We will leave that for the commiter to decide.
>>
>
> OK
>
>
> Documentation
>> --------------------
>> 1. The change in ddl.sgml
>> - We will refer to the child tables as partitions, though
>> they
>> - are in every way normal <productname>PostgreSQL</>
>> tables.
>> + We will refer to the child tables as partitions, though
>> we assume
>> + that they are normal <productname>PostgreSQL</> tables.
>>
>> adds phrase "we assume that", which confuses the intention
>> behind the
>> sentence. The original text is intended to highlight the
>> equivalence
>> between "partition" and "normal table", where as the addition
>> esp. the
>> word "assume" weakens that equivalence. Instead now we have to
>> highlight
>> the equivalence between "partition" and "normal or foreign
>> table". The
>> wording similar to "though they are either normal or foreign
>> tables"
>> should be used there.
>>
>
> You are right, but I feel that there is something out of place in
>> saying that there (5.10.2. Implementing Partitioning) because the
>> procedure there has been written based on normal tables only. Put
>> another way, if we say that, I think we'd need to add more docs,
>> describing the syntax and/or the corresponding examples for
>> foreign-table cases. But I'd like to leave that for another patch.
>> So, how about the wording "we assume *here* that", instead of "we
>> assume that", as I added the following notice in the previous
>> section (5.10.1. Overview)?
>>
>> @@ -2650,7 +2669,10 @@ VALUES ('Albany', NULL, NULL, 'NY');
>> table of a single parent table. The parent table itself is
>> normally
>> empty; it exists just to represent the entire data set. You
>> should be
>> familiar with inheritance (see <xref linkend="ddl-inherit">)
>> before
>> - attempting to set up partitioning.
>> + attempting to set up partitioning. (The setup and management of
>> + partitioned tables illustrated in this section assume that each
>> + partition is a normal table. However, you can do that in a
>> similar way
>> + for cases where some or all partitions are foreign tables.)
>>
>
> This looks ok, though, I would like to see final version of the
>> document. But I think, we will leave that for committer to handle.
>>
>
> OK
>
> 2. The wording "some kind of optimization" gives vague picture.
>> May be
>> it can be worded as "Since the constraints are assumed to be
>> true, they
>> are used in constraint-based query optimization like constraint
>> exclusion for partitioned tables.".
>> + Those constraints are used in some kind of query
>> optimization such
>> + as constraint exclusion for partitioned tables (see
>> + <xref linkend="ddl-partitioning">).
>>
>
> Will follow your revision.
>>
>
> Done.
>
> Code
>> -------
>> 1. In the following change
>> +/*
>> * acquire_inherited_sample_rows -- acquire sample rows from
>> inheritance tree
>> *
>> * This has the same API as acquire_sample_rows, except that
>> rows are
>> * collected from all inheritance children as well as the
>> specified table.
>> - * We fail and return zero if there are no inheritance children.
>> + * We fail and return zero if there are no inheritance children
>> or
>> there are
>> + * inheritance children that foreign tables.
>>
>> The addition should be "there are inheritance children that *are
>> all
>> *foreign tables. Note the addition "are all".
>>
>
> Sorry, I incorrectly wrote the comment. What I tried to write is
>> "We fail and return zero if there are no inheritance children or if
>> we are not in VAC_MODE_SINGLE case and inheritance tree contains at
>> least one foreign table.".
>>
>
> You might want to use "English" description of VAC_MODE_SINGLE instead
>> of that macro in the comment, so that reader doesn't have to look up
>> VAC_MODE_SINGLE. But I think, we will leave this for the committer.
>>
>
> I corrected the comments and translated the macro into the English
> description.
>
> 2. The function has_foreign() be better named
>> has_foreign_child()? This
>>
>
> How about "has_foreign_table"?
>>
>
> has_foreign_child() would be better, since these are "children" in the
>> inheritance hierarchy and not mere "table"s.
>>
>
> Done. But I renamed it to has_foreign_children() because it sounds more
> natural at least to me.
>
> function loops through all the tableoids passed even after it
>> has found
>> a foreign table. Why can't we return true immediately after
>> finding the
>> first foreign table, unless the side effects of heap_open() on
>> all the
>> table are required. But I don't see that to be the case, since
>> these
>> tables are locked already through a previous call to
>> heap_open(). In the
>>
>
> Good catch! Will fix.
>>
>
> same function instead of argument name parentrelId may be we
>> should use
>> name parent_oid, so that we use same notation for parent and
>> child table
>> OIDs.
>>
>
> Will fix.
>>
>
> Done.
>
> 3. Regarding enum VacuumMode - it's being used only in case of
>> acquire_inherited_sample_rows(__) and that too, to check only a
>> single
>> value of the three defined there. May be we should just infer
>> that value
>> inside acquire_inherited_sample_rows(__) or pass a boolean true
>> or false
>> from do_analyse_rel() based on the VacuumStmt. I do not see need
>> for a
>> separate three value enum of which only one value is used and
>> also to
>> pass it down from vacuum() by changing signatures of the minion
>> functions.
>>
>
> I introduced that for possible future use. See the discussion in [1].
>>
>
> Will leave it for the commiter to decide.
>>
>
> I noticed that the signatures need not to be modified, as you said. Thanks
> for pointing that out! So, I revised the patch not to change the
> signatures, though I left the enum, renaming it to AnalyzeMode. Let's have
> the committer's review.
>
> 4. In postgresGetForeignPlan(), the code added by this patch is
>> required
>> to handle the case when the row mark is placed on a parent table
>> and
>> hence is required to be applied on the child table. We need a
>> comment
>> explaining this. Otherwise, the three step process to get the
>> row mark
>> information isn't clear for a reader.
>>
>
> Will add the comment.
>>
>
> Done.
>
> 5. In expand_inherited_rtentry() why do you need a separate
>> variable
>> hasForeign? Instead of using that variable, you can actually
>> set/reset
>> rte->hasForeign since existence of even a single foreign child
>> would
>> mark that member as true. - After typing this, I got the answer
>> when I
>> looked at the function code. Every child's RTE is initially a
>> copy of
>> parent's RTE and hence hasForeign status would be inherited by
>> every
>> child after the first foreign child. I think, this reasoning
>> should be
>> added as comment before assignment to rte->hasForeign at the end
>> of the
>> function.
>>
>
> As you mentioned, I think we could set rte->hasForeign directly
>> during the scan for the inheritance set, without the separate
>> variable, hasForeign. But ISTM that it'd improve code readability
>> to set rte->hasForeign using the separate variable at the end of the
>> function because rte->hasForeign has its meaning only when rte->inh
>> is true and because we know whether rte->inh is true, at the end of
>> the function.
>>
>
> Fine. Please use "hasForeignChild" instead of just "hasForeign" without
>> a clue as to what is "foreign" here.
>>
>
> Done. But I renamed it to "hasForeignChildren".
>
> 6. The tests in foreign_data.sql are pretty extensive. Thanks
>> for that.
>> I think, we should also check the effect of each of the following
>> command using \d on appropriate tables.
>> +CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
>> + SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted"
>> 'value');
>> +ALTER FOREIGN TABLE ft2 NO INHERIT pt1;
>> +DROP FOREIGN TABLE ft2;
>> +CREATE FOREIGN TABLE ft2 (
>> + c1 integer NOT NULL,
>> + c2 text,
>> + c3 date
>> +) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted"
>> 'value');
>> +ALTER FOREIGN TABLE ft2 INHERIT pt1;
>>
>
> Will fix.
>>
>
> Done.
>
> Apart from the above, I noticed that the patch doesn't consider to
>> call ExplainForeignModify during EXPLAIN for an inherited
>> UPDATE/DELETE, as shown below (note that there are no UPDATE remote
>> queries displayed):
>>
>
> Since there seems to be no objections, I updated the patch as proposed
> upthread. Here is an example.
>
> postgres=# explain (format text, verbose) update parent as p set a = a * 2
> returning *;
> QUERY PLAN
> ------------------------------------------------------------
> --------------------
> Update on public.parent p (cost=0.00..202.33 rows=11 width=10)
> Output: p.a
> For public.ft1 p_1
> Remote SQL: UPDATE public.mytable_1 SET a = $2 WHERE ctid = $1
> RETURNING a
> For public.ft2 p_2
> Remote SQL: UPDATE public.mytable_2 SET a = $2 WHERE ctid = $1
> RETURNING a
> -> Seq Scan on public.parent p (cost=0.00..0.00 rows=1 width=10)
> Output: (p.a * 2), p.ctid
> -> Foreign Scan on public.ft1 p_1 (cost=100.00..101.16 rows=5
> width=10)
> Output: (p_1.a * 2), p_1.ctid
> Remote SQL: SELECT a, ctid FROM public.mytable_1 FOR UPDATE
> -> Foreign Scan on public.ft2 p_2 (cost=100.00..101.16 rows=5
> width=10)
> Output: (p_2.a * 2), p_2.ctid
> Remote SQL: SELECT a, ctid FROM public.mytable_2 FOR UPDATE
> (14 rows)
>
> Other changes:
> * revised regression tests for contrib/file_fdw to refer to tableoid.
> * revised docs a bit further.
>
> Attached are updated patches. Patch fdw-inh-5.patch has been created on
> top of patch fdw-chk-5.patch. Patch fdw-chk-5.patch is basically the same
> as the previous one fdw-chk-4.patch, but I slightly modified that one. The
> changes are the following.
> * added to foreign_data.sql more tests for your comments.
> * revised docs on ALTER FOREIGN TABLE a bit further.
>
>
> Thanks,
>
> Best regards,
> Etsuro Fujita
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-12-11 03:09 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-12-11 03:09 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
Hi Ashutosh,
Thanks for the review!
(2014/12/10 14:47), Ashutosh Bapat wrote:
> We haven't heard anything from Horiguchi-san and Hanada-san for almost a
> week. So, I am fine marking it as "ready for committer". What do you say?
ISTM that both of them are not against us, so let's ask for the
committer's review!
Thanks,
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-12-11 05:54 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 2 replies; 179+ messages in thread
From: Ashutosh Bapat @ 2014-12-11 05:54 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
I marked this as ready for committer.
On Thu, Dec 11, 2014 at 8:39 AM, Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
wrote:
> Hi Ashutosh,
>
> Thanks for the review!
>
> (2014/12/10 14:47), Ashutosh Bapat wrote:
>
>> We haven't heard anything from Horiguchi-san and Hanada-san for almost a
>> week. So, I am fine marking it as "ready for committer". What do you say?
>>
>
> ISTM that both of them are not against us, so let's ask for the
> committer's review!
>
>
> Thanks,
>
> Best regards,
> Etsuro Fujita
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-12-11 10:30 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
1 sibling, 0 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-12-11 10:30 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
(2014/12/11 14:54), Ashutosh Bapat wrote:
> I marked this as ready for committer.
Many thanks!
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-12-15 01:35 Michael Paquier <michael.paquier@gmail.com>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
1 sibling, 0 replies; 179+ messages in thread
From: Michael Paquier @ 2014-12-15 01:35 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On Thu, Dec 11, 2014 at 2:54 PM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:
> On Thu, Dec 11, 2014 at 8:39 AM, Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
> wrote:
>>
>> Hi Ashutosh,
>>
>> Thanks for the review!
>>
>> (2014/12/10 14:47), Ashutosh Bapat wrote:
>>>
>>> We haven't heard anything from Horiguchi-san and Hanada-san for almost a
>>> week. So, I am fine marking it as "ready for committer". What do you say?
Moving this patch to CF 2014-12 with the same status. Let's get a
committer having a look at it.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-12-17 22:04 Tom Lane <tgl@sss.pgh.pa.us>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 2 replies; 179+ messages in thread
From: Tom Lane @ 2014-12-17 22:04 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp> writes:
> Attached are updated patches. Patch fdw-inh-5.patch has been created on
> top of patch fdw-chk-5.patch. Patch fdw-chk-5.patch is basically the
> same as the previous one fdw-chk-4.patch, but I slightly modified that
> one. The changes are the following.
> * added to foreign_data.sql more tests for your comments.
> * revised docs on ALTER FOREIGN TABLE a bit further.
I've committed fdw-chk-5.patch with some minor further adjustments;
the most notable one was that I got rid of the error check prohibiting
NO INHERIT, which did not seem to me to have any value. Attaching such
a clause won't have any effect, but so what?
Have not looked at the other patch yet.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-12-18 01:49 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 0 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-12-18 01:49 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
(2014/12/18 7:04), Tom Lane wrote:
> Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp> writes:
>> Attached are updated patches. Patch fdw-inh-5.patch has been created on
>> top of patch fdw-chk-5.patch. Patch fdw-chk-5.patch is basically the
>> same as the previous one fdw-chk-4.patch, but I slightly modified that
>> one. The changes are the following.
>> * added to foreign_data.sql more tests for your comments.
>> * revised docs on ALTER FOREIGN TABLE a bit further.
>
> I've committed fdw-chk-5.patch with some minor further adjustments;
> the most notable one was that I got rid of the error check prohibiting
> NO INHERIT, which did not seem to me to have any value. Attaching such
> a clause won't have any effect, but so what?
>
> Have not looked at the other patch yet.
Thanks!
I added the error check because the other patch, fdw-inh-5.patch,
doesn't allow foreign tables to be inherited and so it seems more
consistent at least to me to do so.
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-12-22 11:20 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2014-12-22 11:20 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On 2014/12/18 7:04, Tom Lane wrote:
> Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp> writes:
>> Attached are updated patches. Patch fdw-inh-5.patch has been created on
>> top of patch fdw-chk-5.patch.
> I've committed fdw-chk-5.patch with some minor further adjustments;
> Have not looked at the other patch yet.
I updated the remaining patch correspondingly to the fix [1]. Please
find attached a patch (the patch has been created on top of the patch in
[1]).
I haven't done anything about the issue that postgresGetForeignPlan() is
using get_parse_rowmark(), discussed in eg, [2]. Tom, will you work on
that?
Thanks,
[1] http://www.postgresql.org/message-id/5497BF4C.6080302@lab.ntt.co.jp
[2] http://www.postgresql.org/message-id/18256.1418401027@sss.pgh.pa.us
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-patch] fdw-inh-6.patch (96.6K, ../../5497FE79.7090804@lab.ntt.co.jp/2-fdw-inh-6.patch)
download | inline diff:
*** a/contrib/file_fdw/input/file_fdw.source
--- b/contrib/file_fdw/input/file_fdw.source
***************
*** 148,153 **** EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
--- 148,167 ----
SELECT * FROM agg_csv WHERE a < 0;
RESET constraint_exclusion;
+ -- table inheritance tests
+ CREATE TABLE agg (a int2, b float4);
+ ALTER FOREIGN TABLE agg_csv INHERIT agg;
+ SELECT tableoid::regclass, * FROM agg;
+ SELECT tableoid::regclass, * FROM agg_csv;
+ SELECT tableoid::regclass, * FROM ONLY agg;
+ -- updates aren't supported
+ UPDATE agg SET a = 1;
+ DELETE FROM agg WHERE a = 100;
+ -- but this should be ignored
+ SELECT tableoid::regclass, * FROM agg FOR UPDATE;
+ ALTER FOREIGN TABLE agg_csv NO INHERIT agg;
+ DROP TABLE agg;
+
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/file_fdw/output/file_fdw.source
--- b/contrib/file_fdw/output/file_fdw.source
***************
*** 249,254 **** SELECT * FROM agg_csv WHERE a < 0;
--- 249,294 ----
(0 rows)
RESET constraint_exclusion;
+ -- table inheritance tests
+ CREATE TABLE agg (a int2, b float4);
+ ALTER FOREIGN TABLE agg_csv INHERIT agg;
+ SELECT tableoid::regclass, * FROM agg;
+ tableoid | a | b
+ ----------+-----+---------
+ agg_csv | 100 | 99.097
+ agg_csv | 0 | 0.09561
+ agg_csv | 42 | 324.78
+ (3 rows)
+
+ SELECT tableoid::regclass, * FROM agg_csv;
+ tableoid | a | b
+ ----------+-----+---------
+ agg_csv | 100 | 99.097
+ agg_csv | 0 | 0.09561
+ agg_csv | 42 | 324.78
+ (3 rows)
+
+ SELECT tableoid::regclass, * FROM ONLY agg;
+ tableoid | a | b
+ ----------+---+---
+ (0 rows)
+
+ -- updates aren't supported
+ UPDATE agg SET a = 1;
+ ERROR: cannot update foreign table "agg_csv"
+ DELETE FROM agg WHERE a = 100;
+ ERROR: cannot delete from foreign table "agg_csv"
+ -- but this should be ignored
+ SELECT tableoid::regclass, * FROM agg FOR UPDATE;
+ tableoid | a | b
+ ----------+-----+---------
+ agg_csv | 100 | 99.097
+ agg_csv | 0 | 0.09561
+ agg_csv | 42 | 324.78
+ (3 rows)
+
+ ALTER FOREIGN TABLE agg_csv NO INHERIT agg;
+ DROP TABLE agg;
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 3027,3032 **** NOTICE: NEW: (13,"test triggered !")
--- 3027,3544 ----
(1 row)
-- ===================================================================
+ -- test inheritance features
+ -- ===================================================================
+ CREATE TABLE a (aa TEXT);
+ CREATE TABLE loct (aa TEXT, bb TEXT);
+ CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a)
+ SERVER loopback OPTIONS (table_name 'loct');
+ INSERT INTO a(aa) VALUES('aaa');
+ INSERT INTO a(aa) VALUES('aaaa');
+ INSERT INTO a(aa) VALUES('aaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaaa');
+ INSERT INTO b(aa) VALUES('bbb');
+ INSERT INTO b(aa) VALUES('bbbb');
+ INSERT INTO b(aa) VALUES('bbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbbb');
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | aaa
+ a | aaaa
+ a | aaaaa
+ a | aaaaaa
+ a | aaaaaaa
+ a | aaaaaaaa
+ b | bbb
+ b | bbbb
+ b | bbbbb
+ b | bbbbbb
+ b | bbbbbbb
+ b | bbbbbbbb
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----------+----
+ b | bbb |
+ b | bbbb |
+ b | bbbbb |
+ b | bbbbbb |
+ b | bbbbbbb |
+ b | bbbbbbbb |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | aaa
+ a | aaaa
+ a | aaaaa
+ a | aaaaaa
+ a | aaaaaaa
+ a | aaaaaaaa
+ (6 rows)
+
+ UPDATE a SET aa='zzzzzz' WHERE aa LIKE 'aaa%';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ b | bbb
+ b | bbbb
+ b | bbbbb
+ b | bbbbbb
+ b | bbbbbbb
+ b | bbbbbbbb
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----------+----
+ b | bbb |
+ b | bbbb |
+ b | bbbbb |
+ b | bbbbbb |
+ b | bbbbbbb |
+ b | bbbbbbbb |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ (6 rows)
+
+ UPDATE b SET aa='new';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+-----+----
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ (6 rows)
+
+ UPDATE a SET aa='new';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+-----
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+-----+----
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+-----
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ (6 rows)
+
+ DELETE FROM a;
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----
+ (0 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----+----
+ (0 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----
+ (0 rows)
+
+ DROP TABLE a CASCADE;
+ NOTICE: drop cascades to foreign table b
+ DROP TABLE loct;
+ -- Check SELECT FOR UPDATE/SHARE with an inherited source table
+ create table loct1 (f1 int, f2 int, f3 int);
+ create table loct2 (f1 int, f2 int, f3 int);
+ create table foo (f1 int, f2 int);
+ create foreign table foo2 (f3 int) inherits (foo)
+ server loopback options (table_name 'loct1');
+ create table bar (f1 int, f2 int);
+ create foreign table bar2 (f3 int) inherits (bar)
+ server loopback options (table_name 'loct2');
+ insert into foo values(1,1);
+ insert into foo values(3,3);
+ insert into foo2 values(2,2,2);
+ insert into foo2 values(3,3,3);
+ insert into bar values(1,1);
+ insert into bar values(2,2);
+ insert into bar values(3,3);
+ insert into bar values(4,4);
+ insert into bar2 values(1,1,1);
+ insert into bar2 values(2,2,2);
+ insert into bar2 values(3,3,3);
+ insert into bar2 values(4,4,4);
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for update;
+ QUERY PLAN
+ ----------------------------------------------------------------------------------------------
+ LockRows
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ -> Hash Join
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Append
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.ctid, bar2.tableoid, bar2.*
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (22 rows)
+
+ select * from bar where f1 in (select f1 from foo) for update;
+ f1 | f2
+ ----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ (6 rows)
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for share;
+ QUERY PLAN
+ ----------------------------------------------------------------------------------------------
+ LockRows
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ -> Hash Join
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Append
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.ctid, bar2.tableoid, bar2.*
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (22 rows)
+
+ select * from bar where f1 in (select f1 from foo) for share;
+ f1 | f2
+ ----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ (6 rows)
+
+ -- Check UPDATE with inherited target and an inherited source table
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ QUERY PLAN
+ ---------------------------------------------------------------------------------------------
+ Update on public.bar
+ For public.bar2
+ Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
+ -> Hash Join
+ Output: bar.f1, (bar.f2 + 100), bar.ctid, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ -> Hash Join
+ Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar2.f1 = foo.f1)
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (36 rows)
+
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+ relname | f1 | f2
+ ---------+----+-----
+ bar | 1 | 101
+ bar | 2 | 102
+ bar | 3 | 103
+ bar | 4 | 4
+ bar2 | 1 | 101
+ bar2 | 2 | 102
+ bar2 | 3 | 103
+ bar2 | 4 | 4
+ (8 rows)
+
+ -- Check UPDATE with inherited target and an appendrel subquery
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ QUERY PLAN
+ --------------------------------------------------------------------------------------
+ Update on public.bar
+ For public.bar2
+ Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
+ -> Hash Join
+ Output: bar.f1, (bar.f2 + 100), bar.ctid, (ROW(foo.f1))
+ Hash Cond: (foo.f1 = bar.f1)
+ -> Append
+ -> Seq Scan on public.foo
+ Output: ROW(foo.f1), foo.f1
+ -> Foreign Scan on public.foo2
+ Output: ROW(foo2.f1), foo2.f1
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Seq Scan on public.foo foo_1
+ Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3)
+ -> Foreign Scan on public.foo2 foo2_1
+ Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3)
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Hash
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Merge Join
+ Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, (ROW(foo.f1))
+ Merge Cond: (bar2.f1 = foo.f1)
+ -> Sort
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Sort Key: bar2.f1
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Sort
+ Output: (ROW(foo.f1)), foo.f1
+ Sort Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: ROW(foo.f1), foo.f1
+ -> Foreign Scan on public.foo2
+ Output: ROW(foo2.f1), foo2.f1
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Seq Scan on public.foo foo_1
+ Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3)
+ -> Foreign Scan on public.foo2 foo2_1
+ Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3)
+ Remote SQL: SELECT f1 FROM public.loct1
+ (44 rows)
+
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+ relname | f1 | f2
+ ---------+----+-----
+ bar | 1 | 201
+ bar | 2 | 202
+ bar | 3 | 203
+ bar | 4 | 104
+ bar2 | 1 | 201
+ bar2 | 2 | 202
+ bar2 | 3 | 203
+ bar2 | 4 | 104
+ (8 rows)
+
+ drop table foo cascade;
+ NOTICE: drop cascades to foreign table foo2
+ drop table bar cascade;
+ NOTICE: drop cascades to foreign table bar2
+ drop table loct1;
+ drop table loct2;
+ -- Test that WHERE CURRENT OF is not supported
+ create table ltbl (a int, b text);
+ create table ptbl (a int, b text);
+ create table locc () inherits (ptbl);
+ insert into locc values(1, 'foo');
+ create foreign table remc () inherits (ptbl)
+ server loopback options (table_name 'ltbl');
+ insert into remc values(2, 'bar');
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'foo';
+ fetch from c;
+ ?column?
+ ----------
+ 1
+ (1 row)
+
+ update ptbl set b = null where current of c;
+ ERROR: WHERE CURRENT OF is not supported for this table type
+ rollback;
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'bar';
+ fetch from c;
+ ?column?
+ ----------
+ 1
+ (1 row)
+
+ update ptbl set b = null where current of c;
+ ERROR: WHERE CURRENT OF is not supported for this table type
+ rollback;
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ -- Test TRUNCATE
+ truncate ptbl;
+ select * from ptbl;
+ a | b
+ ---+-----
+ 2 | bar
+ (1 row)
+
+ select * from only ptbl;
+ a | b
+ ---+---
+ (0 rows)
+
+ select * from locc;
+ a | b
+ ---+---
+ (0 rows)
+
+ select * from remc;
+ a | b
+ ---+-----
+ 2 | bar
+ (1 row)
+
+ truncate remc; -- ERROR
+ ERROR: "remc" is not a table
+ drop table ptbl cascade;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to table locc
+ drop cascades to foreign table remc
+ drop table ltbl;
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
CREATE SCHEMA import_source;
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 824,834 **** postgresGetForeignPlan(PlannerInfo *root,
{
RowMarkClause *rc = get_parse_rowmark(root->parse, baserel->relid);
if (rc)
{
/*
! * Relation is specified as a FOR UPDATE/SHARE target, so handle
! * that.
*
* For now, just ignore any [NO] KEY specification, since (a) it's
* not clear what that means for a remote table that we don't have
--- 824,847 ----
{
RowMarkClause *rc = get_parse_rowmark(root->parse, baserel->relid);
+ /*
+ * It's possible that relation is contained in an inheritance set and
+ * that parent relation is selected FOR UPDATE/SHARE. If so, get the
+ * RowMarkClause for parent relation.
+ */
+ if (rc == NULL)
+ {
+ PlanRowMark *prm = get_plan_rowmark(root->rowMarks, baserel->relid);
+
+ if (prm && prm->rti != prm->prti)
+ rc = get_parse_rowmark(root->parse, prm->prti);
+ }
+
if (rc)
{
/*
! * Relation or parent relation is specified as a FOR UPDATE/SHARE
! * target, so handle that.
*
* For now, just ignore any [NO] KEY specification, since (a) it's
* not clear what that means for a remote table that we don't have
***************
*** 1682,1690 **** postgresExplainForeignModify(ModifyTableState *mtstate,
--- 1695,1722 ----
{
if (es->verbose)
{
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ Index rti = list_nth_int(plan->resultRelations, subplan_index);
char *sql = strVal(list_nth(fdw_private,
FdwModifyPrivateUpdateSql));
+ /*
+ * If we are in an inherited UPDATE/DELETE case, show the SQL statement
+ * together with the target relation. See
+ * ExplainOpenForeignModifyGroup/ExplainCloseForeignModifyGroup.
+ *
+ * Note: a foreign table shouldn't be the parent of an inheritance set,
+ * so if the RT index is different from plan->resultRelation, it means
+ * that we are in an inherited UPDATE/DELETE case.
+ */
+ if (rti != plan->resultRelation)
+ {
+ ExplainOpenForeignModifyGroup(plan, subplan_index, es);
+ ExplainPropertyText("Remote SQL", sql, es);
+ ExplainCloseForeignModifyGroup(plan, subplan_index, es);
+ return;
+ }
+
ExplainPropertyText("Remote SQL", sql, es);
}
}
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 666,671 **** UPDATE rem1 SET f2 = 'testo';
--- 666,822 ----
INSERT INTO rem1(f2) VALUES ('test') RETURNING ctid;
-- ===================================================================
+ -- test inheritance features
+ -- ===================================================================
+
+ CREATE TABLE a (aa TEXT);
+ CREATE TABLE loct (aa TEXT, bb TEXT);
+ CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a)
+ SERVER loopback OPTIONS (table_name 'loct');
+
+ INSERT INTO a(aa) VALUES('aaa');
+ INSERT INTO a(aa) VALUES('aaaa');
+ INSERT INTO a(aa) VALUES('aaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaaa');
+
+ INSERT INTO b(aa) VALUES('bbb');
+ INSERT INTO b(aa) VALUES('bbbb');
+ INSERT INTO b(aa) VALUES('bbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbbb');
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE a SET aa='zzzzzz' WHERE aa LIKE 'aaa%';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE b SET aa='new';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE a SET aa='new';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ DELETE FROM a;
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ DROP TABLE a CASCADE;
+ DROP TABLE loct;
+
+ -- Check SELECT FOR UPDATE/SHARE with an inherited source table
+ create table loct1 (f1 int, f2 int, f3 int);
+ create table loct2 (f1 int, f2 int, f3 int);
+
+ create table foo (f1 int, f2 int);
+ create foreign table foo2 (f3 int) inherits (foo)
+ server loopback options (table_name 'loct1');
+ create table bar (f1 int, f2 int);
+ create foreign table bar2 (f3 int) inherits (bar)
+ server loopback options (table_name 'loct2');
+
+ insert into foo values(1,1);
+ insert into foo values(3,3);
+ insert into foo2 values(2,2,2);
+ insert into foo2 values(3,3,3);
+ insert into bar values(1,1);
+ insert into bar values(2,2);
+ insert into bar values(3,3);
+ insert into bar values(4,4);
+ insert into bar2 values(1,1,1);
+ insert into bar2 values(2,2,2);
+ insert into bar2 values(3,3,3);
+ insert into bar2 values(4,4,4);
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for update;
+ select * from bar where f1 in (select f1 from foo) for update;
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for share;
+ select * from bar where f1 in (select f1 from foo) for share;
+
+ -- Check UPDATE with inherited target and an inherited source table
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+
+ -- Check UPDATE with inherited target and an appendrel subquery
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+
+ drop table foo cascade;
+ drop table bar cascade;
+ drop table loct1;
+ drop table loct2;
+
+ -- Test that WHERE CURRENT OF is not supported
+ create table ltbl (a int, b text);
+
+ create table ptbl (a int, b text);
+ create table locc () inherits (ptbl);
+ insert into locc values(1, 'foo');
+
+ create foreign table remc () inherits (ptbl)
+ server loopback options (table_name 'ltbl');
+ insert into remc values(2, 'bar');
+
+ select * from ptbl;
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'foo';
+ fetch from c;
+ update ptbl set b = null where current of c;
+ rollback;
+
+ select * from ptbl;
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'bar';
+ fetch from c;
+ update ptbl set b = null where current of c;
+ rollback;
+
+ select * from ptbl;
+
+ -- Test TRUNCATE
+ truncate ptbl;
+ select * from ptbl;
+ select * from only ptbl;
+ select * from locc;
+ select * from remc;
+ truncate remc; -- ERROR
+
+ drop table ptbl cascade;
+ drop table ltbl;
+
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
*** a/doc/src/sgml/ddl.sgml
--- b/doc/src/sgml/ddl.sgml
***************
*** 2503,2508 **** VALUES ('Albany', NULL, NULL, 'NY');
--- 2503,2526 ----
further privileges to be granted.
</para>
+ <para>
+ Note that a foreign table can also inherit from more than one parent
+ table (see <xref linkend="ddl-foreign-data"> for an overview of
+ foreign tables).
+ Like normal tables, table inheritance is typically established when
+ the foreign table is created, using the <literal>INHERITS</> clause
+ of the <xref linkend="sql-createforeigntable"> statement.
+ Alternatively, a foreign table which is already defined
+ in a compatible way can have a new parent relationship added, using
+ the <literal>INHERIT</literal> variant of
+ <xref linkend="sql-alterforeigntable">.
+ Similarly an inheritance link can be removed from a child using the
+ <literal>NO INHERIT</literal> variant of <command>ALTER FOREIGN TABLE</>.
+ <command>CREATE FOREIGN TABLE</> and <command>ALTER FOREIGN TABLE</>
+ follow the same rules for duplicate column merging and rejection as
+ <command>CREATE TABLE</> and <command>ALTER TABLE</>, respectively.
+ </para>
+
<sect2 id="ddl-inherit-caveats">
<title>Caveats</title>
***************
*** 2650,2656 **** VALUES ('Albany', NULL, NULL, 'NY');
table of a single parent table. The parent table itself is normally
empty; it exists just to represent the entire data set. You should be
familiar with inheritance (see <xref linkend="ddl-inherit">) before
! attempting to set up partitioning.
</para>
<para>
--- 2668,2677 ----
table of a single parent table. The parent table itself is normally
empty; it exists just to represent the entire data set. You should be
familiar with inheritance (see <xref linkend="ddl-inherit">) before
! attempting to set up partitioning. (The setup and management of
! partitioned tables illustrated in this section assume that each
! partition is a normal table. However, you can do that in a similar way
! for cases where some or all partitions are foreign tables.)
</para>
<para>
***************
*** 2713,2720 **** VALUES ('Albany', NULL, NULL, 'NY');
</para>
<para>
! We will refer to the child tables as partitions, though they
! are in every way normal <productname>PostgreSQL</> tables.
</para>
</listitem>
--- 2734,2741 ----
</para>
<para>
! We will refer to the child tables as partitions, though we assume
! here that they are normal <productname>PostgreSQL</> tables.
</para>
</listitem>
*** a/doc/src/sgml/ref/alter_foreign_table.sgml
--- b/doc/src/sgml/ref/alter_foreign_table.sgml
***************
*** 48,53 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 48,55 ----
ENABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
ENABLE REPLICA TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
ENABLE ALWAYS TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 188,193 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 190,215 ----
</varlistentry>
<varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OWNER</literal></term>
<listitem>
<para>
***************
*** 384,389 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 406,421 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">new_owner</replaceable></term>
<listitem>
<para>
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 1010,1015 **** ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
--- 1010,1028 ----
</para>
<para>
+ A recursive <literal>SET STORAGE</literal> operation will make
+ the storage mode unchanged for any column of descendant tables
+ that are foreign.
+ </para>
+
+ <para>
+ A recursive <literal>SET WITH OIDS</literal> operation will be
+ rejected if any of descendant tables is foreign, since it is
+ not permitted to add an <literal>oid</literal> system column to
+ foreign tables.
+ </para>
+
+ <para>
The <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>,
and <literal>TABLESPACE</> actions never recurse to descendant tables;
that is, they always act as though <literal>ONLY</> were specified.
***************
*** 1018,1023 **** ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
--- 1031,1044 ----
</para>
<para>
+ When adding a <literal>CHECK</> constraint with the <literal>NOT VALID
+ </literal> option recursively, the inherited constraint on a descendant
+ table that is foreign will be marked valid without checking consistency
+ with the foreign server. It is the user's resposibility to ensure that
+ the constraint definition matches the remote side.
+ </para>
+
+ <para>
Changing any part of a system catalog table is not permitted.
</para>
*** a/doc/src/sgml/ref/analyze.sgml
--- b/doc/src/sgml/ref/analyze.sgml
***************
*** 200,205 **** ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
--- 200,212 ----
</para>
<para>
+ The inheritance statistics for a parent table that contains one or more
+ children that are foreign tables are collected only when explicitly
+ selected. If any of the foreign table's wrapper does not support
+ <command>ANALYZE</command>, the command prints a warning and does nothing.
+ </para>
+
+ <para>
If the table being analyzed is completely empty, <command>ANALYZE</command>
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
***************
*** 23,28 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 23,29 ----
| <replaceable>table_constraint</replaceable> }
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 121,126 **** CHECK ( <replaceable class="PARAMETER">expression</replaceable> )
--- 122,158 ----
</varlistentry>
<varlistentry>
+ <term><literal>INHERITS ( <replaceable>parent_table</replaceable> [, ... ] )</literal></term>
+ <listitem>
+ <para>
+ The optional <literal>INHERITS</> clause specifies a list of
+ tables from which the new foreign table automatically inherits
+ all columns. See the similar form of
+ <xref linkend="sql-createtable"> for more details.
+ </para>
+
+ <para>
+ Note that unlike <command>CREATE TABLE</>, column
+ <literal>STORAGE</> settings are not copied from parent tables,
+ resulting in the copied columns in the new foreign table having
+ type-specific default settings.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for
+ more information.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>NOT NULL</></term>
<listitem>
<para>
***************
*** 249,254 **** CHECK ( <replaceable class="PARAMETER">expression</replaceable> )
--- 281,292 ----
responsibility to ensure that the constraint definition matches
reality.
</para>
+
+ <para>
+ Since it is not permitted to add an <literal>oid</> system column to
+ foreign tables, the command will be rejected if any of parent tables
+ has an <literal>oid</> system column.
+ </para>
</refsect1>
<refsect1 id="SQL-CREATEFOREIGNTABLE-examples">
*** a/doc/src/sgml/ref/truncate.sgml
--- b/doc/src/sgml/ref/truncate.sgml
***************
*** 179,184 **** TRUNCATE [ TABLE ] [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [
--- 179,189 ----
This is similar to the usual behavior of <function>currval()</> after
a failed transaction.
</para>
+
+ <para>
+ A recursive <command>TRUNCATE</> ignores any descendant tables that are
+ foreign, and applies the operation to descendant tables that are normal.
+ </para>
</refsect1>
<refsect1>
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 56,61 ****
--- 56,69 ----
#include "utils/tqual.h"
+ /* Possible modes for ANALYZE */
+ typedef enum
+ {
+ ANL_MODE_ALL, /* Analyze all relations */
+ ANL_MODE_SINGLE, /* Analyze a specific relation */
+ ANL_MODE_AUTOVACUUM /* Autovacuum worker */
+ } AnalyzeMode;
+
/* Data structure for Algorithm S from Knuth 3.4.2 */
typedef struct
{
***************
*** 81,86 **** typedef struct AnlIndexData
--- 89,95 ----
int default_statistics_target = 100;
/* A few variables that don't seem worth passing around as parameters */
+ static AnalyzeMode anl_mode;
static MemoryContext anl_context = NULL;
static BufferAccessStrategy vac_strategy;
***************
*** 102,107 **** static int acquire_sample_rows(Relation onerel, int elevel,
--- 111,117 ----
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
static int compare_rows(const void *a, const void *b);
+ static bool has_foreign_children(Oid parentOID, List *tableOIDs);
static int acquire_inherited_sample_rows(Relation onerel, int elevel,
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
***************
*** 130,135 **** analyze_rel(Oid relid, VacuumStmt *vacstmt,
--- 140,154 ----
elevel = DEBUG2;
/* Set up static variables */
+ if (IsAutoVacuumWorkerProcess())
+ anl_mode = ANL_MODE_AUTOVACUUM;
+ else
+ {
+ if (!vacstmt->relation)
+ anl_mode = ANL_MODE_ALL;
+ else
+ anl_mode = ANL_MODE_SINGLE;
+ }
vac_strategy = bstrategy;
/*
***************
*** 297,305 **** analyze_rel(Oid relid, VacuumStmt *vacstmt,
* do_analyze_rel() -- analyze one relation, recursively or not
*
* Note that "acquirefunc" is only relevant for the non-inherited case.
! * If we supported foreign tables in inheritance trees,
! * acquire_inherited_sample_rows would need to determine the appropriate
! * acquirefunc for each child table.
*/
static void
do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
--- 316,323 ----
* do_analyze_rel() -- analyze one relation, recursively or not
*
* Note that "acquirefunc" is only relevant for the non-inherited case.
! * For the inherited case, acquire_inherited_sample_rows determines the
! * appropriate acquirefunc for each child table.
*/
static void
do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
***************
*** 1444,1454 **** compare_rows(const void *a, const void *b)
/*
* acquire_inherited_sample_rows -- acquire sample rows from inheritance tree
*
* This has the same API as acquire_sample_rows, except that rows are
* collected from all inheritance children as well as the specified table.
! * We fail and return zero if there are no inheritance children.
*/
static int
acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1462,1512 ----
/*
+ * Detect wether the inheritance tree contains any foreign tables
+ */
+ static bool
+ has_foreign_children(Oid parentOID, List *tableOIDs)
+ {
+ bool found;
+ ListCell *lc;
+
+ /* There are no children */
+ if (list_length(tableOIDs) < 2)
+ return false;
+
+ found = false;
+ foreach(lc, tableOIDs)
+ {
+ Oid childOID = lfirst_oid(lc);
+ Relation childrel;
+
+ /* Parent should not be foreign */
+ if (childOID == parentOID)
+ continue;
+
+ /* We already got the needed lock */
+ childrel = heap_open(childOID, NoLock);
+ if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /* Found it */
+ found = true;
+ }
+ heap_close(childrel, NoLock);
+
+ if (found)
+ return true;
+ }
+ return false;
+ }
+
+ /*
* acquire_inherited_sample_rows -- acquire sample rows from inheritance tree
*
* This has the same API as acquire_sample_rows, except that rows are
* collected from all inheritance children as well as the specified table.
! * We fail and return zero if there are no inheritance children, or if
! * inheritance tree contains any foreign tables and we are doing an
! * ANALYZE-with-no-parameter or in an autovacuum process.
*/
static int
acquire_inherited_sample_rows(Relation onerel, int elevel,
***************
*** 1457,1462 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1515,1521 ----
{
List *tableOIDs;
Relation *rels;
+ AcquireSampleRowsFunc *acquirefunc;
double *relblocks;
double totalblocks;
int numrows,
***************
*** 1491,1500 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1550,1580 ----
}
/*
+ * If we are doing an ANALYZE-with-no-parameter or in an autovacuum process
+ * and inheritance tree contains any foreign tables, then fail.
+ */
+ if (anl_mode != ANL_MODE_SINGLE)
+ {
+ if (has_foreign_children(RelationGetRelid(onerel), tableOIDs))
+ {
+ ereport(elevel,
+ (errmsg("skipping analyze of \"%s.%s\" inheritance tree --- this inheritance tree contains foreign tables",
+ get_namespace_name(RelationGetNamespace(onerel)),
+ RelationGetRelationName(onerel)),
+ errhint("Try ANALYZE \"%s.%s\" for the inheritance statistics.",
+ get_namespace_name(RelationGetNamespace(onerel)),
+ RelationGetRelationName(onerel))));
+ return 0;
+ }
+ }
+
+ /*
* Count the blocks in all the relations. The result could overflow
* 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;
***************
*** 1516,1522 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
}
rels[nrels] = childrel;
! relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
totalblocks += relblocks[nrels];
nrels++;
}
--- 1596,1631 ----
}
rels[nrels] = childrel;
!
! if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
! {
! FdwRoutine *fdwroutine;
! BlockNumber relpages = 0;
! bool ok = false;
!
! /* 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++;
}
***************
*** 1534,1539 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1643,1649 ----
{
Relation childrel = rels[i];
double childblocks = relblocks[i];
+ AcquireSampleRowsFunc childacquirefunc = acquirefunc[i];
if (childblocks > 0)
{
***************
*** 1549,1560 **** 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 &&
--- 1659,1670 ----
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/explain.c
--- b/src/backend/commands/explain.c
***************
*** 2221,2250 **** ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
}
/*
* Show extra information for a ModifyTable node
*/
static void
show_modifytable_info(ModifyTableState *mtstate, ExplainState *es)
{
! FdwRoutine *fdwroutine = mtstate->resultRelInfo->ri_FdwRoutine;
! /*
! * If the first target relation is a foreign table, call its FDW to
! * display whatever additional fields it wants to. For now, we ignore the
! * possibility of other targets being foreign tables, although the API for
! * ExplainForeignModify is designed to allow them to be processed.
! */
! if (fdwroutine != NULL &&
! fdwroutine->ExplainForeignModify != NULL)
{
! ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
! List *fdw_private = (List *) linitial(node->fdwPrivLists);
! fdwroutine->ExplainForeignModify(mtstate,
! mtstate->resultRelInfo,
! fdw_private,
! 0,
! es);
}
}
--- 2221,2318 ----
}
/*
+ * Open a group for foreign modification
+ */
+ void
+ ExplainOpenForeignModifyGroup(ModifyTable *plan, int subplan_index,
+ ExplainState *es)
+ {
+ char *objectname;
+ char *namespace;
+ char *refname;
+ Index rti;
+ RangeTblEntry *rte;
+
+ Assert(subplan_index > 0);
+
+ /* Open a group */
+ ExplainOpenGroup("Foreign Modification", "Foreign Modification", true, es);
+
+ /* Show the target relation */
+ Assert(plan->resultRelations != NIL);
+ rti = list_nth_int(plan->resultRelations, subplan_index);
+ rte = rt_fetch(rti, es->rtable);
+ /* Assert it's on a real relation */
+ Assert(rte->rtekind == RTE_RELATION);
+ objectname = get_rel_name(rte->relid);
+ namespace = get_namespace_name(get_rel_namespace(rte->relid));
+ refname = (char *) list_nth(es->rtable_names, rti - 1);
+
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ appendStringInfoSpaces(es->str, es->indent * 2);
+ appendStringInfoString(es->str, "For");
+ appendStringInfo(es->str, " %s.%s", quote_identifier(namespace),
+ quote_identifier(objectname));
+ if (strcmp(refname, objectname) != 0)
+ appendStringInfo(es->str, " %s", quote_identifier(refname));
+ appendStringInfoChar(es->str, '\n');
+ es->indent++;
+ }
+ else
+ {
+ ExplainPropertyText("Relation Name", objectname, es);
+ ExplainPropertyText("Schema", namespace, es);
+ ExplainPropertyText("Alias", refname, es);
+ }
+ }
+
+ /*
+ * Close a group for foreign modification
+ */
+ void
+ ExplainCloseForeignModifyGroup(ModifyTable *plan, int subplan_index,
+ ExplainState *es)
+ {
+ Assert(subplan_index > 0);
+
+ /* Undo the indentation we added in text format */
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ es->indent--;
+
+ /* Close a group */
+ ExplainCloseGroup("Foreign Modification", "Foreign Modification", true, es);
+ }
+
+ /*
* Show extra information for a ModifyTable node
*/
static void
show_modifytable_info(ModifyTableState *mtstate, ExplainState *es)
{
! ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
! int j;
! for (j = 0; j < mtstate->mt_nplans; j++)
{
! ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
! FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
! /*
! * If the target relation is a foreign table, call its FDW to display
! * whatever additional fields it wants to.
! */
! if (fdwroutine != NULL &&
! fdwroutine->ExplainForeignModify != NULL)
! {
! List *fdw_private = (List *) list_nth(plan->fdwPrivLists, j);
!
! fdwroutine->ExplainForeignModify(mtstate,
! resultRelInfo,
! fdw_private,
! j,
! es);
! }
}
}
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 318,324 **** 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,
--- 318,325 ----
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,
***************
*** 568,573 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
--- 569,598 ----
stmt->relation->relpersistence,
&inheritOids, &old_constraints, &parentOidCount);
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * Don't allow a foreign table to inherit from parents that have OID
+ * system columns.
+ */
+ if (parentOidCount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot inherit from relation with OIDs")));
+
+ /*
+ * Reset the storage parameter for inherited attributes that have
+ * non-default values.
+ */
+ foreach(listptr, schema)
+ {
+ ColumnDef *colDef = lfirst(listptr);
+
+ if (colDef->storage != 0)
+ colDef->storage = 0;
+ }
+ }
+
/*
* Create a tuple descriptor from the relation schema. Note that this
* deals with column names, types, and NOT NULL constraints, but not
***************
*** 1025,1030 **** ExecuteTruncate(TruncateStmt *stmt)
--- 1050,1061 ----
/* find_all_inheritors already got lock */
rel = heap_open(childrelid, NoLock);
+ /* ignore if the rel is a foreign table */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ heap_close(rel, NoLock);
+ continue;
+ }
truncate_check_rel(rel);
rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
***************
*** 3102,3125 **** 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;
--- 3133,3160 ----
* 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;
***************
*** 3132,3138 **** 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;
--- 3167,3174 ----
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;
***************
*** 3254,3264 **** 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;
--- 3290,3306 ----
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);
pass = AT_PASS_MISC;
***************
*** 3292,3298 **** 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 */
case AT_EnableRowSecurity:
--- 3334,3339 ----
***************
*** 4292,4298 **** 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
--- 4333,4340 ----
*/
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
***************
*** 4320,4327 **** 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);
}
}
--- 4362,4373 ----
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);
}
}
***************
*** 4611,4617 **** 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);
--- 4657,4663 ----
/* 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);
***************
*** 4907,4912 **** ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4953,4963 ----
/* 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 */
***************
*** 5507,5513 **** 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
--- 5558,5564 ----
/* 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
***************
*** 5899,5905 **** 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
--- 5950,5956 ----
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/*
* Call AddRelationNewConstraints to do the work, making sure it works on
***************
*** 5910,5918 **** 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 */
--- 5961,5977 ----
* 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 */
***************
*** 7399,7405 **** 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);
--- 7458,7464 ----
/* 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);
***************
*** 7734,7740 **** 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,
--- 7793,7802 ----
* 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/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 2307,2312 **** EvalPlanQualFetchRowMarks(EPQState *epqstate)
--- 2307,2332 ----
Assert(erm->markType == ROW_MARK_COPY);
+ /* if child rel, must check whether it produced this row */
+ if (erm->rti != erm->prti)
+ {
+ Oid tableoid;
+
+ datum = ExecGetJunkAttribute(epqstate->origslot,
+ aerm->toidAttNo,
+ &isNull);
+ /* non-locked rels could be on the inside of outer joins */
+ if (isNull)
+ continue;
+ tableoid = DatumGetObjectId(datum);
+
+ if (tableoid != RelationGetRelid(erm->relation))
+ {
+ /* this child is inactive right now */
+ continue;
+ }
+ }
+
/* fetch the whole-row Var for the relation */
datum = ExecGetJunkAttribute(epqstate->origslot,
aerm->wholeAttNo,
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2025,2030 **** _copyRangeTblEntry(const RangeTblEntry *from)
--- 2025,2031 ----
COPY_NODE_FIELD(eref);
COPY_SCALAR_FIELD(lateral);
COPY_SCALAR_FIELD(inh);
+ COPY_SCALAR_FIELD(hasForeignChildren);
COPY_SCALAR_FIELD(inFromCl);
COPY_SCALAR_FIELD(requiredPerms);
COPY_SCALAR_FIELD(checkAsUser);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 2343,2348 **** _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
--- 2343,2349 ----
COMPARE_NODE_FIELD(eref);
COMPARE_SCALAR_FIELD(lateral);
COMPARE_SCALAR_FIELD(inh);
+ COMPARE_SCALAR_FIELD(hasForeignChildren);
COMPARE_SCALAR_FIELD(inFromCl);
COMPARE_SCALAR_FIELD(requiredPerms);
COMPARE_SCALAR_FIELD(checkAsUser);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 2453,2458 **** _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
--- 2453,2459 ----
WRITE_BOOL_FIELD(lateral);
WRITE_BOOL_FIELD(inh);
+ WRITE_BOOL_FIELD(hasForeignChildren);
WRITE_BOOL_FIELD(inFromCl);
WRITE_UINT_FIELD(requiredPerms);
WRITE_OID_FIELD(checkAsUser);
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 1249,1254 **** _readRangeTblEntry(void)
--- 1249,1255 ----
READ_BOOL_FIELD(lateral);
READ_BOOL_FIELD(inh);
+ READ_BOOL_FIELD(hasForeignChildren);
READ_BOOL_FIELD(inFromCl);
READ_UINT_FIELD(requiredPerms);
READ_OID_FIELD(checkAsUser);
*** a/src/backend/optimizer/prep/preptlist.c
--- b/src/backend/optimizer/prep/preptlist.c
***************
*** 111,116 **** preprocess_targetlist(PlannerInfo *root, List *tlist)
--- 111,118 ----
/* if parent of inheritance tree, need the tableoid too */
if (rc->isParent)
{
+ RangeTblEntry *rte = rt_fetch(rc->rti, parse->rtable);
+
var = makeVar(rc->rti,
TableOidAttributeNumber,
OIDOID,
***************
*** 123,128 **** preprocess_targetlist(PlannerInfo *root, List *tlist)
--- 125,146 ----
pstrdup(resname),
true);
tlist = lappend(tlist, tle);
+
+ /* if containing foreign tables, fetch the whole row too */
+ if (rte->hasForeignChildren)
+ {
+ /* Not a table, so we need the whole row as a junk var */
+ var = makeWholeRowVar(rt_fetch(rc->rti, range_table),
+ rc->rti,
+ 0,
+ false);
+ snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId);
+ tle = makeTargetEntry((Expr *) var,
+ list_length(tlist) + 1,
+ pstrdup(resname),
+ true);
+ tlist = lappend(tlist, tle);
+ }
}
}
else
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 1237,1242 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1237,1243 ----
LOCKMODE lockmode;
List *inhOIDs;
List *appinfos;
+ bool hasForeignChildren;
ListCell *l;
/* Does RT entry allow inheritance? */
***************
*** 1309,1314 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1310,1316 ----
/* Scan the inheritance set and expand it */
appinfos = NIL;
+ hasForeignChildren = false;
foreach(l, inhOIDs)
{
Oid childOID = lfirst_oid(l);
***************
*** 1338,1348 **** 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);
--- 1340,1351 ----
/*
* 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);
***************
*** 1388,1400 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
newrc->rti = childRTindex;
newrc->prti = rti;
newrc->rowmarkId = oldrc->rowmarkId;
! newrc->markType = oldrc->markType;
newrc->waitPolicy = oldrc->waitPolicy;
newrc->isParent = false;
root->rowMarks = lappend(root->rowMarks, newrc);
}
/* Close child relations, but keep locks */
if (childOID != parentOID)
heap_close(newrelation, NoLock);
--- 1391,1409 ----
newrc->rti = childRTindex;
newrc->prti = rti;
newrc->rowmarkId = oldrc->rowmarkId;
! if (childrte->relkind == RELKIND_FOREIGN_TABLE)
! newrc->markType = ROW_MARK_COPY;
! else
! newrc->markType = oldrc->markType;
newrc->waitPolicy = oldrc->waitPolicy;
newrc->isParent = false;
root->rowMarks = lappend(root->rowMarks, newrc);
}
+ if (childrte->relkind == RELKIND_FOREIGN_TABLE)
+ hasForeignChildren = true;
+
/* Close child relations, but keep locks */
if (childOID != parentOID)
heap_close(newrelation, NoLock);
***************
*** 1416,1421 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1425,1433 ----
/* Otherwise, OK to add to root->append_rel_list */
root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+
+ /* And set rte->hasForeignChildren */
+ rte->hasForeignChildren = hasForeignChildren;
}
/*
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 4363,4394 **** 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;
}
;
--- 4363,4394 ----
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/include/commands/explain.h
--- b/src/include/commands/explain.h
***************
*** 91,94 **** extern void ExplainPropertyLong(const char *qlabel, long value,
--- 91,99 ----
extern void ExplainPropertyFloat(const char *qlabel, double value, int ndigits,
ExplainState *es);
+ extern void ExplainOpenForeignModifyGroup(ModifyTable *plan, int subplan_index,
+ ExplainState *es);
+ extern void ExplainCloseForeignModifyGroup(ModifyTable *plan, int subplan_index,
+ ExplainState *es);
+
#endif /* EXPLAIN_H */
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 812,817 **** typedef struct RangeTblEntry
--- 812,819 ----
Alias *eref; /* expanded reference names */
bool lateral; /* subquery, function, or values is LATERAL? */
bool inh; /* inheritance requested? */
+ bool hasForeignChildren; /* does inheritance set include
+ * any foreign tables? */
bool inFromCl; /* present in FROM clause? */
AclMode requiredPerms; /* bitmask of required access permissions */
Oid checkAsUser; /* if valid, check access as this role */
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 851,856 **** typedef enum RowMarkType
--- 851,858 ----
* The tableoid column is only present for an inheritance hierarchy.
* When markType == ROW_MARK_COPY, there is instead a single column named
* wholerow%u whole-row value of relation
+ * The wholerow column is also present for an inheritance hierarchy that
+ * contains foreign tables.
* In all three cases, %u represents the rowmark ID number (rowmarkId).
* This number is unique within a plan tree, except that child relation
* entries copy their parent's rowmarkId. (Assigning unique numbers
*** a/src/test/regress/expected/foreign_data.out
--- b/src/test/regress/expected/foreign_data.out
***************
*** 1234,1239 **** DROP TRIGGER trigtest_before_row ON foreign_schema.foreign_table_1;
--- 1234,1644 ----
DROP TRIGGER trigtest_after_stmt ON foreign_schema.foreign_table_1;
DROP TRIGGER trigtest_after_row ON foreign_schema.foreign_table_1;
DROP FUNCTION dummy_trigger();
+ -- Table inheritance
+ CREATE TABLE pt1 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ );
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ DROP FOREIGN TABLE ft2;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+
+ CREATE FOREIGN TABLE ft2 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+
+ ALTER FOREIGN TABLE ft2 INHERIT pt1;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ CREATE VIEW v1 (c1, c2, c3) AS SELECT 1, 'v1'::text, '1994-01-01'::date;
+ ALTER TABLE v1 INHERIT pt1; -- ERROR
+ ERROR: "v1" is not a table or foreign table
+ DROP VIEW v1;
+ -- add attributes recursively
+ ALTER TABLE pt1 ADD COLUMN c4 integer;
+ ALTER TABLE pt1 ADD COLUMN c5 integer DEFAULT 0;
+ ALTER TABLE pt1 ADD COLUMN c6 integer;
+ ALTER TABLE pt1 ADD COLUMN c7 integer NOT NULL;
+ ALTER TABLE pt1 ADD COLUMN c8 integer;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ c4 | integer | | plain | |
+ c5 | integer | default 0 | plain | |
+ c6 | integer | | plain | |
+ c7 | integer | not null | plain | |
+ c8 | integer | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ c4 | integer | | | plain | |
+ c5 | integer | default 0 | | plain | |
+ c6 | integer | | | plain | |
+ c7 | integer | not null | | plain | |
+ c8 | integer | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- alter attributes recursively
+ ALTER TABLE pt1 ALTER COLUMN c4 SET DEFAULT 0;
+ ALTER TABLE pt1 ALTER COLUMN c5 DROP DEFAULT;
+ ALTER TABLE pt1 ALTER COLUMN c6 SET NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN c7 DROP NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN c8 TYPE char(10) USING '0'; -- ERROR
+ ERROR: "ft2" is not a table
+ ALTER TABLE pt1 ALTER COLUMN c8 TYPE char(10);
+ ALTER TABLE pt1 ALTER COLUMN c8 SET DATA TYPE text;
+ ALTER TABLE pt1 ALTER COLUMN c1 SET STATISTICS 10000;
+ ALTER TABLE pt1 ALTER COLUMN c1 SET (n_distinct = 100);
+ ALTER TABLE pt1 ALTER COLUMN c8 SET STATISTICS -1;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ c4 | integer | default 0 | plain | |
+ c5 | integer | | plain | |
+ c6 | integer | not null | plain | |
+ c7 | integer | | plain | |
+ c8 | text | | extended | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | 10000 |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ c4 | integer | default 0 | | plain | |
+ c5 | integer | | | plain | |
+ c6 | integer | not null | | plain | |
+ c7 | integer | | | plain | |
+ c8 | text | | | extended | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- drop attributes recursively
+ ALTER TABLE pt1 DROP COLUMN c4;
+ ALTER TABLE pt1 DROP COLUMN c5;
+ ALTER TABLE pt1 DROP COLUMN c6;
+ ALTER TABLE pt1 DROP COLUMN c7;
+ ALTER TABLE pt1 DROP COLUMN c8;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | 10000 |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- cannot change storage mode for an attribute of foreign tables
+ ALTER TABLE pt1 ALTER COLUMN c2 SET STORAGE EXTERNAL;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | external | |
+ c3 | date | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | 10000 |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ DROP FOREIGN TABLE ft2;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | external | |
+ c3 | date | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ ALTER TABLE pt1 ALTER COLUMN c2 SET STORAGE EXTENDED;
+ -- add constraints recursively
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk1 CHECK (c1 > 0) NO INHERIT;
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '');
+ -- connoinherit should be true for NO INHERIT constraint
+ SELECT pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pgc.connoinherit FROM pg_class AS pc INNER JOIN pg_constraint AS pgc ON (pgc.conrelid = pc.oid) WHERE pc.relname = 'pt1' order by 1,2;
+ relname | conname | contype | conislocal | coninhcount | connoinherit
+ ---------+---------+---------+------------+-------------+--------------
+ pt1 | pt1chk1 | c | t | 0 | t
+ pt1 | pt1chk2 | c | t | 0 | f
+ (2 rows)
+
+ -- child does not inherit NO INHERIT constraints
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Check constraints:
+ "pt1chk1" CHECK (c1 > 0) NO INHERIT
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ DROP FOREIGN TABLE ft2;
+ CREATE FOREIGN TABLE ft2 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ -- child must have parent's INHERIT constraints
+ ALTER FOREIGN TABLE ft2 INHERIT pt1; -- ERROR
+ ERROR: child table is missing constraint "pt1chk2"
+ ALTER FOREIGN TABLE ft2 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '');
+ ALTER FOREIGN TABLE ft2 INHERIT pt1;
+ -- child does not inherit NO INHERIT constraints
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Check constraints:
+ "pt1chk1" CHECK (c1 > 0) NO INHERIT
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- drop constraints recursively
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk1 CASCADE;
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk2 CASCADE;
+ -- NOT VALID should be ignored for foreign tables
+ INSERT INTO pt1 VALUES (1, 'pt1'::text, '1994-01-01'::date);
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '') NOT VALID;
+ NOTICE: merging constraint "pt1chk2" with inherited definition
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text) NOT VALID
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- VALIDATE CONSTRAINT should work by ignoring foreign tables
+ ALTER TABLE pt1 VALIDATE CONSTRAINT pt1chk2;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- cannot add an OID system column to foreign tables
+ ALTER TABLE pt1 SET WITH OIDS; -- ERROR
+ ERROR: cannot add OID column to foreign table "ft2"
+ DROP FOREIGN TABLE ft2;
+ ALTER TABLE pt1 SET WITH OIDS;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
+ ERROR: cannot inherit from relation with OIDs
+ ALTER TABLE pt1 SET WITHOUT OIDS;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ -- changes name of an attribute recursively
+ ALTER TABLE pt1 RENAME COLUMN c1 TO f1;
+ ALTER TABLE pt1 RENAME COLUMN c2 TO f2;
+ ALTER TABLE pt1 RENAME COLUMN c3 TO f3;
+ -- changes name of a constraint recursively
+ ALTER TABLE pt1 RENAME CONSTRAINT pt1chk2 TO f2_check;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ f1 | integer | not null | plain | 10000 |
+ f2 | text | | extended | |
+ f3 | date | | plain | |
+ Check constraints:
+ "f2_check" CHECK (f2 <> ''::text)
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ f1 | integer | not null | | plain | |
+ f2 | text | | | extended | |
+ f3 | date | | | plain | |
+ Check constraints:
+ "f2_check" CHECK (f2 <> ''::text)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- TRUNCATE should work by ignoring foreign tables
+ TRUNCATE pt1;
+ TRUNCATE ft2; -- ERROR
+ ERROR: "ft2" is not a table
+ DROP TABLE pt1 CASCADE;
+ NOTICE: drop cascades to foreign table ft2
-- IMPORT FOREIGN SCHEMA
IMPORT FOREIGN SCHEMA s1 FROM SERVER s9 INTO public; -- ERROR
ERROR: foreign-data wrapper "foo" has no handler
*** a/src/test/regress/sql/foreign_data.sql
--- b/src/test/regress/sql/foreign_data.sql
***************
*** 536,541 **** DROP TRIGGER trigtest_after_row ON foreign_schema.foreign_table_1;
--- 536,670 ----
DROP FUNCTION dummy_trigger();
+ -- Table inheritance
+ CREATE TABLE pt1 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ );
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ \d+ ft2
+ DROP FOREIGN TABLE ft2;
+ \d+ pt1
+ CREATE FOREIGN TABLE ft2 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ ft2
+ ALTER FOREIGN TABLE ft2 INHERIT pt1;
+ \d+ pt1
+ \d+ ft2
+ CREATE VIEW v1 (c1, c2, c3) AS SELECT 1, 'v1'::text, '1994-01-01'::date;
+ ALTER TABLE v1 INHERIT pt1; -- ERROR
+ DROP VIEW v1;
+
+ -- add attributes recursively
+ ALTER TABLE pt1 ADD COLUMN c4 integer;
+ ALTER TABLE pt1 ADD COLUMN c5 integer DEFAULT 0;
+ ALTER TABLE pt1 ADD COLUMN c6 integer;
+ ALTER TABLE pt1 ADD COLUMN c7 integer NOT NULL;
+ ALTER TABLE pt1 ADD COLUMN c8 integer;
+ \d+ pt1
+ \d+ ft2
+
+ -- alter attributes recursively
+ ALTER TABLE pt1 ALTER COLUMN c4 SET DEFAULT 0;
+ ALTER TABLE pt1 ALTER COLUMN c5 DROP DEFAULT;
+ ALTER TABLE pt1 ALTER COLUMN c6 SET NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN c7 DROP NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN c8 TYPE char(10) USING '0'; -- ERROR
+ ALTER TABLE pt1 ALTER COLUMN c8 TYPE char(10);
+ ALTER TABLE pt1 ALTER COLUMN c8 SET DATA TYPE text;
+ ALTER TABLE pt1 ALTER COLUMN c1 SET STATISTICS 10000;
+ ALTER TABLE pt1 ALTER COLUMN c1 SET (n_distinct = 100);
+ ALTER TABLE pt1 ALTER COLUMN c8 SET STATISTICS -1;
+ \d+ pt1
+ \d+ ft2
+
+ -- drop attributes recursively
+ ALTER TABLE pt1 DROP COLUMN c4;
+ ALTER TABLE pt1 DROP COLUMN c5;
+ ALTER TABLE pt1 DROP COLUMN c6;
+ ALTER TABLE pt1 DROP COLUMN c7;
+ ALTER TABLE pt1 DROP COLUMN c8;
+ \d+ pt1
+ \d+ ft2
+
+ -- cannot change storage mode for an attribute of foreign tables
+ ALTER TABLE pt1 ALTER COLUMN c2 SET STORAGE EXTERNAL;
+ \d+ pt1
+ \d+ ft2
+ DROP FOREIGN TABLE ft2;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ \d+ ft2
+ ALTER TABLE pt1 ALTER COLUMN c2 SET STORAGE EXTENDED;
+
+ -- add constraints recursively
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk1 CHECK (c1 > 0) NO INHERIT;
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '');
+ -- connoinherit should be true for NO INHERIT constraint
+ SELECT pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pgc.connoinherit FROM pg_class AS pc INNER JOIN pg_constraint AS pgc ON (pgc.conrelid = pc.oid) WHERE pc.relname = 'pt1' order by 1,2;
+ -- child does not inherit NO INHERIT constraints
+ \d+ pt1
+ \d+ ft2
+ DROP FOREIGN TABLE ft2;
+ CREATE FOREIGN TABLE ft2 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ -- child must have parent's INHERIT constraints
+ ALTER FOREIGN TABLE ft2 INHERIT pt1; -- ERROR
+ ALTER FOREIGN TABLE ft2 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '');
+ ALTER FOREIGN TABLE ft2 INHERIT pt1;
+ -- child does not inherit NO INHERIT constraints
+ \d+ pt1
+ \d+ ft2
+
+ -- drop constraints recursively
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk1 CASCADE;
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk2 CASCADE;
+
+ -- NOT VALID should be ignored for foreign tables
+ INSERT INTO pt1 VALUES (1, 'pt1'::text, '1994-01-01'::date);
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '') NOT VALID;
+ \d+ pt1
+ \d+ ft2
+ -- VALIDATE CONSTRAINT should work by ignoring foreign tables
+ ALTER TABLE pt1 VALIDATE CONSTRAINT pt1chk2;
+ \d+ pt1
+ \d+ ft2
+
+ -- cannot add an OID system column to foreign tables
+ ALTER TABLE pt1 SET WITH OIDS; -- ERROR
+ DROP FOREIGN TABLE ft2;
+ ALTER TABLE pt1 SET WITH OIDS;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
+ ALTER TABLE pt1 SET WITHOUT OIDS;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+
+ -- changes name of an attribute recursively
+ ALTER TABLE pt1 RENAME COLUMN c1 TO f1;
+ ALTER TABLE pt1 RENAME COLUMN c2 TO f2;
+ ALTER TABLE pt1 RENAME COLUMN c3 TO f3;
+ -- changes name of a constraint recursively
+ ALTER TABLE pt1 RENAME CONSTRAINT pt1chk2 TO f2_check;
+ \d+ pt1
+ \d+ ft2
+
+ -- TRUNCATE should work by ignoring foreign tables
+ TRUNCATE pt1;
+ TRUNCATE ft2; -- ERROR
+
+ DROP TABLE pt1 CASCADE;
+
-- IMPORT FOREIGN SCHEMA
IMPORT FOREIGN SCHEMA s1 FROM SERVER s9 INTO public; -- ERROR
IMPORT FOREIGN SCHEMA s1 LIMIT TO (t1) FROM SERVER s9 INTO public; --ERROR
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-12-22 15:36 Tom Lane <tgl@sss.pgh.pa.us>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 2 replies; 179+ messages in thread
From: Tom Lane @ 2014-12-22 15:36 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp> writes:
> I haven't done anything about the issue that postgresGetForeignPlan() is
> using get_parse_rowmark(), discussed in eg, [2]. Tom, will you work on
> that?
Yeah, we need to do something about the PlanRowMark data structure.
Aside from the pre-existing issue in postgres_fdw, we need to fix it
to support inheritance trees in which more than one rowmark method
is being used. That rte.hasForeignChildren thing is a crock, and
would still be a crock even if it were correctly documented as being
a planner temporary variable (rather than the current implication that
it's always valid). RangeTblEntry is no place for planner temporaries.
The idea I'd had about that was to convert the markType field into a
bitmask, so that a parent node's markType could represent the logical
OR of the rowmark methods being used by all its children. I've not
attempted to code this up though, and probably won't get to it until
after Christmas. One thing that's not clear is what should happen
with ExecRowMark.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2014-12-26 06:10 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 0 replies; 179+ messages in thread
From: Etsuro Fujita @ 2014-12-26 06:10 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On 2014/12/23 0:36, Tom Lane wrote:
> Yeah, we need to do something about the PlanRowMark data structure.
> Aside from the pre-existing issue in postgres_fdw, we need to fix it
> to support inheritance trees in which more than one rowmark method
> is being used. That rte.hasForeignChildren thing is a crock, and
> would still be a crock even if it were correctly documented as being
> a planner temporary variable (rather than the current implication that
> it's always valid). RangeTblEntry is no place for planner temporaries.
Agreed.
> The idea I'd had about that was to convert the markType field into a
> bitmask, so that a parent node's markType could represent the logical
> OR of the rowmark methods being used by all its children. I've not
> attempted to code this up though, and probably won't get to it until
> after Christmas. One thing that's not clear is what should happen
> with ExecRowMark.
That seems like a good idea, as parent PlanRowMarks are ignored at runtime.
Aside from the above, I noticed that the patch has a bug in handling
ExecRowMarks/ExecAuxRowMarks for foreign tables in inheritance trees
during the EPQ processing.:-( Attached is an updated version of the
patch to fix that, which has been created on top of [1], as said before.
Thanks,
[1] http://www.postgresql.org/message-id/5497BF4C.6080302@lab.ntt.co.jp
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-patch] fdw-inh-7.patch (100.8K, ../../549CFBD8.50204@lab.ntt.co.jp/2-fdw-inh-7.patch)
download | inline diff:
*** a/contrib/file_fdw/input/file_fdw.source
--- b/contrib/file_fdw/input/file_fdw.source
***************
*** 148,153 **** EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
--- 148,167 ----
SELECT * FROM agg_csv WHERE a < 0;
RESET constraint_exclusion;
+ -- table inheritance tests
+ CREATE TABLE agg (a int2, b float4);
+ ALTER FOREIGN TABLE agg_csv INHERIT agg;
+ SELECT tableoid::regclass, * FROM agg;
+ SELECT tableoid::regclass, * FROM agg_csv;
+ SELECT tableoid::regclass, * FROM ONLY agg;
+ -- updates aren't supported
+ UPDATE agg SET a = 1;
+ DELETE FROM agg WHERE a = 100;
+ -- but this should be ignored
+ SELECT tableoid::regclass, * FROM agg FOR UPDATE;
+ ALTER FOREIGN TABLE agg_csv NO INHERIT agg;
+ DROP TABLE agg;
+
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/file_fdw/output/file_fdw.source
--- b/contrib/file_fdw/output/file_fdw.source
***************
*** 249,254 **** SELECT * FROM agg_csv WHERE a < 0;
--- 249,294 ----
(0 rows)
RESET constraint_exclusion;
+ -- table inheritance tests
+ CREATE TABLE agg (a int2, b float4);
+ ALTER FOREIGN TABLE agg_csv INHERIT agg;
+ SELECT tableoid::regclass, * FROM agg;
+ tableoid | a | b
+ ----------+-----+---------
+ agg_csv | 100 | 99.097
+ agg_csv | 0 | 0.09561
+ agg_csv | 42 | 324.78
+ (3 rows)
+
+ SELECT tableoid::regclass, * FROM agg_csv;
+ tableoid | a | b
+ ----------+-----+---------
+ agg_csv | 100 | 99.097
+ agg_csv | 0 | 0.09561
+ agg_csv | 42 | 324.78
+ (3 rows)
+
+ SELECT tableoid::regclass, * FROM ONLY agg;
+ tableoid | a | b
+ ----------+---+---
+ (0 rows)
+
+ -- updates aren't supported
+ UPDATE agg SET a = 1;
+ ERROR: cannot update foreign table "agg_csv"
+ DELETE FROM agg WHERE a = 100;
+ ERROR: cannot delete from foreign table "agg_csv"
+ -- but this should be ignored
+ SELECT tableoid::regclass, * FROM agg FOR UPDATE;
+ tableoid | a | b
+ ----------+-----+---------
+ agg_csv | 100 | 99.097
+ agg_csv | 0 | 0.09561
+ agg_csv | 42 | 324.78
+ (3 rows)
+
+ ALTER FOREIGN TABLE agg_csv NO INHERIT agg;
+ DROP TABLE agg;
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 3027,3032 **** NOTICE: NEW: (13,"test triggered !")
--- 3027,3544 ----
(1 row)
-- ===================================================================
+ -- test inheritance features
+ -- ===================================================================
+ CREATE TABLE a (aa TEXT);
+ CREATE TABLE loct (aa TEXT, bb TEXT);
+ CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a)
+ SERVER loopback OPTIONS (table_name 'loct');
+ INSERT INTO a(aa) VALUES('aaa');
+ INSERT INTO a(aa) VALUES('aaaa');
+ INSERT INTO a(aa) VALUES('aaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaaa');
+ INSERT INTO b(aa) VALUES('bbb');
+ INSERT INTO b(aa) VALUES('bbbb');
+ INSERT INTO b(aa) VALUES('bbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbbb');
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | aaa
+ a | aaaa
+ a | aaaaa
+ a | aaaaaa
+ a | aaaaaaa
+ a | aaaaaaaa
+ b | bbb
+ b | bbbb
+ b | bbbbb
+ b | bbbbbb
+ b | bbbbbbb
+ b | bbbbbbbb
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----------+----
+ b | bbb |
+ b | bbbb |
+ b | bbbbb |
+ b | bbbbbb |
+ b | bbbbbbb |
+ b | bbbbbbbb |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | aaa
+ a | aaaa
+ a | aaaaa
+ a | aaaaaa
+ a | aaaaaaa
+ a | aaaaaaaa
+ (6 rows)
+
+ UPDATE a SET aa='zzzzzz' WHERE aa LIKE 'aaa%';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ b | bbb
+ b | bbbb
+ b | bbbbb
+ b | bbbbbb
+ b | bbbbbbb
+ b | bbbbbbbb
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----------+----
+ b | bbb |
+ b | bbbb |
+ b | bbbbb |
+ b | bbbbbb |
+ b | bbbbbbb |
+ b | bbbbbbbb |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ (6 rows)
+
+ UPDATE b SET aa='new';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+-----+----
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ (6 rows)
+
+ UPDATE a SET aa='new';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+-----
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+-----+----
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+-----
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ (6 rows)
+
+ DELETE FROM a;
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----
+ (0 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----+----
+ (0 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----
+ (0 rows)
+
+ DROP TABLE a CASCADE;
+ NOTICE: drop cascades to foreign table b
+ DROP TABLE loct;
+ -- Check SELECT FOR UPDATE/SHARE with an inherited source table
+ create table loct1 (f1 int, f2 int, f3 int);
+ create table loct2 (f1 int, f2 int, f3 int);
+ create table foo (f1 int, f2 int);
+ create foreign table foo2 (f3 int) inherits (foo)
+ server loopback options (table_name 'loct1');
+ create table bar (f1 int, f2 int);
+ create foreign table bar2 (f3 int) inherits (bar)
+ server loopback options (table_name 'loct2');
+ insert into foo values(1,1);
+ insert into foo values(3,3);
+ insert into foo2 values(2,2,2);
+ insert into foo2 values(3,3,3);
+ insert into bar values(1,1);
+ insert into bar values(2,2);
+ insert into bar values(3,3);
+ insert into bar values(4,4);
+ insert into bar2 values(1,1,1);
+ insert into bar2 values(2,2,2);
+ insert into bar2 values(3,3,3);
+ insert into bar2 values(4,4,4);
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for update;
+ QUERY PLAN
+ ----------------------------------------------------------------------------------------------
+ LockRows
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ -> Hash Join
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Append
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.ctid, bar2.tableoid, bar2.*
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (22 rows)
+
+ select * from bar where f1 in (select f1 from foo) for update;
+ f1 | f2
+ ----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ (6 rows)
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for share;
+ QUERY PLAN
+ ----------------------------------------------------------------------------------------------
+ LockRows
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ -> Hash Join
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Append
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.ctid, bar2.tableoid, bar2.*
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (22 rows)
+
+ select * from bar where f1 in (select f1 from foo) for share;
+ f1 | f2
+ ----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ (6 rows)
+
+ -- Check UPDATE with inherited target and an inherited source table
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ QUERY PLAN
+ ---------------------------------------------------------------------------------------------
+ Update on public.bar
+ For public.bar2
+ Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
+ -> Hash Join
+ Output: bar.f1, (bar.f2 + 100), bar.ctid, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ -> Hash Join
+ Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar2.f1 = foo.f1)
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (36 rows)
+
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+ relname | f1 | f2
+ ---------+----+-----
+ bar | 1 | 101
+ bar | 2 | 102
+ bar | 3 | 103
+ bar | 4 | 4
+ bar2 | 1 | 101
+ bar2 | 2 | 102
+ bar2 | 3 | 103
+ bar2 | 4 | 4
+ (8 rows)
+
+ -- Check UPDATE with inherited target and an appendrel subquery
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ QUERY PLAN
+ --------------------------------------------------------------------------------------
+ Update on public.bar
+ For public.bar2
+ Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
+ -> Hash Join
+ Output: bar.f1, (bar.f2 + 100), bar.ctid, (ROW(foo.f1))
+ Hash Cond: (foo.f1 = bar.f1)
+ -> Append
+ -> Seq Scan on public.foo
+ Output: ROW(foo.f1), foo.f1
+ -> Foreign Scan on public.foo2
+ Output: ROW(foo2.f1), foo2.f1
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Seq Scan on public.foo foo_1
+ Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3)
+ -> Foreign Scan on public.foo2 foo2_1
+ Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3)
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Hash
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Merge Join
+ Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, (ROW(foo.f1))
+ Merge Cond: (bar2.f1 = foo.f1)
+ -> Sort
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Sort Key: bar2.f1
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Sort
+ Output: (ROW(foo.f1)), foo.f1
+ Sort Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: ROW(foo.f1), foo.f1
+ -> Foreign Scan on public.foo2
+ Output: ROW(foo2.f1), foo2.f1
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Seq Scan on public.foo foo_1
+ Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3)
+ -> Foreign Scan on public.foo2 foo2_1
+ Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3)
+ Remote SQL: SELECT f1 FROM public.loct1
+ (44 rows)
+
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+ relname | f1 | f2
+ ---------+----+-----
+ bar | 1 | 201
+ bar | 2 | 202
+ bar | 3 | 203
+ bar | 4 | 104
+ bar2 | 1 | 201
+ bar2 | 2 | 202
+ bar2 | 3 | 203
+ bar2 | 4 | 104
+ (8 rows)
+
+ drop table foo cascade;
+ NOTICE: drop cascades to foreign table foo2
+ drop table bar cascade;
+ NOTICE: drop cascades to foreign table bar2
+ drop table loct1;
+ drop table loct2;
+ -- Test that WHERE CURRENT OF is not supported
+ create table ltbl (a int, b text);
+ create table ptbl (a int, b text);
+ create table locc () inherits (ptbl);
+ insert into locc values(1, 'foo');
+ create foreign table remc () inherits (ptbl)
+ server loopback options (table_name 'ltbl');
+ insert into remc values(2, 'bar');
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'foo';
+ fetch from c;
+ ?column?
+ ----------
+ 1
+ (1 row)
+
+ update ptbl set b = null where current of c;
+ ERROR: WHERE CURRENT OF is not supported for this table type
+ rollback;
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'bar';
+ fetch from c;
+ ?column?
+ ----------
+ 1
+ (1 row)
+
+ update ptbl set b = null where current of c;
+ ERROR: WHERE CURRENT OF is not supported for this table type
+ rollback;
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ -- Test TRUNCATE
+ truncate ptbl;
+ select * from ptbl;
+ a | b
+ ---+-----
+ 2 | bar
+ (1 row)
+
+ select * from only ptbl;
+ a | b
+ ---+---
+ (0 rows)
+
+ select * from locc;
+ a | b
+ ---+---
+ (0 rows)
+
+ select * from remc;
+ a | b
+ ---+-----
+ 2 | bar
+ (1 row)
+
+ truncate remc; -- ERROR
+ ERROR: "remc" is not a table
+ drop table ptbl cascade;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to table locc
+ drop cascades to foreign table remc
+ drop table ltbl;
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
CREATE SCHEMA import_source;
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 824,834 **** postgresGetForeignPlan(PlannerInfo *root,
{
RowMarkClause *rc = get_parse_rowmark(root->parse, baserel->relid);
if (rc)
{
/*
! * Relation is specified as a FOR UPDATE/SHARE target, so handle
! * that.
*
* For now, just ignore any [NO] KEY specification, since (a) it's
* not clear what that means for a remote table that we don't have
--- 824,847 ----
{
RowMarkClause *rc = get_parse_rowmark(root->parse, baserel->relid);
+ /*
+ * It's possible that relation is contained in an inheritance set and
+ * that parent relation is selected FOR UPDATE/SHARE. If so, get the
+ * RowMarkClause for parent relation.
+ */
+ if (rc == NULL)
+ {
+ PlanRowMark *prm = get_plan_rowmark(root->rowMarks, baserel->relid);
+
+ if (prm && prm->rti != prm->prti)
+ rc = get_parse_rowmark(root->parse, prm->prti);
+ }
+
if (rc)
{
/*
! * Relation or parent relation is specified as a FOR UPDATE/SHARE
! * target, so handle that.
*
* For now, just ignore any [NO] KEY specification, since (a) it's
* not clear what that means for a remote table that we don't have
***************
*** 1682,1690 **** postgresExplainForeignModify(ModifyTableState *mtstate,
--- 1695,1722 ----
{
if (es->verbose)
{
+ ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+ Index rti = list_nth_int(plan->resultRelations, subplan_index);
char *sql = strVal(list_nth(fdw_private,
FdwModifyPrivateUpdateSql));
+ /*
+ * If we are in an inherited UPDATE/DELETE case, show the SQL statement
+ * together with the target relation. See
+ * ExplainOpenForeignModifyGroup/ExplainCloseForeignModifyGroup.
+ *
+ * Note: a foreign table shouldn't be the parent of an inheritance set.
+ * So if the RT index matches plan->resultRelation, it means that we
+ * are in a non-inherited case. Otherwise we are in an inherited case.
+ */
+ if (rti != plan->resultRelation)
+ {
+ ExplainOpenForeignModifyGroup(plan, subplan_index, es);
+ ExplainPropertyText("Remote SQL", sql, es);
+ ExplainCloseForeignModifyGroup(plan, subplan_index, es);
+ return;
+ }
+
ExplainPropertyText("Remote SQL", sql, es);
}
}
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 666,671 **** UPDATE rem1 SET f2 = 'testo';
--- 666,822 ----
INSERT INTO rem1(f2) VALUES ('test') RETURNING ctid;
-- ===================================================================
+ -- test inheritance features
+ -- ===================================================================
+
+ CREATE TABLE a (aa TEXT);
+ CREATE TABLE loct (aa TEXT, bb TEXT);
+ CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a)
+ SERVER loopback OPTIONS (table_name 'loct');
+
+ INSERT INTO a(aa) VALUES('aaa');
+ INSERT INTO a(aa) VALUES('aaaa');
+ INSERT INTO a(aa) VALUES('aaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaaa');
+
+ INSERT INTO b(aa) VALUES('bbb');
+ INSERT INTO b(aa) VALUES('bbbb');
+ INSERT INTO b(aa) VALUES('bbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbbb');
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE a SET aa='zzzzzz' WHERE aa LIKE 'aaa%';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE b SET aa='new';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE a SET aa='new';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ DELETE FROM a;
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ DROP TABLE a CASCADE;
+ DROP TABLE loct;
+
+ -- Check SELECT FOR UPDATE/SHARE with an inherited source table
+ create table loct1 (f1 int, f2 int, f3 int);
+ create table loct2 (f1 int, f2 int, f3 int);
+
+ create table foo (f1 int, f2 int);
+ create foreign table foo2 (f3 int) inherits (foo)
+ server loopback options (table_name 'loct1');
+ create table bar (f1 int, f2 int);
+ create foreign table bar2 (f3 int) inherits (bar)
+ server loopback options (table_name 'loct2');
+
+ insert into foo values(1,1);
+ insert into foo values(3,3);
+ insert into foo2 values(2,2,2);
+ insert into foo2 values(3,3,3);
+ insert into bar values(1,1);
+ insert into bar values(2,2);
+ insert into bar values(3,3);
+ insert into bar values(4,4);
+ insert into bar2 values(1,1,1);
+ insert into bar2 values(2,2,2);
+ insert into bar2 values(3,3,3);
+ insert into bar2 values(4,4,4);
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for update;
+ select * from bar where f1 in (select f1 from foo) for update;
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for share;
+ select * from bar where f1 in (select f1 from foo) for share;
+
+ -- Check UPDATE with inherited target and an inherited source table
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+
+ -- Check UPDATE with inherited target and an appendrel subquery
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+
+ drop table foo cascade;
+ drop table bar cascade;
+ drop table loct1;
+ drop table loct2;
+
+ -- Test that WHERE CURRENT OF is not supported
+ create table ltbl (a int, b text);
+
+ create table ptbl (a int, b text);
+ create table locc () inherits (ptbl);
+ insert into locc values(1, 'foo');
+
+ create foreign table remc () inherits (ptbl)
+ server loopback options (table_name 'ltbl');
+ insert into remc values(2, 'bar');
+
+ select * from ptbl;
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'foo';
+ fetch from c;
+ update ptbl set b = null where current of c;
+ rollback;
+
+ select * from ptbl;
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'bar';
+ fetch from c;
+ update ptbl set b = null where current of c;
+ rollback;
+
+ select * from ptbl;
+
+ -- Test TRUNCATE
+ truncate ptbl;
+ select * from ptbl;
+ select * from only ptbl;
+ select * from locc;
+ select * from remc;
+ truncate remc; -- ERROR
+
+ drop table ptbl cascade;
+ drop table ltbl;
+
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
*** a/doc/src/sgml/ddl.sgml
--- b/doc/src/sgml/ddl.sgml
***************
*** 2503,2508 **** VALUES ('Albany', NULL, NULL, 'NY');
--- 2503,2526 ----
further privileges to be granted.
</para>
+ <para>
+ Note that a foreign table can also inherit from more than one parent
+ table (see <xref linkend="ddl-foreign-data"> for an overview of
+ foreign tables).
+ Like normal tables, table inheritance is typically established when
+ the foreign table is created, using the <literal>INHERITS</> clause
+ of the <xref linkend="sql-createforeigntable"> statement.
+ Alternatively, a foreign table which is already defined
+ in a compatible way can have a new parent relationship added, using
+ the <literal>INHERIT</literal> variant of
+ <xref linkend="sql-alterforeigntable">.
+ Similarly an inheritance link can be removed from a child using the
+ <literal>NO INHERIT</literal> variant of <command>ALTER FOREIGN TABLE</>.
+ <command>CREATE FOREIGN TABLE</> and <command>ALTER FOREIGN TABLE</>
+ follow the same rules for duplicate column merging and rejection as
+ <command>CREATE TABLE</> and <command>ALTER TABLE</>, respectively.
+ </para>
+
<sect2 id="ddl-inherit-caveats">
<title>Caveats</title>
***************
*** 2650,2656 **** VALUES ('Albany', NULL, NULL, 'NY');
table of a single parent table. The parent table itself is normally
empty; it exists just to represent the entire data set. You should be
familiar with inheritance (see <xref linkend="ddl-inherit">) before
! attempting to set up partitioning.
</para>
<para>
--- 2668,2677 ----
table of a single parent table. The parent table itself is normally
empty; it exists just to represent the entire data set. You should be
familiar with inheritance (see <xref linkend="ddl-inherit">) before
! attempting to set up partitioning. (The setup and management of
! partitioned tables illustrated in this section assume that each
! partition is a normal table. However, you can do that in a similar way
! for cases where some or all partitions are foreign tables.)
</para>
<para>
***************
*** 2713,2720 **** VALUES ('Albany', NULL, NULL, 'NY');
</para>
<para>
! We will refer to the child tables as partitions, though they
! are in every way normal <productname>PostgreSQL</> tables.
</para>
</listitem>
--- 2734,2741 ----
</para>
<para>
! We will refer to the child tables as partitions, though we assume
! here that they are normal <productname>PostgreSQL</> tables.
</para>
</listitem>
*** a/doc/src/sgml/ref/alter_foreign_table.sgml
--- b/doc/src/sgml/ref/alter_foreign_table.sgml
***************
*** 48,53 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 48,55 ----
ENABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
ENABLE REPLICA TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
ENABLE ALWAYS TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 188,193 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 190,215 ----
</varlistentry>
<varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OWNER</literal></term>
<listitem>
<para>
***************
*** 384,389 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 406,421 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">new_owner</replaceable></term>
<listitem>
<para>
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 1010,1015 **** ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
--- 1010,1028 ----
</para>
<para>
+ A recursive <literal>SET STORAGE</literal> operation will make
+ the storage mode unchanged for any column of descendant tables
+ that are foreign.
+ </para>
+
+ <para>
+ A recursive <literal>SET WITH OIDS</literal> operation will be
+ rejected if any of descendant tables is foreign, since it is
+ not permitted to add an <literal>oid</literal> system column to
+ foreign tables.
+ </para>
+
+ <para>
The <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>,
and <literal>TABLESPACE</> actions never recurse to descendant tables;
that is, they always act as though <literal>ONLY</> were specified.
***************
*** 1018,1023 **** ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
--- 1031,1044 ----
</para>
<para>
+ When adding a <literal>CHECK</> constraint with the <literal>NOT VALID
+ </literal> option recursively, the inherited constraint on a descendant
+ table that is foreign will be marked valid without checking consistency
+ with the foreign server. It is the user's resposibility to ensure that
+ the constraint definition matches the remote side.
+ </para>
+
+ <para>
Changing any part of a system catalog table is not permitted.
</para>
*** a/doc/src/sgml/ref/analyze.sgml
--- b/doc/src/sgml/ref/analyze.sgml
***************
*** 200,205 **** ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
--- 200,212 ----
</para>
<para>
+ The inheritance statistics for a parent table that contains one or more
+ children that are foreign tables are collected only when explicitly
+ selected. If any of the foreign table's wrapper does not support
+ <command>ANALYZE</command>, the command prints a warning and does nothing.
+ </para>
+
+ <para>
If the table being analyzed is completely empty, <command>ANALYZE</command>
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
***************
*** 23,28 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 23,29 ----
| <replaceable>table_constraint</replaceable> }
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 121,126 **** CHECK ( <replaceable class="PARAMETER">expression</replaceable> )
--- 122,158 ----
</varlistentry>
<varlistentry>
+ <term><literal>INHERITS ( <replaceable>parent_table</replaceable> [, ... ] )</literal></term>
+ <listitem>
+ <para>
+ The optional <literal>INHERITS</> clause specifies a list of
+ tables from which the new foreign table automatically inherits
+ all columns. See the similar form of
+ <xref linkend="sql-createtable"> for more details.
+ </para>
+
+ <para>
+ Note that unlike <command>CREATE TABLE</>, column
+ <literal>STORAGE</> settings are not copied from parent tables,
+ resulting in the copied columns in the new foreign table having
+ type-specific default settings.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for
+ more information.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>NOT NULL</></term>
<listitem>
<para>
***************
*** 249,254 **** CHECK ( <replaceable class="PARAMETER">expression</replaceable> )
--- 281,292 ----
responsibility to ensure that the constraint definition matches
reality.
</para>
+
+ <para>
+ Since it is not permitted to add an <literal>oid</> system column to
+ foreign tables, the command will be rejected if any of parent tables
+ has an <literal>oid</> system column.
+ </para>
</refsect1>
<refsect1 id="SQL-CREATEFOREIGNTABLE-examples">
*** a/doc/src/sgml/ref/truncate.sgml
--- b/doc/src/sgml/ref/truncate.sgml
***************
*** 179,184 **** TRUNCATE [ TABLE ] [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [
--- 179,189 ----
This is similar to the usual behavior of <function>currval()</> after
a failed transaction.
</para>
+
+ <para>
+ A recursive <command>TRUNCATE</> ignores any descendant tables that are
+ foreign, and applies the operation to descendant tables that are normal.
+ </para>
</refsect1>
<refsect1>
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 56,61 ****
--- 56,69 ----
#include "utils/tqual.h"
+ /* Possible modes for ANALYZE */
+ typedef enum
+ {
+ ANL_MODE_ALL, /* Analyze all relations */
+ ANL_MODE_SINGLE, /* Analyze a specific relation */
+ ANL_MODE_AUTOVACUUM /* Autovacuum worker */
+ } AnalyzeMode;
+
/* Data structure for Algorithm S from Knuth 3.4.2 */
typedef struct
{
***************
*** 81,86 **** typedef struct AnlIndexData
--- 89,95 ----
int default_statistics_target = 100;
/* A few variables that don't seem worth passing around as parameters */
+ static AnalyzeMode anl_mode;
static MemoryContext anl_context = NULL;
static BufferAccessStrategy vac_strategy;
***************
*** 102,107 **** static int acquire_sample_rows(Relation onerel, int elevel,
--- 111,117 ----
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
static int compare_rows(const void *a, const void *b);
+ static bool has_foreign_children(Oid parentOID, List *tableOIDs);
static int acquire_inherited_sample_rows(Relation onerel, int elevel,
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
***************
*** 130,135 **** analyze_rel(Oid relid, VacuumStmt *vacstmt,
--- 140,154 ----
elevel = DEBUG2;
/* Set up static variables */
+ if (IsAutoVacuumWorkerProcess())
+ anl_mode = ANL_MODE_AUTOVACUUM;
+ else
+ {
+ if (!vacstmt->relation)
+ anl_mode = ANL_MODE_ALL;
+ else
+ anl_mode = ANL_MODE_SINGLE;
+ }
vac_strategy = bstrategy;
/*
***************
*** 297,305 **** analyze_rel(Oid relid, VacuumStmt *vacstmt,
* do_analyze_rel() -- analyze one relation, recursively or not
*
* Note that "acquirefunc" is only relevant for the non-inherited case.
! * If we supported foreign tables in inheritance trees,
! * acquire_inherited_sample_rows would need to determine the appropriate
! * acquirefunc for each child table.
*/
static void
do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
--- 316,323 ----
* do_analyze_rel() -- analyze one relation, recursively or not
*
* Note that "acquirefunc" is only relevant for the non-inherited case.
! * For the inherited case, acquire_inherited_sample_rows determines the
! * appropriate acquirefunc for each child table.
*/
static void
do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
***************
*** 1444,1454 **** compare_rows(const void *a, const void *b)
/*
* acquire_inherited_sample_rows -- acquire sample rows from inheritance tree
*
* This has the same API as acquire_sample_rows, except that rows are
* collected from all inheritance children as well as the specified table.
! * We fail and return zero if there are no inheritance children.
*/
static int
acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1462,1512 ----
/*
+ * Detect wether the inheritance tree contains any foreign tables
+ */
+ static bool
+ has_foreign_children(Oid parentOID, List *tableOIDs)
+ {
+ bool found;
+ ListCell *lc;
+
+ /* There are no children */
+ if (list_length(tableOIDs) < 2)
+ return false;
+
+ found = false;
+ foreach(lc, tableOIDs)
+ {
+ Oid childOID = lfirst_oid(lc);
+ Relation childrel;
+
+ /* Parent should not be foreign */
+ if (childOID == parentOID)
+ continue;
+
+ /* We already got the needed lock */
+ childrel = heap_open(childOID, NoLock);
+ if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /* Found it */
+ found = true;
+ }
+ heap_close(childrel, NoLock);
+
+ if (found)
+ return true;
+ }
+ return false;
+ }
+
+ /*
* acquire_inherited_sample_rows -- acquire sample rows from inheritance tree
*
* This has the same API as acquire_sample_rows, except that rows are
* collected from all inheritance children as well as the specified table.
! * We fail and return zero if there are no inheritance children, or if
! * inheritance tree contains any foreign tables and we are doing an
! * ANALYZE-with-no-parameter or in an autovacuum process.
*/
static int
acquire_inherited_sample_rows(Relation onerel, int elevel,
***************
*** 1457,1462 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1515,1521 ----
{
List *tableOIDs;
Relation *rels;
+ AcquireSampleRowsFunc *acquirefunc;
double *relblocks;
double totalblocks;
int numrows,
***************
*** 1491,1500 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1550,1580 ----
}
/*
+ * If we are doing an ANALYZE-with-no-parameter or in an autovacuum process
+ * and inheritance tree contains any foreign tables, then fail.
+ */
+ if (anl_mode != ANL_MODE_SINGLE)
+ {
+ if (has_foreign_children(RelationGetRelid(onerel), tableOIDs))
+ {
+ ereport(elevel,
+ (errmsg("skipping analyze of \"%s.%s\" inheritance tree --- this inheritance tree contains foreign tables",
+ get_namespace_name(RelationGetNamespace(onerel)),
+ RelationGetRelationName(onerel)),
+ errhint("Try ANALYZE \"%s.%s\" for the inheritance statistics.",
+ get_namespace_name(RelationGetNamespace(onerel)),
+ RelationGetRelationName(onerel))));
+ return 0;
+ }
+ }
+
+ /*
* Count the blocks in all the relations. The result could overflow
* 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;
***************
*** 1516,1522 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
}
rels[nrels] = childrel;
! relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
totalblocks += relblocks[nrels];
nrels++;
}
--- 1596,1631 ----
}
rels[nrels] = childrel;
!
! if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
! {
! FdwRoutine *fdwroutine;
! BlockNumber relpages = 0;
! bool ok = false;
!
! /* 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++;
}
***************
*** 1534,1539 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1643,1649 ----
{
Relation childrel = rels[i];
double childblocks = relblocks[i];
+ AcquireSampleRowsFunc childacquirefunc = acquirefunc[i];
if (childblocks > 0)
{
***************
*** 1549,1560 **** 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 &&
--- 1659,1670 ----
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/explain.c
--- b/src/backend/commands/explain.c
***************
*** 2221,2250 **** ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
}
/*
* Show extra information for a ModifyTable node
*/
static void
show_modifytable_info(ModifyTableState *mtstate, ExplainState *es)
{
! FdwRoutine *fdwroutine = mtstate->resultRelInfo->ri_FdwRoutine;
! /*
! * If the first target relation is a foreign table, call its FDW to
! * display whatever additional fields it wants to. For now, we ignore the
! * possibility of other targets being foreign tables, although the API for
! * ExplainForeignModify is designed to allow them to be processed.
! */
! if (fdwroutine != NULL &&
! fdwroutine->ExplainForeignModify != NULL)
{
! ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
! List *fdw_private = (List *) linitial(node->fdwPrivLists);
! fdwroutine->ExplainForeignModify(mtstate,
! mtstate->resultRelInfo,
! fdw_private,
! 0,
! es);
}
}
--- 2221,2318 ----
}
/*
+ * Open a group for foreign modification
+ */
+ void
+ ExplainOpenForeignModifyGroup(ModifyTable *plan, int subplan_index,
+ ExplainState *es)
+ {
+ char *objectname;
+ char *namespace;
+ char *refname;
+ Index rti;
+ RangeTblEntry *rte;
+
+ Assert(subplan_index > 0);
+
+ /* Open a group */
+ ExplainOpenGroup("Foreign Modification", "Foreign Modification", true, es);
+
+ /* Show the target relation */
+ Assert(plan->resultRelations != NIL);
+ rti = list_nth_int(plan->resultRelations, subplan_index);
+ rte = rt_fetch(rti, es->rtable);
+ /* Assert it's on a real relation */
+ Assert(rte->rtekind == RTE_RELATION);
+ objectname = get_rel_name(rte->relid);
+ namespace = get_namespace_name(get_rel_namespace(rte->relid));
+ refname = (char *) list_nth(es->rtable_names, rti - 1);
+
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ appendStringInfoSpaces(es->str, es->indent * 2);
+ appendStringInfoString(es->str, "For");
+ appendStringInfo(es->str, " %s.%s", quote_identifier(namespace),
+ quote_identifier(objectname));
+ if (strcmp(refname, objectname) != 0)
+ appendStringInfo(es->str, " %s", quote_identifier(refname));
+ appendStringInfoChar(es->str, '\n');
+ es->indent++;
+ }
+ else
+ {
+ ExplainPropertyText("Relation Name", objectname, es);
+ ExplainPropertyText("Schema", namespace, es);
+ ExplainPropertyText("Alias", refname, es);
+ }
+ }
+
+ /*
+ * Close a group for foreign modification
+ */
+ void
+ ExplainCloseForeignModifyGroup(ModifyTable *plan, int subplan_index,
+ ExplainState *es)
+ {
+ Assert(subplan_index > 0);
+
+ /* Undo the indentation we added in text format */
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ es->indent--;
+
+ /* Close a group */
+ ExplainCloseGroup("Foreign Modification", "Foreign Modification", true, es);
+ }
+
+ /*
* Show extra information for a ModifyTable node
*/
static void
show_modifytable_info(ModifyTableState *mtstate, ExplainState *es)
{
! ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
! int j;
! for (j = 0; j < mtstate->mt_nplans; j++)
{
! ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
! FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
! /*
! * If the target relation is a foreign table, call its FDW to display
! * whatever additional fields it wants to.
! */
! if (fdwroutine != NULL &&
! fdwroutine->ExplainForeignModify != NULL)
! {
! List *fdw_private = (List *) list_nth(plan->fdwPrivLists, j);
!
! fdwroutine->ExplainForeignModify(mtstate,
! resultRelInfo,
! fdw_private,
! j,
! es);
! }
}
}
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 318,324 **** 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,
--- 318,325 ----
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,
***************
*** 568,573 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
--- 569,598 ----
stmt->relation->relpersistence,
&inheritOids, &old_constraints, &parentOidCount);
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * Don't allow a foreign table to inherit from parents that have OID
+ * system columns.
+ */
+ if (parentOidCount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot inherit from relation with OIDs")));
+
+ /*
+ * Reset the storage parameter for inherited attributes that have
+ * non-default values.
+ */
+ foreach(listptr, schema)
+ {
+ ColumnDef *colDef = lfirst(listptr);
+
+ if (colDef->storage != 0)
+ colDef->storage = 0;
+ }
+ }
+
/*
* Create a tuple descriptor from the relation schema. Note that this
* deals with column names, types, and NOT NULL constraints, but not
***************
*** 1025,1030 **** ExecuteTruncate(TruncateStmt *stmt)
--- 1050,1061 ----
/* find_all_inheritors already got lock */
rel = heap_open(childrelid, NoLock);
+ /* ignore if the rel is a foreign table */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ heap_close(rel, NoLock);
+ continue;
+ }
truncate_check_rel(rel);
rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
***************
*** 3102,3125 **** 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;
--- 3133,3160 ----
* 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;
***************
*** 3132,3138 **** 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;
--- 3167,3174 ----
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;
***************
*** 3254,3264 **** 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;
--- 3290,3306 ----
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);
pass = AT_PASS_MISC;
***************
*** 3292,3298 **** 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 */
case AT_EnableRowSecurity:
--- 3334,3339 ----
***************
*** 4292,4298 **** 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
--- 4333,4340 ----
*/
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
***************
*** 4320,4327 **** 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);
}
}
--- 4362,4373 ----
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);
}
}
***************
*** 4611,4617 **** 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);
--- 4657,4663 ----
/* 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);
***************
*** 4907,4912 **** ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4953,4963 ----
/* 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 */
***************
*** 5507,5513 **** 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
--- 5558,5564 ----
/* 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
***************
*** 5899,5905 **** 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
--- 5950,5956 ----
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/*
* Call AddRelationNewConstraints to do the work, making sure it works on
***************
*** 5910,5918 **** 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 */
--- 5961,5977 ----
* 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 */
***************
*** 7399,7405 **** 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);
--- 7458,7464 ----
/* 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);
***************
*** 7734,7740 **** 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,
--- 7793,7802 ----
* 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/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 817,826 **** InitPlan(QueryDesc *queryDesc, int eflags)
--- 817,832 ----
break;
case ROW_MARK_COPY:
/* there's no real table here ... */
+ /* if child rel, it should be foreign; get tableoid */
+ if (rc->rti != rc->prti)
+ relid = getrelid(rc->rti, rangeTable);
+ else
+ relid = InvalidOid;
relation = NULL;
break;
default:
elog(ERROR, "unrecognized markType: %d", rc->markType);
+ relid = InvalidOid;
relation = NULL; /* keep compiler quiet */
break;
}
***************
*** 830,835 **** InitPlan(QueryDesc *queryDesc, int eflags)
--- 836,842 ----
CheckValidRowMarkRel(relation, rc->markType);
erm = (ExecRowMark *) palloc(sizeof(ExecRowMark));
+ erm->relid = relid;
erm->relation = relation;
erm->rti = rc->rti;
erm->prti = rc->prti;
***************
*** 1792,1811 **** ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist)
aerm->rowmark = erm;
/* Look up the resjunk columns associated with this rowmark */
if (erm->relation)
{
Assert(erm->markType != ROW_MARK_COPY);
- /* if child rel, need tableoid */
- if (erm->rti != erm->prti)
- {
- snprintf(resname, sizeof(resname), "tableoid%u", erm->rowmarkId);
- aerm->toidAttNo = ExecFindJunkAttributeInTlist(targetlist,
- resname);
- if (!AttributeNumberIsValid(aerm->toidAttNo))
- elog(ERROR, "could not find junk %s column", resname);
- }
-
/* always need ctid for real relations */
snprintf(resname, sizeof(resname), "ctid%u", erm->rowmarkId);
aerm->ctidAttNo = ExecFindJunkAttributeInTlist(targetlist,
--- 1799,1819 ----
aerm->rowmark = erm;
/* Look up the resjunk columns associated with this rowmark */
+
+ /* if child rel, need tableoid */
+ if (erm->rti != erm->prti)
+ {
+ snprintf(resname, sizeof(resname), "tableoid%u", erm->rowmarkId);
+ aerm->toidAttNo = ExecFindJunkAttributeInTlist(targetlist,
+ resname);
+ if (!AttributeNumberIsValid(aerm->toidAttNo))
+ elog(ERROR, "could not find junk %s column", resname);
+ }
+
if (erm->relation)
{
Assert(erm->markType != ROW_MARK_COPY);
/* always need ctid for real relations */
snprintf(resname, sizeof(resname), "ctid%u", erm->rowmarkId);
aerm->ctidAttNo = ExecFindJunkAttributeInTlist(targetlist,
***************
*** 2256,2286 **** EvalPlanQualFetchRowMarks(EPQState *epqstate)
/* clear any leftover test tuple for this rel */
EvalPlanQualSetTuple(epqstate, erm->rti, NULL);
! if (erm->relation)
{
! Buffer buffer;
! Assert(erm->markType == ROW_MARK_REFERENCE);
! /* if child rel, must check whether it produced this row */
! if (erm->rti != erm->prti)
{
! Oid tableoid;
! datum = ExecGetJunkAttribute(epqstate->origslot,
! aerm->toidAttNo,
! &isNull);
! /* non-locked rels could be on the inside of outer joins */
! if (isNull)
! continue;
! tableoid = DatumGetObjectId(datum);
! if (tableoid != RelationGetRelid(erm->relation))
! {
! /* this child is inactive right now */
! continue;
! }
! }
/* fetch the tuple's ctid */
datum = ExecGetJunkAttribute(epqstate->origslot,
--- 2264,2294 ----
/* clear any leftover test tuple for this rel */
EvalPlanQualSetTuple(epqstate, erm->rti, NULL);
! /* if child rel, must check whether it produced this row */
! if (erm->rti != erm->prti)
{
! Oid tableoid;
! datum = ExecGetJunkAttribute(epqstate->origslot,
! aerm->toidAttNo,
! &isNull);
! /* non-locked rels could be on the inside of outer joins */
! if (isNull)
! continue;
! tableoid = DatumGetObjectId(datum);
! if (tableoid != erm->relid)
{
! /* this child is inactive right now */
! continue;
! }
! }
! if (erm->relation)
! {
! Buffer buffer;
! Assert(erm->markType == ROW_MARK_REFERENCE);
/* fetch the tuple's ctid */
datum = ExecGetJunkAttribute(epqstate->origslot,
*** a/src/backend/executor/nodeLockRows.c
--- b/src/backend/executor/nodeLockRows.c
***************
*** 93,99 **** lnext:
elog(ERROR, "tableoid is NULL");
tableoid = DatumGetObjectId(datum);
! if (tableoid != RelationGetRelid(erm->relation))
{
/* this child is inactive right now */
ItemPointerSetInvalid(&(erm->curCtid));
--- 93,99 ----
elog(ERROR, "tableoid is NULL");
tableoid = DatumGetObjectId(datum);
! if (tableoid != erm->relid)
{
/* this child is inactive right now */
ItemPointerSetInvalid(&(erm->curCtid));
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2025,2030 **** _copyRangeTblEntry(const RangeTblEntry *from)
--- 2025,2031 ----
COPY_NODE_FIELD(eref);
COPY_SCALAR_FIELD(lateral);
COPY_SCALAR_FIELD(inh);
+ COPY_SCALAR_FIELD(hasForeignChildren);
COPY_SCALAR_FIELD(inFromCl);
COPY_SCALAR_FIELD(requiredPerms);
COPY_SCALAR_FIELD(checkAsUser);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 2343,2348 **** _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
--- 2343,2349 ----
COMPARE_NODE_FIELD(eref);
COMPARE_SCALAR_FIELD(lateral);
COMPARE_SCALAR_FIELD(inh);
+ COMPARE_SCALAR_FIELD(hasForeignChildren);
COMPARE_SCALAR_FIELD(inFromCl);
COMPARE_SCALAR_FIELD(requiredPerms);
COMPARE_SCALAR_FIELD(checkAsUser);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 2453,2458 **** _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
--- 2453,2459 ----
WRITE_BOOL_FIELD(lateral);
WRITE_BOOL_FIELD(inh);
+ WRITE_BOOL_FIELD(hasForeignChildren);
WRITE_BOOL_FIELD(inFromCl);
WRITE_UINT_FIELD(requiredPerms);
WRITE_OID_FIELD(checkAsUser);
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 1249,1254 **** _readRangeTblEntry(void)
--- 1249,1255 ----
READ_BOOL_FIELD(lateral);
READ_BOOL_FIELD(inh);
+ READ_BOOL_FIELD(hasForeignChildren);
READ_BOOL_FIELD(inFromCl);
READ_UINT_FIELD(requiredPerms);
READ_OID_FIELD(checkAsUser);
*** a/src/backend/optimizer/prep/preptlist.c
--- b/src/backend/optimizer/prep/preptlist.c
***************
*** 111,116 **** preprocess_targetlist(PlannerInfo *root, List *tlist)
--- 111,118 ----
/* if parent of inheritance tree, need the tableoid too */
if (rc->isParent)
{
+ RangeTblEntry *rte = rt_fetch(rc->rti, parse->rtable);
+
var = makeVar(rc->rti,
TableOidAttributeNumber,
OIDOID,
***************
*** 123,128 **** preprocess_targetlist(PlannerInfo *root, List *tlist)
--- 125,146 ----
pstrdup(resname),
true);
tlist = lappend(tlist, tle);
+
+ /* if containing foreign tables, fetch the whole row too */
+ if (rte->hasForeignChildren)
+ {
+ /* Not a table, so we need the whole row as a junk var */
+ var = makeWholeRowVar(rt_fetch(rc->rti, range_table),
+ rc->rti,
+ 0,
+ false);
+ snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId);
+ tle = makeTargetEntry((Expr *) var,
+ list_length(tlist) + 1,
+ pstrdup(resname),
+ true);
+ tlist = lappend(tlist, tle);
+ }
}
}
else
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 1237,1242 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1237,1243 ----
LOCKMODE lockmode;
List *inhOIDs;
List *appinfos;
+ bool hasForeignChildren;
ListCell *l;
/* Does RT entry allow inheritance? */
***************
*** 1309,1314 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1310,1316 ----
/* Scan the inheritance set and expand it */
appinfos = NIL;
+ hasForeignChildren = false;
foreach(l, inhOIDs)
{
Oid childOID = lfirst_oid(l);
***************
*** 1338,1348 **** 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);
--- 1340,1351 ----
/*
* 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);
***************
*** 1388,1400 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
newrc->rti = childRTindex;
newrc->prti = rti;
newrc->rowmarkId = oldrc->rowmarkId;
! newrc->markType = oldrc->markType;
newrc->waitPolicy = oldrc->waitPolicy;
newrc->isParent = false;
root->rowMarks = lappend(root->rowMarks, newrc);
}
/* Close child relations, but keep locks */
if (childOID != parentOID)
heap_close(newrelation, NoLock);
--- 1391,1409 ----
newrc->rti = childRTindex;
newrc->prti = rti;
newrc->rowmarkId = oldrc->rowmarkId;
! if (childrte->relkind == RELKIND_FOREIGN_TABLE)
! newrc->markType = ROW_MARK_COPY;
! else
! newrc->markType = oldrc->markType;
newrc->waitPolicy = oldrc->waitPolicy;
newrc->isParent = false;
root->rowMarks = lappend(root->rowMarks, newrc);
}
+ if (childrte->relkind == RELKIND_FOREIGN_TABLE)
+ hasForeignChildren = true;
+
/* Close child relations, but keep locks */
if (childOID != parentOID)
heap_close(newrelation, NoLock);
***************
*** 1416,1421 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1425,1433 ----
/* Otherwise, OK to add to root->append_rel_list */
root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+
+ /* And set rte->hasForeignChildren */
+ rte->hasForeignChildren = hasForeignChildren;
}
/*
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 4363,4394 **** 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;
}
;
--- 4363,4394 ----
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/include/commands/explain.h
--- b/src/include/commands/explain.h
***************
*** 91,94 **** extern void ExplainPropertyLong(const char *qlabel, long value,
--- 91,99 ----
extern void ExplainPropertyFloat(const char *qlabel, double value, int ndigits,
ExplainState *es);
+ extern void ExplainOpenForeignModifyGroup(ModifyTable *plan, int subplan_index,
+ ExplainState *es);
+ extern void ExplainCloseForeignModifyGroup(ModifyTable *plan, int subplan_index,
+ ExplainState *es);
+
#endif /* EXPLAIN_H */
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 424,429 **** typedef struct EState
--- 424,430 ----
*/
typedef struct ExecRowMark
{
+ Oid relid; /* relation Oid */
Relation relation; /* opened and suitably locked relation */
Index rti; /* its range table index */
Index prti; /* parent range table index, if child */
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 812,817 **** typedef struct RangeTblEntry
--- 812,819 ----
Alias *eref; /* expanded reference names */
bool lateral; /* subquery, function, or values is LATERAL? */
bool inh; /* inheritance requested? */
+ bool hasForeignChildren; /* does inheritance set include
+ * any foreign tables? */
bool inFromCl; /* present in FROM clause? */
AclMode requiredPerms; /* bitmask of required access permissions */
Oid checkAsUser; /* if valid, check access as this role */
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 851,856 **** typedef enum RowMarkType
--- 851,858 ----
* The tableoid column is only present for an inheritance hierarchy.
* When markType == ROW_MARK_COPY, there is instead a single column named
* wholerow%u whole-row value of relation
+ * The wholerow column is also present for an inheritance hierarchy that
+ * contains foreign tables.
* In all three cases, %u represents the rowmark ID number (rowmarkId).
* This number is unique within a plan tree, except that child relation
* entries copy their parent's rowmarkId. (Assigning unique numbers
*** a/src/test/regress/expected/foreign_data.out
--- b/src/test/regress/expected/foreign_data.out
***************
*** 1234,1239 **** DROP TRIGGER trigtest_before_row ON foreign_schema.foreign_table_1;
--- 1234,1644 ----
DROP TRIGGER trigtest_after_stmt ON foreign_schema.foreign_table_1;
DROP TRIGGER trigtest_after_row ON foreign_schema.foreign_table_1;
DROP FUNCTION dummy_trigger();
+ -- Table inheritance
+ CREATE TABLE pt1 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ );
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ DROP FOREIGN TABLE ft2;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+
+ CREATE FOREIGN TABLE ft2 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+
+ ALTER FOREIGN TABLE ft2 INHERIT pt1;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ CREATE VIEW v1 (c1, c2, c3) AS SELECT 1, 'v1'::text, '1994-01-01'::date;
+ ALTER TABLE v1 INHERIT pt1; -- ERROR
+ ERROR: "v1" is not a table or foreign table
+ DROP VIEW v1;
+ -- add attributes recursively
+ ALTER TABLE pt1 ADD COLUMN c4 integer;
+ ALTER TABLE pt1 ADD COLUMN c5 integer DEFAULT 0;
+ ALTER TABLE pt1 ADD COLUMN c6 integer;
+ ALTER TABLE pt1 ADD COLUMN c7 integer NOT NULL;
+ ALTER TABLE pt1 ADD COLUMN c8 integer;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ c4 | integer | | plain | |
+ c5 | integer | default 0 | plain | |
+ c6 | integer | | plain | |
+ c7 | integer | not null | plain | |
+ c8 | integer | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ c4 | integer | | | plain | |
+ c5 | integer | default 0 | | plain | |
+ c6 | integer | | | plain | |
+ c7 | integer | not null | | plain | |
+ c8 | integer | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- alter attributes recursively
+ ALTER TABLE pt1 ALTER COLUMN c4 SET DEFAULT 0;
+ ALTER TABLE pt1 ALTER COLUMN c5 DROP DEFAULT;
+ ALTER TABLE pt1 ALTER COLUMN c6 SET NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN c7 DROP NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN c8 TYPE char(10) USING '0'; -- ERROR
+ ERROR: "ft2" is not a table
+ ALTER TABLE pt1 ALTER COLUMN c8 TYPE char(10);
+ ALTER TABLE pt1 ALTER COLUMN c8 SET DATA TYPE text;
+ ALTER TABLE pt1 ALTER COLUMN c1 SET STATISTICS 10000;
+ ALTER TABLE pt1 ALTER COLUMN c1 SET (n_distinct = 100);
+ ALTER TABLE pt1 ALTER COLUMN c8 SET STATISTICS -1;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ c4 | integer | default 0 | plain | |
+ c5 | integer | | plain | |
+ c6 | integer | not null | plain | |
+ c7 | integer | | plain | |
+ c8 | text | | extended | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | 10000 |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ c4 | integer | default 0 | | plain | |
+ c5 | integer | | | plain | |
+ c6 | integer | not null | | plain | |
+ c7 | integer | | | plain | |
+ c8 | text | | | extended | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- drop attributes recursively
+ ALTER TABLE pt1 DROP COLUMN c4;
+ ALTER TABLE pt1 DROP COLUMN c5;
+ ALTER TABLE pt1 DROP COLUMN c6;
+ ALTER TABLE pt1 DROP COLUMN c7;
+ ALTER TABLE pt1 DROP COLUMN c8;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | 10000 |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- cannot change storage mode for an attribute of foreign tables
+ ALTER TABLE pt1 ALTER COLUMN c2 SET STORAGE EXTERNAL;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | external | |
+ c3 | date | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | 10000 |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ DROP FOREIGN TABLE ft2;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | external | |
+ c3 | date | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ ALTER TABLE pt1 ALTER COLUMN c2 SET STORAGE EXTENDED;
+ -- add constraints recursively
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk1 CHECK (c1 > 0) NO INHERIT;
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '');
+ -- connoinherit should be true for NO INHERIT constraint
+ SELECT pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pgc.connoinherit FROM pg_class AS pc INNER JOIN pg_constraint AS pgc ON (pgc.conrelid = pc.oid) WHERE pc.relname = 'pt1' order by 1,2;
+ relname | conname | contype | conislocal | coninhcount | connoinherit
+ ---------+---------+---------+------------+-------------+--------------
+ pt1 | pt1chk1 | c | t | 0 | t
+ pt1 | pt1chk2 | c | t | 0 | f
+ (2 rows)
+
+ -- child does not inherit NO INHERIT constraints
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Check constraints:
+ "pt1chk1" CHECK (c1 > 0) NO INHERIT
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ DROP FOREIGN TABLE ft2;
+ CREATE FOREIGN TABLE ft2 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ -- child must have parent's INHERIT constraints
+ ALTER FOREIGN TABLE ft2 INHERIT pt1; -- ERROR
+ ERROR: child table is missing constraint "pt1chk2"
+ ALTER FOREIGN TABLE ft2 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '');
+ ALTER FOREIGN TABLE ft2 INHERIT pt1;
+ -- child does not inherit NO INHERIT constraints
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Check constraints:
+ "pt1chk1" CHECK (c1 > 0) NO INHERIT
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- drop constraints recursively
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk1 CASCADE;
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk2 CASCADE;
+ -- NOT VALID should be ignored for foreign tables
+ INSERT INTO pt1 VALUES (1, 'pt1'::text, '1994-01-01'::date);
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '') NOT VALID;
+ NOTICE: merging constraint "pt1chk2" with inherited definition
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text) NOT VALID
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- VALIDATE CONSTRAINT should work by ignoring foreign tables
+ ALTER TABLE pt1 VALIDATE CONSTRAINT pt1chk2;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- cannot add an OID system column to foreign tables
+ ALTER TABLE pt1 SET WITH OIDS; -- ERROR
+ ERROR: cannot add OID column to foreign table "ft2"
+ DROP FOREIGN TABLE ft2;
+ ALTER TABLE pt1 SET WITH OIDS;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
+ ERROR: cannot inherit from relation with OIDs
+ ALTER TABLE pt1 SET WITHOUT OIDS;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ -- changes name of an attribute recursively
+ ALTER TABLE pt1 RENAME COLUMN c1 TO f1;
+ ALTER TABLE pt1 RENAME COLUMN c2 TO f2;
+ ALTER TABLE pt1 RENAME COLUMN c3 TO f3;
+ -- changes name of a constraint recursively
+ ALTER TABLE pt1 RENAME CONSTRAINT pt1chk2 TO f2_check;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ f1 | integer | not null | plain | 10000 |
+ f2 | text | | extended | |
+ f3 | date | | plain | |
+ Check constraints:
+ "f2_check" CHECK (f2 <> ''::text)
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ f1 | integer | not null | | plain | |
+ f2 | text | | | extended | |
+ f3 | date | | | plain | |
+ Check constraints:
+ "f2_check" CHECK (f2 <> ''::text)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- TRUNCATE should work by ignoring foreign tables
+ TRUNCATE pt1;
+ TRUNCATE ft2; -- ERROR
+ ERROR: "ft2" is not a table
+ DROP TABLE pt1 CASCADE;
+ NOTICE: drop cascades to foreign table ft2
-- IMPORT FOREIGN SCHEMA
IMPORT FOREIGN SCHEMA s1 FROM SERVER s9 INTO public; -- ERROR
ERROR: foreign-data wrapper "foo" has no handler
*** a/src/test/regress/sql/foreign_data.sql
--- b/src/test/regress/sql/foreign_data.sql
***************
*** 536,541 **** DROP TRIGGER trigtest_after_row ON foreign_schema.foreign_table_1;
--- 536,670 ----
DROP FUNCTION dummy_trigger();
+ -- Table inheritance
+ CREATE TABLE pt1 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ );
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ \d+ ft2
+ DROP FOREIGN TABLE ft2;
+ \d+ pt1
+ CREATE FOREIGN TABLE ft2 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ ft2
+ ALTER FOREIGN TABLE ft2 INHERIT pt1;
+ \d+ pt1
+ \d+ ft2
+ CREATE VIEW v1 (c1, c2, c3) AS SELECT 1, 'v1'::text, '1994-01-01'::date;
+ ALTER TABLE v1 INHERIT pt1; -- ERROR
+ DROP VIEW v1;
+
+ -- add attributes recursively
+ ALTER TABLE pt1 ADD COLUMN c4 integer;
+ ALTER TABLE pt1 ADD COLUMN c5 integer DEFAULT 0;
+ ALTER TABLE pt1 ADD COLUMN c6 integer;
+ ALTER TABLE pt1 ADD COLUMN c7 integer NOT NULL;
+ ALTER TABLE pt1 ADD COLUMN c8 integer;
+ \d+ pt1
+ \d+ ft2
+
+ -- alter attributes recursively
+ ALTER TABLE pt1 ALTER COLUMN c4 SET DEFAULT 0;
+ ALTER TABLE pt1 ALTER COLUMN c5 DROP DEFAULT;
+ ALTER TABLE pt1 ALTER COLUMN c6 SET NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN c7 DROP NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN c8 TYPE char(10) USING '0'; -- ERROR
+ ALTER TABLE pt1 ALTER COLUMN c8 TYPE char(10);
+ ALTER TABLE pt1 ALTER COLUMN c8 SET DATA TYPE text;
+ ALTER TABLE pt1 ALTER COLUMN c1 SET STATISTICS 10000;
+ ALTER TABLE pt1 ALTER COLUMN c1 SET (n_distinct = 100);
+ ALTER TABLE pt1 ALTER COLUMN c8 SET STATISTICS -1;
+ \d+ pt1
+ \d+ ft2
+
+ -- drop attributes recursively
+ ALTER TABLE pt1 DROP COLUMN c4;
+ ALTER TABLE pt1 DROP COLUMN c5;
+ ALTER TABLE pt1 DROP COLUMN c6;
+ ALTER TABLE pt1 DROP COLUMN c7;
+ ALTER TABLE pt1 DROP COLUMN c8;
+ \d+ pt1
+ \d+ ft2
+
+ -- cannot change storage mode for an attribute of foreign tables
+ ALTER TABLE pt1 ALTER COLUMN c2 SET STORAGE EXTERNAL;
+ \d+ pt1
+ \d+ ft2
+ DROP FOREIGN TABLE ft2;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ \d+ ft2
+ ALTER TABLE pt1 ALTER COLUMN c2 SET STORAGE EXTENDED;
+
+ -- add constraints recursively
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk1 CHECK (c1 > 0) NO INHERIT;
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '');
+ -- connoinherit should be true for NO INHERIT constraint
+ SELECT pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pgc.connoinherit FROM pg_class AS pc INNER JOIN pg_constraint AS pgc ON (pgc.conrelid = pc.oid) WHERE pc.relname = 'pt1' order by 1,2;
+ -- child does not inherit NO INHERIT constraints
+ \d+ pt1
+ \d+ ft2
+ DROP FOREIGN TABLE ft2;
+ CREATE FOREIGN TABLE ft2 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ -- child must have parent's INHERIT constraints
+ ALTER FOREIGN TABLE ft2 INHERIT pt1; -- ERROR
+ ALTER FOREIGN TABLE ft2 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '');
+ ALTER FOREIGN TABLE ft2 INHERIT pt1;
+ -- child does not inherit NO INHERIT constraints
+ \d+ pt1
+ \d+ ft2
+
+ -- drop constraints recursively
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk1 CASCADE;
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk2 CASCADE;
+
+ -- NOT VALID should be ignored for foreign tables
+ INSERT INTO pt1 VALUES (1, 'pt1'::text, '1994-01-01'::date);
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '') NOT VALID;
+ \d+ pt1
+ \d+ ft2
+ -- VALIDATE CONSTRAINT should work by ignoring foreign tables
+ ALTER TABLE pt1 VALIDATE CONSTRAINT pt1chk2;
+ \d+ pt1
+ \d+ ft2
+
+ -- cannot add an OID system column to foreign tables
+ ALTER TABLE pt1 SET WITH OIDS; -- ERROR
+ DROP FOREIGN TABLE ft2;
+ ALTER TABLE pt1 SET WITH OIDS;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
+ ALTER TABLE pt1 SET WITHOUT OIDS;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+
+ -- changes name of an attribute recursively
+ ALTER TABLE pt1 RENAME COLUMN c1 TO f1;
+ ALTER TABLE pt1 RENAME COLUMN c2 TO f2;
+ ALTER TABLE pt1 RENAME COLUMN c3 TO f3;
+ -- changes name of a constraint recursively
+ ALTER TABLE pt1 RENAME CONSTRAINT pt1chk2 TO f2_check;
+ \d+ pt1
+ \d+ ft2
+
+ -- TRUNCATE should work by ignoring foreign tables
+ TRUNCATE pt1;
+ TRUNCATE ft2; -- ERROR
+
+ DROP TABLE pt1 CASCADE;
+
-- IMPORT FOREIGN SCHEMA
IMPORT FOREIGN SCHEMA s1 FROM SERVER s9 INTO public; -- ERROR
IMPORT FOREIGN SCHEMA s1 LIMIT TO (t1) FROM SERVER s9 INTO public; --ERROR
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2015-01-15 07:35 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 2 replies; 179+ messages in thread
From: Etsuro Fujita @ 2015-01-15 07:35 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On 2014/12/23 0:36, Tom Lane wrote:
> Yeah, we need to do something about the PlanRowMark data structure.
> Aside from the pre-existing issue in postgres_fdw, we need to fix it
> to support inheritance trees in which more than one rowmark method
> is being used. That rte.hasForeignChildren thing is a crock,
> The idea I'd had about that was to convert the markType field into a
> bitmask, so that a parent node's markType could represent the logical
> OR of the rowmark methods being used by all its children. I've not
> attempted to code this up though, and probably won't get to it until
> after Christmas. One thing that's not clear is what should happen
> with ExecRowMark.
As I said before, that seems to me like a good idea. So I'll update the
patch based on that if you're okey with it. Or you've found any problem
concerning the above idea?
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2015-02-13 07:03 Michael Paquier <michael.paquier@gmail.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 0 replies; 179+ messages in thread
From: Michael Paquier @ 2015-02-13 07:03 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On Thu, Jan 15, 2015 at 4:35 PM, Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
wrote:
> As I said before, that seems to me like a good idea. So I'll update the
> patch based on that if you're okey with it. Or you've found any problem
> concerning the above idea?
>
Patch moved to CF 2015-02 with same status, "Ready for committer".
--
Michael
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2015-02-20 06:59 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2015-02-20 06:59 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On 2015/01/15 16:35, Etsuro Fujita wrote:
> On 2014/12/23 0:36, Tom Lane wrote:
>> Yeah, we need to do something about the PlanRowMark data structure.
>> Aside from the pre-existing issue in postgres_fdw, we need to fix it
>> to support inheritance trees in which more than one rowmark method
>> is being used. That rte.hasForeignChildren thing is a crock,
>
>> The idea I'd had about that was to convert the markType field into a
>> bitmask, so that a parent node's markType could represent the logical
>> OR of the rowmark methods being used by all its children.
> As I said before, that seems to me like a good idea. So I'll update the
> patch based on that if you're okey with it.
Done based on your ideas: (a) add a field to PlanRowMark to record the
original lock strength to fix the postgres_fdw issue and (b) convert its
markType field into a bitmask to support the inheritance trees. I think
that both work well and that (a) is useful for the other places.
Patch attached, which has been created on top of [1].
Best regards,
Etsuro Fujita
[1] http://www.postgresql.org/message-id/54BCBBF8.3020103@lab.ntt.co.jp
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] fdw-inh-8.patch (108.3K, ../../54E6DB4E.40801@lab.ntt.co.jp/2-fdw-inh-8.patch)
download | inline diff:
*** a/contrib/file_fdw/input/file_fdw.source
--- b/contrib/file_fdw/input/file_fdw.source
***************
*** 148,153 **** EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
--- 148,167 ----
SELECT * FROM agg_csv WHERE a < 0;
RESET constraint_exclusion;
+ -- table inheritance tests
+ CREATE TABLE agg (a int2, b float4);
+ ALTER FOREIGN TABLE agg_csv INHERIT agg;
+ SELECT tableoid::regclass, * FROM agg;
+ SELECT tableoid::regclass, * FROM agg_csv;
+ SELECT tableoid::regclass, * FROM ONLY agg;
+ -- updates aren't supported
+ UPDATE agg SET a = 1;
+ DELETE FROM agg WHERE a = 100;
+ -- but this should be ignored
+ SELECT tableoid::regclass, * FROM agg FOR UPDATE;
+ ALTER FOREIGN TABLE agg_csv NO INHERIT agg;
+ DROP TABLE agg;
+
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/file_fdw/output/file_fdw.source
--- b/contrib/file_fdw/output/file_fdw.source
***************
*** 249,254 **** SELECT * FROM agg_csv WHERE a < 0;
--- 249,294 ----
(0 rows)
RESET constraint_exclusion;
+ -- table inheritance tests
+ CREATE TABLE agg (a int2, b float4);
+ ALTER FOREIGN TABLE agg_csv INHERIT agg;
+ SELECT tableoid::regclass, * FROM agg;
+ tableoid | a | b
+ ----------+-----+---------
+ agg_csv | 100 | 99.097
+ agg_csv | 0 | 0.09561
+ agg_csv | 42 | 324.78
+ (3 rows)
+
+ SELECT tableoid::regclass, * FROM agg_csv;
+ tableoid | a | b
+ ----------+-----+---------
+ agg_csv | 100 | 99.097
+ agg_csv | 0 | 0.09561
+ agg_csv | 42 | 324.78
+ (3 rows)
+
+ SELECT tableoid::regclass, * FROM ONLY agg;
+ tableoid | a | b
+ ----------+---+---
+ (0 rows)
+
+ -- updates aren't supported
+ UPDATE agg SET a = 1;
+ ERROR: cannot update foreign table "agg_csv"
+ DELETE FROM agg WHERE a = 100;
+ ERROR: cannot delete from foreign table "agg_csv"
+ -- but this should be ignored
+ SELECT tableoid::regclass, * FROM agg FOR UPDATE;
+ tableoid | a | b
+ ----------+-----+---------
+ agg_csv | 100 | 99.097
+ agg_csv | 0 | 0.09561
+ agg_csv | 42 | 324.78
+ (3 rows)
+
+ ALTER FOREIGN TABLE agg_csv NO INHERIT agg;
+ DROP TABLE agg;
-- privilege tests
SET ROLE file_fdw_superuser;
SELECT * FROM agg_text ORDER BY a;
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 3027,3032 **** NOTICE: NEW: (13,"test triggered !")
--- 3027,3544 ----
(1 row)
-- ===================================================================
+ -- test inheritance features
+ -- ===================================================================
+ CREATE TABLE a (aa TEXT);
+ CREATE TABLE loct (aa TEXT, bb TEXT);
+ CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a)
+ SERVER loopback OPTIONS (table_name 'loct');
+ INSERT INTO a(aa) VALUES('aaa');
+ INSERT INTO a(aa) VALUES('aaaa');
+ INSERT INTO a(aa) VALUES('aaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaaa');
+ INSERT INTO b(aa) VALUES('bbb');
+ INSERT INTO b(aa) VALUES('bbbb');
+ INSERT INTO b(aa) VALUES('bbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbbb');
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | aaa
+ a | aaaa
+ a | aaaaa
+ a | aaaaaa
+ a | aaaaaaa
+ a | aaaaaaaa
+ b | bbb
+ b | bbbb
+ b | bbbbb
+ b | bbbbbb
+ b | bbbbbbb
+ b | bbbbbbbb
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----------+----
+ b | bbb |
+ b | bbbb |
+ b | bbbbb |
+ b | bbbbbb |
+ b | bbbbbbb |
+ b | bbbbbbbb |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | aaa
+ a | aaaa
+ a | aaaaa
+ a | aaaaaa
+ a | aaaaaaa
+ a | aaaaaaaa
+ (6 rows)
+
+ UPDATE a SET aa='zzzzzz' WHERE aa LIKE 'aaa%';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ b | bbb
+ b | bbbb
+ b | bbbbb
+ b | bbbbbb
+ b | bbbbbbb
+ b | bbbbbbbb
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----------+----
+ b | bbb |
+ b | bbbb |
+ b | bbbbb |
+ b | bbbbbb |
+ b | bbbbbbb |
+ b | bbbbbbbb |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ (6 rows)
+
+ UPDATE b SET aa='new';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+-----+----
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+--------
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ a | zzzzzz
+ (6 rows)
+
+ UPDATE a SET aa='new';
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+-----
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ b | new
+ (12 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+-----+----
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ b | new |
+ (6 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+-----
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ a | new
+ (6 rows)
+
+ DELETE FROM a;
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----
+ (0 rows)
+
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ relname | aa | bb
+ ---------+----+----
+ (0 rows)
+
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+ relname | aa
+ ---------+----
+ (0 rows)
+
+ DROP TABLE a CASCADE;
+ NOTICE: drop cascades to foreign table b
+ DROP TABLE loct;
+ -- Check SELECT FOR UPDATE/SHARE with an inherited source table
+ create table loct1 (f1 int, f2 int, f3 int);
+ create table loct2 (f1 int, f2 int, f3 int);
+ create table foo (f1 int, f2 int);
+ create foreign table foo2 (f3 int) inherits (foo)
+ server loopback options (table_name 'loct1');
+ create table bar (f1 int, f2 int);
+ create foreign table bar2 (f3 int) inherits (bar)
+ server loopback options (table_name 'loct2');
+ insert into foo values(1,1);
+ insert into foo values(3,3);
+ insert into foo2 values(2,2,2);
+ insert into foo2 values(3,3,3);
+ insert into bar values(1,1);
+ insert into bar values(2,2);
+ insert into bar values(3,3);
+ insert into bar values(4,4);
+ insert into bar2 values(1,1,1);
+ insert into bar2 values(2,2,2);
+ insert into bar2 values(3,3,3);
+ insert into bar2 values(4,4,4);
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for update;
+ QUERY PLAN
+ ----------------------------------------------------------------------------------------------
+ LockRows
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ -> Hash Join
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Append
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.ctid, bar2.tableoid, bar2.*
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (22 rows)
+
+ select * from bar where f1 in (select f1 from foo) for update;
+ f1 | f2
+ ----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ (6 rows)
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for share;
+ QUERY PLAN
+ ----------------------------------------------------------------------------------------------
+ LockRows
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ -> Hash Join
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Append
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.ctid, bar2.tableoid, bar2.*
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (22 rows)
+
+ select * from bar where f1 in (select f1 from foo) for share;
+ f1 | f2
+ ----+----
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ (6 rows)
+
+ -- Check UPDATE with inherited target and an inherited source table
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ QUERY PLAN
+ ---------------------------------------------------------------------------------------------
+ Update on public.bar
+ For public.bar2
+ Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
+ -> Hash Join
+ Output: bar.f1, (bar.f2 + 100), bar.ctid, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar.f1 = foo.f1)
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ -> Hash Join
+ Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.tableoid, foo.*
+ Hash Cond: (bar2.f1 = foo.f1)
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Hash
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> HashAggregate
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ Group Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+ -> Foreign Scan on public.foo2
+ Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+ (36 rows)
+
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+ relname | f1 | f2
+ ---------+----+-----
+ bar | 1 | 101
+ bar | 2 | 102
+ bar | 3 | 103
+ bar | 4 | 4
+ bar2 | 1 | 101
+ bar2 | 2 | 102
+ bar2 | 3 | 103
+ bar2 | 4 | 4
+ (8 rows)
+
+ -- Check UPDATE with inherited target and an appendrel subquery
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ QUERY PLAN
+ --------------------------------------------------------------------------------------
+ Update on public.bar
+ For public.bar2
+ Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
+ -> Hash Join
+ Output: bar.f1, (bar.f2 + 100), bar.ctid, (ROW(foo.f1))
+ Hash Cond: (foo.f1 = bar.f1)
+ -> Append
+ -> Seq Scan on public.foo
+ Output: ROW(foo.f1), foo.f1
+ -> Foreign Scan on public.foo2
+ Output: ROW(foo2.f1), foo2.f1
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Seq Scan on public.foo foo_1
+ Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3)
+ -> Foreign Scan on public.foo2 foo2_1
+ Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3)
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Hash
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Seq Scan on public.bar
+ Output: bar.f1, bar.f2, bar.ctid
+ -> Merge Join
+ Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, (ROW(foo.f1))
+ Merge Cond: (bar2.f1 = foo.f1)
+ -> Sort
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Sort Key: bar2.f1
+ -> Foreign Scan on public.bar2
+ Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+ Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+ -> Sort
+ Output: (ROW(foo.f1)), foo.f1
+ Sort Key: foo.f1
+ -> Append
+ -> Seq Scan on public.foo
+ Output: ROW(foo.f1), foo.f1
+ -> Foreign Scan on public.foo2
+ Output: ROW(foo2.f1), foo2.f1
+ Remote SQL: SELECT f1 FROM public.loct1
+ -> Seq Scan on public.foo foo_1
+ Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3)
+ -> Foreign Scan on public.foo2 foo2_1
+ Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3)
+ Remote SQL: SELECT f1 FROM public.loct1
+ (44 rows)
+
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+ relname | f1 | f2
+ ---------+----+-----
+ bar | 1 | 201
+ bar | 2 | 202
+ bar | 3 | 203
+ bar | 4 | 104
+ bar2 | 1 | 201
+ bar2 | 2 | 202
+ bar2 | 3 | 203
+ bar2 | 4 | 104
+ (8 rows)
+
+ drop table foo cascade;
+ NOTICE: drop cascades to foreign table foo2
+ drop table bar cascade;
+ NOTICE: drop cascades to foreign table bar2
+ drop table loct1;
+ drop table loct2;
+ -- Test that WHERE CURRENT OF is not supported
+ create table ltbl (a int, b text);
+ create table ptbl (a int, b text);
+ create table locc () inherits (ptbl);
+ insert into locc values(1, 'foo');
+ create foreign table remc () inherits (ptbl)
+ server loopback options (table_name 'ltbl');
+ insert into remc values(2, 'bar');
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'foo';
+ fetch from c;
+ ?column?
+ ----------
+ 1
+ (1 row)
+
+ update ptbl set b = null where current of c;
+ ERROR: WHERE CURRENT OF is not supported for this table type
+ rollback;
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'bar';
+ fetch from c;
+ ?column?
+ ----------
+ 1
+ (1 row)
+
+ update ptbl set b = null where current of c;
+ ERROR: WHERE CURRENT OF is not supported for this table type
+ rollback;
+ select * from ptbl;
+ a | b
+ ---+-----
+ 1 | foo
+ 2 | bar
+ (2 rows)
+
+ -- Test TRUNCATE
+ truncate ptbl;
+ select * from ptbl;
+ a | b
+ ---+-----
+ 2 | bar
+ (1 row)
+
+ select * from only ptbl;
+ a | b
+ ---+---
+ (0 rows)
+
+ select * from locc;
+ a | b
+ ---+---
+ (0 rows)
+
+ select * from remc;
+ a | b
+ ---+-----
+ 2 | bar
+ (1 row)
+
+ truncate remc; -- ERROR
+ ERROR: "remc" is not a table
+ drop table ptbl cascade;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to table locc
+ drop cascades to foreign table remc
+ drop table ltbl;
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
CREATE SCHEMA import_source;
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 34,39 ****
--- 34,40 ----
#include "parser/parsetree.h"
#include "utils/builtins.h"
#include "utils/guc.h"
+ #include "utils/lockclausestrength.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"
***************
*** 822,828 **** postgresGetForeignPlan(PlannerInfo *root,
}
else
{
! RowMarkClause *rc = get_parse_rowmark(root->parse, baserel->relid);
if (rc)
{
--- 823,829 ----
}
else
{
! PlanRowMark *rc = get_plan_rowmark(root->rowMarks, baserel->relid);
if (rc)
{
***************
*** 837,842 **** postgresGetForeignPlan(PlannerInfo *root,
--- 838,846 ----
*/
switch (rc->strength)
{
+ case LCS_NOLOCK:
+ /* No locking needed */
+ break;
case LCS_FORKEYSHARE:
case LCS_FORSHARE:
appendStringInfoString(&sql, " FOR SHARE");
***************
*** 1682,1691 **** postgresExplainForeignModify(ModifyTableState *mtstate,
{
if (es->verbose)
{
char *sql = strVal(list_nth(fdw_private,
FdwModifyPrivateUpdateSql));
! ExplainPropertyText("Remote SQL", sql, es);
}
}
--- 1686,1717 ----
{
if (es->verbose)
{
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+ Index rti = list_nth_int(node->resultRelations, subplan_index);
char *sql = strVal(list_nth(fdw_private,
FdwModifyPrivateUpdateSql));
! if (rti != node->nominalRelation)
! {
! /*
! * Inherited UPDATE/DELETE case; show the SQL statement together
! * with the target relation.
! */
! ExplainBeginInheritedForeignModify(node, subplan_index, es);
! ExplainPropertyText("Remote SQL", sql, es);
! ExplainEndInheritedForeignModify(node, subplan_index, es);
! }
! else
! {
! /*
! * Non-inherited normal case.
! *
! * Note: foreign tables can't be the parent of an inheritance set.
! * So if the RT index matches nominalRelation, we are in a non-
! * inherited normal case.
! */
! ExplainPropertyText("Remote SQL", sql, es);
! }
}
}
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 666,671 **** UPDATE rem1 SET f2 = 'testo';
--- 666,822 ----
INSERT INTO rem1(f2) VALUES ('test') RETURNING ctid;
-- ===================================================================
+ -- test inheritance features
+ -- ===================================================================
+
+ CREATE TABLE a (aa TEXT);
+ CREATE TABLE loct (aa TEXT, bb TEXT);
+ CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a)
+ SERVER loopback OPTIONS (table_name 'loct');
+
+ INSERT INTO a(aa) VALUES('aaa');
+ INSERT INTO a(aa) VALUES('aaaa');
+ INSERT INTO a(aa) VALUES('aaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaa');
+ INSERT INTO a(aa) VALUES('aaaaaaaa');
+
+ INSERT INTO b(aa) VALUES('bbb');
+ INSERT INTO b(aa) VALUES('bbbb');
+ INSERT INTO b(aa) VALUES('bbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbb');
+ INSERT INTO b(aa) VALUES('bbbbbbbb');
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE a SET aa='zzzzzz' WHERE aa LIKE 'aaa%';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE b SET aa='new';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ UPDATE a SET aa='new';
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ DELETE FROM a;
+
+ SELECT relname, a.* FROM a, pg_class where a.tableoid = pg_class.oid;
+ SELECT relname, b.* FROM b, pg_class where b.tableoid = pg_class.oid;
+ SELECT relname, a.* FROM ONLY a, pg_class where a.tableoid = pg_class.oid;
+
+ DROP TABLE a CASCADE;
+ DROP TABLE loct;
+
+ -- Check SELECT FOR UPDATE/SHARE with an inherited source table
+ create table loct1 (f1 int, f2 int, f3 int);
+ create table loct2 (f1 int, f2 int, f3 int);
+
+ create table foo (f1 int, f2 int);
+ create foreign table foo2 (f3 int) inherits (foo)
+ server loopback options (table_name 'loct1');
+ create table bar (f1 int, f2 int);
+ create foreign table bar2 (f3 int) inherits (bar)
+ server loopback options (table_name 'loct2');
+
+ insert into foo values(1,1);
+ insert into foo values(3,3);
+ insert into foo2 values(2,2,2);
+ insert into foo2 values(3,3,3);
+ insert into bar values(1,1);
+ insert into bar values(2,2);
+ insert into bar values(3,3);
+ insert into bar values(4,4);
+ insert into bar2 values(1,1,1);
+ insert into bar2 values(2,2,2);
+ insert into bar2 values(3,3,3);
+ insert into bar2 values(4,4,4);
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for update;
+ select * from bar where f1 in (select f1 from foo) for update;
+
+ explain (verbose, costs off)
+ select * from bar where f1 in (select f1 from foo) for share;
+ select * from bar where f1 in (select f1 from foo) for share;
+
+ -- Check UPDATE with inherited target and an inherited source table
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+
+ -- Check UPDATE with inherited target and an appendrel subquery
+ explain (verbose, costs off)
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+ update bar set f2 = f2 + 100
+ from
+ ( select f1 from foo union all select f1+3 from foo ) ss
+ where bar.f1 = ss.f1;
+
+ select tableoid::regclass::text as relname, bar.* from bar order by 1,2;
+
+ drop table foo cascade;
+ drop table bar cascade;
+ drop table loct1;
+ drop table loct2;
+
+ -- Test that WHERE CURRENT OF is not supported
+ create table ltbl (a int, b text);
+
+ create table ptbl (a int, b text);
+ create table locc () inherits (ptbl);
+ insert into locc values(1, 'foo');
+
+ create foreign table remc () inherits (ptbl)
+ server loopback options (table_name 'ltbl');
+ insert into remc values(2, 'bar');
+
+ select * from ptbl;
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'foo';
+ fetch from c;
+ update ptbl set b = null where current of c;
+ rollback;
+
+ select * from ptbl;
+
+ begin;
+ declare c cursor for select 1 from ptbl where b = 'bar';
+ fetch from c;
+ update ptbl set b = null where current of c;
+ rollback;
+
+ select * from ptbl;
+
+ -- Test TRUNCATE
+ truncate ptbl;
+ select * from ptbl;
+ select * from only ptbl;
+ select * from locc;
+ select * from remc;
+ truncate remc; -- ERROR
+
+ drop table ptbl cascade;
+ drop table ltbl;
+
+ -- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
*** a/doc/src/sgml/ddl.sgml
--- b/doc/src/sgml/ddl.sgml
***************
*** 2503,2508 **** VALUES ('Albany', NULL, NULL, 'NY');
--- 2503,2526 ----
further privileges to be granted.
</para>
+ <para>
+ Note that a foreign table can also inherit from more than one parent
+ table (see <xref linkend="ddl-foreign-data"> for an overview of
+ foreign tables).
+ Like normal tables, table inheritance is typically established when
+ the foreign table is created, using the <literal>INHERITS</> clause
+ of the <xref linkend="sql-createforeigntable"> statement.
+ Alternatively, a foreign table which is already defined
+ in a compatible way can have a new parent relationship added, using
+ the <literal>INHERIT</literal> variant of
+ <xref linkend="sql-alterforeigntable">.
+ Similarly an inheritance link can be removed from a child using the
+ <literal>NO INHERIT</literal> variant of <command>ALTER FOREIGN TABLE</>.
+ <command>CREATE FOREIGN TABLE</> and <command>ALTER FOREIGN TABLE</>
+ follow the same rules for duplicate column merging and rejection as
+ <command>CREATE TABLE</> and <command>ALTER TABLE</>, respectively.
+ </para>
+
<sect2 id="ddl-inherit-caveats">
<title>Caveats</title>
***************
*** 2650,2656 **** VALUES ('Albany', NULL, NULL, 'NY');
table of a single parent table. The parent table itself is normally
empty; it exists just to represent the entire data set. You should be
familiar with inheritance (see <xref linkend="ddl-inherit">) before
! attempting to set up partitioning.
</para>
<para>
--- 2668,2677 ----
table of a single parent table. The parent table itself is normally
empty; it exists just to represent the entire data set. You should be
familiar with inheritance (see <xref linkend="ddl-inherit">) before
! attempting to set up partitioning. (The setup and management of
! partitioned tables illustrated in this section assume that each
! partition is a normal table. However, you can do that in a similar way
! for cases where some or all partitions are foreign tables.)
</para>
<para>
***************
*** 2713,2720 **** VALUES ('Albany', NULL, NULL, 'NY');
</para>
<para>
! We will refer to the child tables as partitions, though they
! are in every way normal <productname>PostgreSQL</> tables.
</para>
</listitem>
--- 2734,2741 ----
</para>
<para>
! We will refer to the child tables as partitions, though we assume
! here that they are normal <productname>PostgreSQL</> tables.
</para>
</listitem>
*** a/doc/src/sgml/ref/alter_foreign_table.sgml
--- b/doc/src/sgml/ref/alter_foreign_table.sgml
***************
*** 48,53 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 48,55 ----
ENABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
ENABLE REPLICA TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
ENABLE ALWAYS TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
+ INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+ NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
</synopsis>
***************
*** 188,193 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 190,215 ----
</varlistentry>
<varlistentry>
+ <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form adds the target foreign table as a new child of the specified
+ parent table. The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form removes the target foreign table from the list of children of
+ the specified parent table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>OWNER</literal></term>
<listitem>
<para>
***************
*** 384,389 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 406,421 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ A parent table to associate or de-associate with this foreign table.
+ The parent table must be an ordinary table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">new_owner</replaceable></term>
<listitem>
<para>
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 1011,1016 **** ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
--- 1011,1029 ----
</para>
<para>
+ A recursive <literal>SET STORAGE</literal> operation will make
+ the storage mode unchanged for any column of descendant tables
+ that are foreign.
+ </para>
+
+ <para>
+ A recursive <literal>SET WITH OIDS</literal> operation will be
+ rejected if any of descendant tables is foreign, since it is
+ not permitted to add an <literal>oid</literal> system column to
+ foreign tables.
+ </para>
+
+ <para>
The <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>,
and <literal>TABLESPACE</> actions never recurse to descendant tables;
that is, they always act as though <literal>ONLY</> were specified.
***************
*** 1019,1024 **** ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
--- 1032,1045 ----
</para>
<para>
+ When adding a <literal>CHECK</> constraint with the <literal>NOT VALID
+ </literal> option recursively, the inherited constraint on a descendant
+ table that is foreign will be marked valid without checking consistency
+ with the foreign server. It is the user's resposibility to ensure that
+ the constraint definition matches the remote side.
+ </para>
+
+ <para>
Changing any part of a system catalog table is not permitted.
</para>
*** a/doc/src/sgml/ref/analyze.sgml
--- b/doc/src/sgml/ref/analyze.sgml
***************
*** 200,205 **** ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
--- 200,212 ----
</para>
<para>
+ The inheritance statistics for a parent table that contains one or more
+ children that are foreign tables are collected only when explicitly
+ selected. If any of the foreign table's wrapper does not support
+ <command>ANALYZE</command>, the command prints a warning and does nothing.
+ </para>
+
+ <para>
If the table being analyzed is completely empty, <command>ANALYZE</command>
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
***************
*** 23,28 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 23,29 ----
| <replaceable>table_constraint</replaceable> }
[, ... ]
] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
***************
*** 121,126 **** CHECK ( <replaceable class="PARAMETER">expression</replaceable> )
--- 122,158 ----
</varlistentry>
<varlistentry>
+ <term><literal>INHERITS ( <replaceable>parent_table</replaceable> [, ... ] )</literal></term>
+ <listitem>
+ <para>
+ The optional <literal>INHERITS</> clause specifies a list of
+ tables from which the new foreign table automatically inherits
+ all columns. See the similar form of
+ <xref linkend="sql-createtable"> for more details.
+ </para>
+
+ <para>
+ Note that unlike <command>CREATE TABLE</>, column
+ <literal>STORAGE</> settings are not copied from parent tables,
+ resulting in the copied columns in the new foreign table having
+ type-specific default settings.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="ddl-inherit"> for
+ more information.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>NOT NULL</></term>
<listitem>
<para>
***************
*** 249,254 **** CHECK ( <replaceable class="PARAMETER">expression</replaceable> )
--- 281,292 ----
responsibility to ensure that the constraint definition matches
reality.
</para>
+
+ <para>
+ Since it is not permitted to add an <literal>oid</> system column to
+ foreign tables, the command will be rejected if any of parent tables
+ has an <literal>oid</> system column.
+ </para>
</refsect1>
<refsect1 id="SQL-CREATEFOREIGNTABLE-examples">
*** a/doc/src/sgml/ref/truncate.sgml
--- b/doc/src/sgml/ref/truncate.sgml
***************
*** 179,184 **** TRUNCATE [ TABLE ] [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [
--- 179,189 ----
This is similar to the usual behavior of <function>currval()</> after
a failed transaction.
</para>
+
+ <para>
+ A recursive <command>TRUNCATE</> ignores any descendant tables that are
+ foreign, and applies the operation to descendant tables that are normal.
+ </para>
</refsect1>
<refsect1>
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 56,61 ****
--- 56,69 ----
#include "utils/tqual.h"
+ /* Possible modes for ANALYZE */
+ typedef enum
+ {
+ ANL_MODE_ALL, /* Analyze all relations */
+ ANL_MODE_SINGLE, /* Analyze a specific relation */
+ ANL_MODE_AUTOVACUUM /* Autovacuum worker */
+ } AnalyzeMode;
+
/* Data structure for Algorithm S from Knuth 3.4.2 */
typedef struct
{
***************
*** 81,86 **** typedef struct AnlIndexData
--- 89,95 ----
int default_statistics_target = 100;
/* A few variables that don't seem worth passing around as parameters */
+ static AnalyzeMode anl_mode;
static MemoryContext anl_context = NULL;
static BufferAccessStrategy vac_strategy;
***************
*** 102,107 **** static int acquire_sample_rows(Relation onerel, int elevel,
--- 111,117 ----
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
static int compare_rows(const void *a, const void *b);
+ static bool has_foreign_children(Oid parentOID, List *tableOIDs);
static int acquire_inherited_sample_rows(Relation onerel, int elevel,
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
***************
*** 130,135 **** analyze_rel(Oid relid, VacuumStmt *vacstmt,
--- 140,154 ----
elevel = DEBUG2;
/* Set up static variables */
+ if (IsAutoVacuumWorkerProcess())
+ anl_mode = ANL_MODE_AUTOVACUUM;
+ else
+ {
+ if (!vacstmt->relation)
+ anl_mode = ANL_MODE_ALL;
+ else
+ anl_mode = ANL_MODE_SINGLE;
+ }
vac_strategy = bstrategy;
/*
***************
*** 297,305 **** analyze_rel(Oid relid, VacuumStmt *vacstmt,
* do_analyze_rel() -- analyze one relation, recursively or not
*
* Note that "acquirefunc" is only relevant for the non-inherited case.
! * If we supported foreign tables in inheritance trees,
! * acquire_inherited_sample_rows would need to determine the appropriate
! * acquirefunc for each child table.
*/
static void
do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
--- 316,323 ----
* do_analyze_rel() -- analyze one relation, recursively or not
*
* Note that "acquirefunc" is only relevant for the non-inherited case.
! * For the inherited case, acquire_inherited_sample_rows determines the
! * appropriate acquirefunc for each child table.
*/
static void
do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
***************
*** 1444,1454 **** compare_rows(const void *a, const void *b)
/*
* acquire_inherited_sample_rows -- acquire sample rows from inheritance tree
*
* This has the same API as acquire_sample_rows, except that rows are
* collected from all inheritance children as well as the specified table.
! * We fail and return zero if there are no inheritance children.
*/
static int
acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1462,1512 ----
/*
+ * Detect wether the inheritance tree contains any foreign tables
+ */
+ static bool
+ has_foreign_children(Oid parentOID, List *tableOIDs)
+ {
+ bool found;
+ ListCell *lc;
+
+ /* There are no children */
+ if (list_length(tableOIDs) < 2)
+ return false;
+
+ found = false;
+ foreach(lc, tableOIDs)
+ {
+ Oid childOID = lfirst_oid(lc);
+ Relation childrel;
+
+ /* Parent should not be foreign */
+ if (childOID == parentOID)
+ continue;
+
+ /* We already got the needed lock */
+ childrel = heap_open(childOID, NoLock);
+ if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /* Found it */
+ found = true;
+ }
+ heap_close(childrel, NoLock);
+
+ if (found)
+ return true;
+ }
+ return false;
+ }
+
+ /*
* acquire_inherited_sample_rows -- acquire sample rows from inheritance tree
*
* This has the same API as acquire_sample_rows, except that rows are
* collected from all inheritance children as well as the specified table.
! * We fail and return zero if there are no inheritance children, or if
! * inheritance tree contains any foreign tables and we are doing an
! * ANALYZE-with-no-parameter or in an autovacuum process.
*/
static int
acquire_inherited_sample_rows(Relation onerel, int elevel,
***************
*** 1457,1462 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1515,1521 ----
{
List *tableOIDs;
Relation *rels;
+ AcquireSampleRowsFunc *acquirefunc;
double *relblocks;
double totalblocks;
int numrows,
***************
*** 1491,1500 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1550,1580 ----
}
/*
+ * If we are doing an ANALYZE-with-no-parameter or in an autovacuum process
+ * and inheritance tree contains any foreign tables, then fail.
+ */
+ if (anl_mode != ANL_MODE_SINGLE)
+ {
+ if (has_foreign_children(RelationGetRelid(onerel), tableOIDs))
+ {
+ ereport(elevel,
+ (errmsg("skipping analyze of \"%s.%s\" inheritance tree --- this inheritance tree contains foreign tables",
+ get_namespace_name(RelationGetNamespace(onerel)),
+ RelationGetRelationName(onerel)),
+ errhint("Try ANALYZE \"%s.%s\" for the inheritance statistics.",
+ get_namespace_name(RelationGetNamespace(onerel)),
+ RelationGetRelationName(onerel))));
+ return 0;
+ }
+ }
+
+ /*
* Count the blocks in all the relations. The result could overflow
* 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;
***************
*** 1516,1522 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
}
rels[nrels] = childrel;
! relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
totalblocks += relblocks[nrels];
nrels++;
}
--- 1596,1631 ----
}
rels[nrels] = childrel;
!
! if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
! {
! FdwRoutine *fdwroutine;
! BlockNumber relpages = 0;
! bool ok = false;
!
! /* 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++;
}
***************
*** 1534,1539 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1643,1649 ----
{
Relation childrel = rels[i];
double childblocks = relblocks[i];
+ AcquireSampleRowsFunc childacquirefunc = acquirefunc[i];
if (childblocks > 0)
{
***************
*** 1549,1560 **** 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 &&
--- 1659,1670 ----
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/explain.c
--- b/src/backend/commands/explain.c
***************
*** 2302,2331 **** ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
}
/*
* Show extra information for a ModifyTable node
*/
static void
show_modifytable_info(ModifyTableState *mtstate, ExplainState *es)
{
! FdwRoutine *fdwroutine = mtstate->resultRelInfo->ri_FdwRoutine;
! /*
! * If the first target relation is a foreign table, call its FDW to
! * display whatever additional fields it wants to. For now, we ignore the
! * possibility of other targets being foreign tables, although the API for
! * ExplainForeignModify is designed to allow them to be processed.
! */
! if (fdwroutine != NULL &&
! fdwroutine->ExplainForeignModify != NULL)
{
! ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
! List *fdw_private = (List *) linitial(node->fdwPrivLists);
! fdwroutine->ExplainForeignModify(mtstate,
! mtstate->resultRelInfo,
! fdw_private,
! 0,
! es);
}
}
--- 2302,2399 ----
}
/*
+ * Open a group for inherited foreign modification
+ */
+ void
+ ExplainBeginInheritedForeignModify(ModifyTable *plan, int subplan_index,
+ ExplainState *es)
+ {
+ char *objectname;
+ char *namespace;
+ char *refname;
+ Index rti;
+ RangeTblEntry *rte;
+
+ Assert(subplan_index > 0);
+
+ /* Open a group */
+ ExplainOpenGroup("Foreign Modification", "Foreign Modification", true, es);
+
+ /* Show the target relation */
+ Assert(plan->resultRelations != NIL);
+ rti = list_nth_int(plan->resultRelations, subplan_index);
+ rte = rt_fetch(rti, es->rtable);
+ /* Assert it's on a real relation */
+ Assert(rte->rtekind == RTE_RELATION);
+ objectname = get_rel_name(rte->relid);
+ namespace = get_namespace_name(get_rel_namespace(rte->relid));
+ refname = (char *) list_nth(es->rtable_names, rti - 1);
+
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ appendStringInfoSpaces(es->str, es->indent * 2);
+ appendStringInfoString(es->str, "For");
+ appendStringInfo(es->str, " %s.%s", quote_identifier(namespace),
+ quote_identifier(objectname));
+ if (strcmp(refname, objectname) != 0)
+ appendStringInfo(es->str, " %s", quote_identifier(refname));
+ appendStringInfoChar(es->str, '\n');
+ es->indent++;
+ }
+ else
+ {
+ ExplainPropertyText("Relation Name", objectname, es);
+ ExplainPropertyText("Schema", namespace, es);
+ ExplainPropertyText("Alias", refname, es);
+ }
+ }
+
+ /*
+ * Close a group for inherited foreign modification
+ */
+ void
+ ExplainEndInheritedForeignModify(ModifyTable *plan, int subplan_index,
+ ExplainState *es)
+ {
+ Assert(subplan_index > 0);
+
+ /* Undo the indentation we added in text format */
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ es->indent--;
+
+ /* Close a group */
+ ExplainCloseGroup("Foreign Modification", "Foreign Modification", true, es);
+ }
+
+ /*
* Show extra information for a ModifyTable node
*/
static void
show_modifytable_info(ModifyTableState *mtstate, ExplainState *es)
{
! ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
! int j;
! for (j = 0; j < mtstate->mt_nplans; j++)
{
! ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
! FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
! /*
! * If the target relation is a foreign table, call its FDW to display
! * whatever additional fields it wants to.
! */
! if (fdwroutine != NULL &&
! fdwroutine->ExplainForeignModify != NULL)
! {
! List *fdw_private = (List *) list_nth(plan->fdwPrivLists, j);
!
! fdwroutine->ExplainForeignModify(mtstate,
! resultRelInfo,
! fdw_private,
! j,
! es);
! }
}
}
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 318,324 **** 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,
--- 318,325 ----
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,
***************
*** 568,573 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
--- 569,598 ----
stmt->relation->relpersistence,
&inheritOids, &old_constraints, &parentOidCount);
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * Don't allow a foreign table to inherit from parents that have OID
+ * system columns.
+ */
+ if (parentOidCount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot inherit from relation with OIDs")));
+
+ /*
+ * Reset the storage parameter for inherited attributes that have
+ * non-default values.
+ */
+ foreach(listptr, schema)
+ {
+ ColumnDef *colDef = lfirst(listptr);
+
+ if (colDef->storage != 0)
+ colDef->storage = 0;
+ }
+ }
+
/*
* Create a tuple descriptor from the relation schema. Note that this
* deals with column names, types, and NOT NULL constraints, but not
***************
*** 1025,1030 **** ExecuteTruncate(TruncateStmt *stmt)
--- 1050,1061 ----
/* find_all_inheritors already got lock */
rel = heap_open(childrelid, NoLock);
+ /* ignore if the rel is a foreign table */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ heap_close(rel, NoLock);
+ continue;
+ }
truncate_check_rel(rel);
rels = lappend(rels, rel);
relids = lappend_oid(relids, childrelid);
***************
*** 3102,3125 **** 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;
--- 3133,3160 ----
* 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;
***************
*** 3132,3138 **** 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;
--- 3167,3174 ----
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;
***************
*** 3254,3264 **** 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;
--- 3290,3306 ----
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);
pass = AT_PASS_MISC;
***************
*** 3292,3298 **** 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 */
case AT_EnableRowSecurity:
--- 3334,3339 ----
***************
*** 4292,4298 **** 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
--- 4333,4340 ----
*/
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
***************
*** 4320,4327 **** 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);
}
}
--- 4362,4373 ----
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);
}
}
***************
*** 4611,4617 **** 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);
--- 4657,4663 ----
/* 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);
***************
*** 4907,4912 **** ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4953,4963 ----
/* 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 */
***************
*** 5507,5513 **** 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
--- 5558,5564 ----
/* 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
***************
*** 5899,5905 **** 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
--- 5950,5956 ----
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/*
* Call AddRelationNewConstraints to do the work, making sure it works on
***************
*** 5910,5918 **** 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 */
--- 5961,5977 ----
* 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 */
***************
*** 7399,7405 **** 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);
--- 7458,7464 ----
/* 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);
***************
*** 7734,7740 **** 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,
--- 7793,7802 ----
* 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/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 1920,1940 **** ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist)
aerm->rowmark = erm;
/* Look up the resjunk columns associated with this rowmark */
if (erm->relation &&
erm->relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
{
Assert(erm->markType != ROW_MARK_COPY);
- /* if child rel, need tableoid */
- if (erm->rti != erm->prti)
- {
- snprintf(resname, sizeof(resname), "tableoid%u", erm->rowmarkId);
- aerm->toidAttNo = ExecFindJunkAttributeInTlist(targetlist,
- resname);
- if (!AttributeNumberIsValid(aerm->toidAttNo))
- elog(ERROR, "could not find junk %s column", resname);
- }
-
/* always need ctid for real relations */
snprintf(resname, sizeof(resname), "ctid%u", erm->rowmarkId);
aerm->ctidAttNo = ExecFindJunkAttributeInTlist(targetlist,
--- 1920,1941 ----
aerm->rowmark = erm;
/* Look up the resjunk columns associated with this rowmark */
+
+ /* if child rel, need tableoid */
+ if (erm->rti != erm->prti)
+ {
+ snprintf(resname, sizeof(resname), "tableoid%u", erm->rowmarkId);
+ aerm->toidAttNo = ExecFindJunkAttributeInTlist(targetlist,
+ resname);
+ if (!AttributeNumberIsValid(aerm->toidAttNo))
+ elog(ERROR, "could not find junk %s column", resname);
+ }
+
if (erm->relation &&
erm->relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
{
Assert(erm->markType != ROW_MARK_COPY);
/* always need ctid for real relations */
snprintf(resname, sizeof(resname), "ctid%u", erm->rowmarkId);
aerm->ctidAttNo = ExecFindJunkAttributeInTlist(targetlist,
***************
*** 2385,2416 **** EvalPlanQualFetchRowMarks(EPQState *epqstate)
/* clear any leftover test tuple for this rel */
EvalPlanQualSetTuple(epqstate, erm->rti, NULL);
! if (erm->relation &&
! erm->relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
{
! Buffer buffer;
! Assert(erm->markType == ROW_MARK_REFERENCE);
! /* if child rel, must check whether it produced this row */
! if (erm->rti != erm->prti)
{
! Oid tableoid;
! datum = ExecGetJunkAttribute(epqstate->origslot,
! aerm->toidAttNo,
! &isNull);
! /* non-locked rels could be on the inside of outer joins */
! if (isNull)
! continue;
! tableoid = DatumGetObjectId(datum);
! if (tableoid != RelationGetRelid(erm->relation))
! {
! /* this child is inactive right now */
! continue;
! }
! }
/* fetch the tuple's ctid */
datum = ExecGetJunkAttribute(epqstate->origslot,
--- 2386,2419 ----
/* clear any leftover test tuple for this rel */
EvalPlanQualSetTuple(epqstate, erm->rti, NULL);
! /* if child rel, must check whether it produced this row */
! if (erm->rti != erm->prti)
{
! Oid tableoid;
! Assert(erm->relation);
!
! datum = ExecGetJunkAttribute(epqstate->origslot,
! aerm->toidAttNo,
! &isNull);
! /* non-locked rels could be on the inside of outer joins */
! if (isNull)
! continue;
! tableoid = DatumGetObjectId(datum);
! if (tableoid != RelationGetRelid(erm->relation))
{
! /* this child is inactive right now */
! continue;
! }
! }
! if (erm->relation &&
! erm->relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
! {
! Buffer buffer;
! Assert(erm->markType == ROW_MARK_REFERENCE);
/* fetch the tuple's ctid */
datum = ExecGetJunkAttribute(epqstate->origslot,
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 990,995 **** _copyPlanRowMark(const PlanRowMark *from)
--- 990,996 ----
COPY_SCALAR_FIELD(prti);
COPY_SCALAR_FIELD(rowmarkId);
COPY_SCALAR_FIELD(markType);
+ COPY_SCALAR_FIELD(strength);
COPY_SCALAR_FIELD(waitPolicy);
COPY_SCALAR_FIELD(isParent);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 850,856 **** _outPlanRowMark(StringInfo str, const PlanRowMark *node)
WRITE_UINT_FIELD(rti);
WRITE_UINT_FIELD(prti);
WRITE_UINT_FIELD(rowmarkId);
! WRITE_ENUM_FIELD(markType, RowMarkType);
WRITE_BOOL_FIELD(waitPolicy);
WRITE_BOOL_FIELD(isParent);
}
--- 850,857 ----
WRITE_UINT_FIELD(rti);
WRITE_UINT_FIELD(prti);
WRITE_UINT_FIELD(rowmarkId);
! WRITE_UINT_FIELD(markType);
! WRITE_ENUM_FIELD(strength, LockClauseStrength);
WRITE_BOOL_FIELD(waitPolicy);
WRITE_BOOL_FIELD(isParent);
}
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 2249,2254 **** preprocess_rowmarks(PlannerInfo *root)
--- 2249,2255 ----
newrc->markType = ROW_MARK_KEYSHARE;
break;
}
+ newrc->strength = rc->strength;
newrc->waitPolicy = rc->waitPolicy;
newrc->isParent = false;
***************
*** 2262,2267 **** preprocess_rowmarks(PlannerInfo *root)
--- 2263,2269 ----
foreach(l, parse->rtable)
{
RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+ RowMarkClause *rc;
PlanRowMark *newrc;
i++;
***************
*** 2277,2282 **** preprocess_rowmarks(PlannerInfo *root)
--- 2279,2289 ----
newrc->markType = ROW_MARK_REFERENCE;
else
newrc->markType = ROW_MARK_COPY;
+ rc = get_parse_rowmark(parse, i);
+ if (rc)
+ newrc->strength = rc->strength;
+ else
+ newrc->strength = LCS_NOLOCK;
newrc->waitPolicy = LockWaitBlock; /* doesn't matter */
newrc->isParent = false;
*** a/src/backend/optimizer/prep/prepsecurity.c
--- b/src/backend/optimizer/prep/prepsecurity.c
***************
*** 229,257 **** expand_security_qual(PlannerInfo *root, List *tlist, int rt_index,
rc = get_plan_rowmark(root->rowMarks, rt_index);
if (rc != NULL)
{
! switch (rc->markType)
! {
! case ROW_MARK_EXCLUSIVE:
! applyLockingClause(subquery, 1, LCS_FORUPDATE,
! rc->waitPolicy, false);
! break;
! case ROW_MARK_NOKEYEXCLUSIVE:
! applyLockingClause(subquery, 1, LCS_FORNOKEYUPDATE,
! rc->waitPolicy, false);
! break;
! case ROW_MARK_SHARE:
! applyLockingClause(subquery, 1, LCS_FORSHARE,
! rc->waitPolicy, false);
! break;
! case ROW_MARK_KEYSHARE:
! applyLockingClause(subquery, 1, LCS_FORKEYSHARE,
! rc->waitPolicy, false);
! break;
! case ROW_MARK_REFERENCE:
! case ROW_MARK_COPY:
! /* No locking needed */
! break;
! }
root->rowMarks = list_delete(root->rowMarks, rc);
}
--- 229,237 ----
rc = get_plan_rowmark(root->rowMarks, rt_index);
if (rc != NULL)
{
! if (rc->strength != LCS_NOLOCK)
! applyLockingClause(subquery, 1,
! rc->strength, rc->waitPolicy, false);
root->rowMarks = list_delete(root->rowMarks, rc);
}
*** a/src/backend/optimizer/prep/preptlist.c
--- b/src/backend/optimizer/prep/preptlist.c
***************
*** 92,98 **** preprocess_targetlist(PlannerInfo *root, List *tlist)
if (rc->rti != rc->prti)
continue;
! if (rc->markType != ROW_MARK_COPY)
{
/* It's a regular table, so fetch its TID */
var = makeVar(rc->rti,
--- 92,98 ----
if (rc->rti != rc->prti)
continue;
! if (rc->markType & ~ROW_MARK_COPY)
{
/* It's a regular table, so fetch its TID */
var = makeVar(rc->rti,
***************
*** 123,128 **** preprocess_targetlist(PlannerInfo *root, List *tlist)
--- 123,144 ----
pstrdup(resname),
true);
tlist = lappend(tlist, tle);
+
+ /* if containing foreign tables, fetch the whole row too */
+ if (rc->markType & ROW_MARK_COPY)
+ {
+ /* Not a table, so we need the whole row as a junk var */
+ var = makeWholeRowVar(rt_fetch(rc->rti, range_table),
+ rc->rti,
+ 0,
+ false);
+ snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId);
+ tle = makeTargetEntry((Expr *) var,
+ list_length(tlist) + 1,
+ pstrdup(resname),
+ true);
+ tlist = lappend(tlist, tle);
+ }
}
}
else
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 1237,1242 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1237,1243 ----
LOCKMODE lockmode;
List *inhOIDs;
List *appinfos;
+ RowMarkType alltypes;
ListCell *l;
/* Does RT entry allow inheritance? */
***************
*** 1309,1314 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1310,1316 ----
/* Scan the inheritance set and expand it */
appinfos = NIL;
+ alltypes = 0;
foreach(l, inhOIDs)
{
Oid childOID = lfirst_oid(l);
***************
*** 1338,1348 **** 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);
--- 1340,1351 ----
/*
* 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);
***************
*** 1388,1396 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
newrc->rti = childRTindex;
newrc->prti = rti;
newrc->rowmarkId = oldrc->rowmarkId;
! newrc->markType = oldrc->markType;
newrc->waitPolicy = oldrc->waitPolicy;
newrc->isParent = false;
root->rowMarks = lappend(root->rowMarks, newrc);
}
--- 1391,1404 ----
newrc->rti = childRTindex;
newrc->prti = rti;
newrc->rowmarkId = oldrc->rowmarkId;
! if (childrte->relkind != RELKIND_FOREIGN_TABLE)
! newrc->markType = oldrc->markType;
! else
! newrc->markType = ROW_MARK_COPY;
! newrc->strength = oldrc->strength;
newrc->waitPolicy = oldrc->waitPolicy;
newrc->isParent = false;
+ alltypes |= newrc->markType;
root->rowMarks = lappend(root->rowMarks, newrc);
}
***************
*** 1416,1421 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
--- 1424,1433 ----
/* Otherwise, OK to add to root->append_rel_list */
root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+
+ /* and replace the parent markType with alltypes */
+ if (oldrc)
+ oldrc->markType = alltypes;
}
/*
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 4363,4394 **** 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;
}
;
--- 4363,4394 ----
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/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 2352,2373 **** CreateCommandTag(Node *parsetree)
else if (stmt->rowMarks != NIL)
{
/* not 100% but probably close enough */
! switch (((PlanRowMark *) linitial(stmt->rowMarks))->markType)
{
! case ROW_MARK_EXCLUSIVE:
tag = "SELECT FOR UPDATE";
break;
! case ROW_MARK_NOKEYEXCLUSIVE:
tag = "SELECT FOR NO KEY UPDATE";
break;
! case ROW_MARK_SHARE:
tag = "SELECT FOR SHARE";
break;
! case ROW_MARK_KEYSHARE:
tag = "SELECT FOR KEY SHARE";
break;
! case ROW_MARK_REFERENCE:
! case ROW_MARK_COPY:
tag = "SELECT";
break;
default:
--- 2352,2372 ----
else if (stmt->rowMarks != NIL)
{
/* not 100% but probably close enough */
! switch (((PlanRowMark *) linitial(stmt->rowMarks))->strength)
{
! case LCS_FORUPDATE:
tag = "SELECT FOR UPDATE";
break;
! case LCS_FORNOKEYUPDATE:
tag = "SELECT FOR NO KEY UPDATE";
break;
! case LCS_FORSHARE:
tag = "SELECT FOR SHARE";
break;
! case LCS_FORKEYSHARE:
tag = "SELECT FOR KEY SHARE";
break;
! case LCS_NOLOCK:
tag = "SELECT";
break;
default:
*** a/src/backend/utils/cache/plancache.c
--- b/src/backend/utils/cache/plancache.c
***************
*** 1488,1493 **** AcquireExecutorLocks(List *stmt_list, bool acquire)
--- 1488,1497 ----
if (rte->rtekind != RTE_RELATION)
continue;
+ /* Ignore inheritance parents. */
+ if (rte->inh)
+ continue;
+
/*
* Acquire the appropriate type of lock on each relation OID. Note
* that we don't actually try to open the rel, and hence will not
*** a/src/include/commands/explain.h
--- b/src/include/commands/explain.h
***************
*** 92,95 **** extern void ExplainPropertyLong(const char *qlabel, long value,
--- 92,100 ----
extern void ExplainPropertyFloat(const char *qlabel, double value, int ndigits,
ExplainState *es);
+ extern void ExplainBeginInheritedForeignModify(ModifyTable *plan, int subplan_index,
+ ExplainState *es);
+ extern void ExplainEndInheritedForeignModify(ModifyTable *plan, int subplan_index,
+ ExplainState *es);
+
#endif /* EXPLAIN_H */
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 428,434 **** typedef struct ExecRowMark
Index rti; /* its range table index */
Index prti; /* parent range table index, if child */
Index rowmarkId; /* unique identifier for resjunk columns */
! RowMarkType markType; /* see enum in nodes/plannodes.h */
LockWaitPolicy waitPolicy; /* NOWAIT and SKIP LOCKED */
ItemPointerData curCtid; /* ctid of currently locked tuple, if any */
} ExecRowMark;
--- 428,434 ----
Index rti; /* its range table index */
Index prti; /* parent range table index, if child */
Index rowmarkId; /* unique identifier for resjunk columns */
! RowMarkType markType; /* see flags in nodes/plannodes.h */
LockWaitPolicy waitPolicy; /* NOWAIT and SKIP LOCKED */
ItemPointerData curCtid; /* ctid of currently locked tuple, if any */
} ExecRowMark;
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 23,28 ****
--- 23,29 ----
#include "nodes/bitmapset.h"
#include "nodes/primnodes.h"
#include "nodes/value.h"
+ #include "utils/lockclausestrength.h"
#include "utils/lockwaitpolicy.h"
/* Possible sources of a Query */
***************
*** 618,632 **** typedef struct DefElem
* a location field --- currently, parse analysis insists on unqualified
* names in LockingClause.)
*/
- typedef enum LockClauseStrength
- {
- /* order is important -- see applyLockingClause */
- LCS_FORKEYSHARE,
- LCS_FORSHARE,
- LCS_FORNOKEYUPDATE,
- LCS_FORUPDATE
- } LockClauseStrength;
-
typedef struct LockingClause
{
NodeTag type;
--- 619,624 ----
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 18,23 ****
--- 18,24 ----
#include "lib/stringinfo.h"
#include "nodes/bitmapset.h"
#include "nodes/primnodes.h"
+ #include "utils/lockclausestrength.h"
#include "utils/lockwaitpolicy.h"
***************
*** 785,791 **** typedef struct Limit
/*
* RowMarkType -
! * enums for types of row-marking operations
*
* The first four of these values represent different lock strengths that
* we can take on tuples according to SELECT FOR [KEY] UPDATE/SHARE requests.
--- 786,792 ----
/*
* RowMarkType -
! * flags for types of row-marking operations
*
* The first four of these values represent different lock strengths that
* we can take on tuples according to SELECT FOR [KEY] UPDATE/SHARE requests.
***************
*** 814,828 **** typedef struct Limit
* it's probably still faster than doing a second remote fetch, so it doesn't
* seem worth the extra complexity to permit ROW_MARK_REFERENCE.
*/
! typedef enum RowMarkType
! {
! ROW_MARK_EXCLUSIVE, /* obtain exclusive tuple lock */
! ROW_MARK_NOKEYEXCLUSIVE, /* obtain no-key exclusive tuple lock */
! ROW_MARK_SHARE, /* obtain shared tuple lock */
! ROW_MARK_KEYSHARE, /* obtain keyshare tuple lock */
! ROW_MARK_REFERENCE, /* just fetch the TID */
! ROW_MARK_COPY /* physically copy the row value */
! } RowMarkType;
#define RowMarkRequiresRowShareLock(marktype) ((marktype) <= ROW_MARK_KEYSHARE)
--- 815,828 ----
* it's probably still faster than doing a second remote fetch, so it doesn't
* seem worth the extra complexity to permit ROW_MARK_REFERENCE.
*/
! typedef uint16 RowMarkType; /* a bitmask of row-marking operation bits */
!
! #define ROW_MARK_EXCLUSIVE 0x0001 /* obtain exclusive tuple lock */
! #define ROW_MARK_NOKEYEXCLUSIVE 0x0002 /* obtain no-key exclusive tuple lock */
! #define ROW_MARK_SHARE 0x0004 /* obtain shared tuple lock */
! #define ROW_MARK_KEYSHARE 0x0008 /* obtain keyshare tuple lock */
! #define ROW_MARK_REFERENCE 0x0010 /* just fetch the TID */
! #define ROW_MARK_COPY 0x0020 /* physically copy the row value */
#define RowMarkRequiresRowShareLock(marktype) ((marktype) <= ROW_MARK_KEYSHARE)
***************
*** 851,856 **** typedef enum RowMarkType
--- 851,858 ----
* The tableoid column is only present for an inheritance hierarchy.
* When markType == ROW_MARK_COPY, there is instead a single column named
* wholerow%u whole-row value of relation
+ * The wholerow column is also present for an inheritance hierarchy that
+ * contains foreign tables.
* In all three cases, %u represents the rowmark ID number (rowmarkId).
* This number is unique within a plan tree, except that child relation
* entries copy their parent's rowmarkId. (Assigning unique numbers
***************
*** 859,864 **** typedef enum RowMarkType
--- 861,870 ----
* Note this means that all tables in an inheritance hierarchy share the
* same resjunk column names. However, in an inherited UPDATE/DELETE the
* columns could have different physical column numbers in each subplan.
+ *
+ * A parent PlanRowMark's markType represents the logical OR of the rowmark
+ * methods being used by all its children. So, don't use the
+ * RowMarkRequiresRowShareLock macro with parent PlanRowMarks.
*/
typedef struct PlanRowMark
{
***************
*** 866,872 **** typedef struct PlanRowMark
Index rti; /* range table index of markable relation */
Index prti; /* range table index of parent relation */
Index rowmarkId; /* unique identifier for resjunk columns */
! RowMarkType markType; /* see enum above */
LockWaitPolicy waitPolicy; /* NOWAIT and SKIP LOCKED options */
bool isParent; /* true if this is a "dummy" parent entry */
} PlanRowMark;
--- 872,879 ----
Index rti; /* range table index of markable relation */
Index prti; /* range table index of parent relation */
Index rowmarkId; /* unique identifier for resjunk columns */
! RowMarkType markType; /* see flags above */
! LockClauseStrength strength;
LockWaitPolicy waitPolicy; /* NOWAIT and SKIP LOCKED options */
bool isParent; /* true if this is a "dummy" parent entry */
} PlanRowMark;
*** /dev/null
--- b/src/include/utils/lockclausestrength.h
***************
*** 0 ****
--- 1,32 ----
+ /*-------------------------------------------------------------------------
+ *
+ * lockclausestrength.h
+ * Header file for LockClauseStrength enum.
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/lockclausestrength.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef LOCKCLAUSESTRENGTH_H
+ #define LOCKCLAUSESTRENGTH_H
+
+ /*
+ * This enum represents the locking strength of a FOR UPDATE/SHARE clause.
+ * The ordering here is important, because the highest numerical value takes
+ * precedence when a RTE is specified multiple ways. See applyLockingClause.
+ */
+ typedef enum LockClauseStrength
+ {
+ /* order is important -- see applyLockingClause */
+ LCS_NOLOCK, /* No locking needed (this is only used in
+ * PlanRowMarks.) */
+ LCS_FORKEYSHARE,
+ LCS_FORSHARE,
+ LCS_FORNOKEYUPDATE,
+ LCS_FORUPDATE
+ } LockClauseStrength;
+
+ #endif /* LOCKCLAUSESTRENGTH_H */
*** a/src/test/regress/expected/foreign_data.out
--- b/src/test/regress/expected/foreign_data.out
***************
*** 1234,1239 **** DROP TRIGGER trigtest_before_row ON foreign_schema.foreign_table_1;
--- 1234,1644 ----
DROP TRIGGER trigtest_after_stmt ON foreign_schema.foreign_table_1;
DROP TRIGGER trigtest_after_row ON foreign_schema.foreign_table_1;
DROP FUNCTION dummy_trigger();
+ -- Table inheritance
+ CREATE TABLE pt1 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ );
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ DROP FOREIGN TABLE ft2;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+
+ CREATE FOREIGN TABLE ft2 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+
+ ALTER FOREIGN TABLE ft2 INHERIT pt1;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ CREATE VIEW v1 (c1, c2, c3) AS SELECT 1, 'v1'::text, '1994-01-01'::date;
+ ALTER TABLE v1 INHERIT pt1; -- ERROR
+ ERROR: "v1" is not a table or foreign table
+ DROP VIEW v1;
+ -- add attributes recursively
+ ALTER TABLE pt1 ADD COLUMN c4 integer;
+ ALTER TABLE pt1 ADD COLUMN c5 integer DEFAULT 0;
+ ALTER TABLE pt1 ADD COLUMN c6 integer;
+ ALTER TABLE pt1 ADD COLUMN c7 integer NOT NULL;
+ ALTER TABLE pt1 ADD COLUMN c8 integer;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ c4 | integer | | plain | |
+ c5 | integer | default 0 | plain | |
+ c6 | integer | | plain | |
+ c7 | integer | not null | plain | |
+ c8 | integer | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ c4 | integer | | | plain | |
+ c5 | integer | default 0 | | plain | |
+ c6 | integer | | | plain | |
+ c7 | integer | not null | | plain | |
+ c8 | integer | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- alter attributes recursively
+ ALTER TABLE pt1 ALTER COLUMN c4 SET DEFAULT 0;
+ ALTER TABLE pt1 ALTER COLUMN c5 DROP DEFAULT;
+ ALTER TABLE pt1 ALTER COLUMN c6 SET NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN c7 DROP NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN c8 TYPE char(10) USING '0'; -- ERROR
+ ERROR: "ft2" is not a table
+ ALTER TABLE pt1 ALTER COLUMN c8 TYPE char(10);
+ ALTER TABLE pt1 ALTER COLUMN c8 SET DATA TYPE text;
+ ALTER TABLE pt1 ALTER COLUMN c1 SET STATISTICS 10000;
+ ALTER TABLE pt1 ALTER COLUMN c1 SET (n_distinct = 100);
+ ALTER TABLE pt1 ALTER COLUMN c8 SET STATISTICS -1;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ c4 | integer | default 0 | plain | |
+ c5 | integer | | plain | |
+ c6 | integer | not null | plain | |
+ c7 | integer | | plain | |
+ c8 | text | | extended | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | 10000 |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ c4 | integer | default 0 | | plain | |
+ c5 | integer | | | plain | |
+ c6 | integer | not null | | plain | |
+ c7 | integer | | | plain | |
+ c8 | text | | | extended | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- drop attributes recursively
+ ALTER TABLE pt1 DROP COLUMN c4;
+ ALTER TABLE pt1 DROP COLUMN c5;
+ ALTER TABLE pt1 DROP COLUMN c6;
+ ALTER TABLE pt1 DROP COLUMN c7;
+ ALTER TABLE pt1 DROP COLUMN c8;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | 10000 |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- cannot change storage mode for an attribute of foreign tables
+ ALTER TABLE pt1 ALTER COLUMN c2 SET STORAGE EXTERNAL;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | external | |
+ c3 | date | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | 10000 |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ DROP FOREIGN TABLE ft2;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | external | |
+ c3 | date | | plain | |
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ ALTER TABLE pt1 ALTER COLUMN c2 SET STORAGE EXTENDED;
+ -- add constraints recursively
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk1 CHECK (c1 > 0) NO INHERIT;
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '');
+ -- connoinherit should be true for NO INHERIT constraint
+ SELECT pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pgc.connoinherit FROM pg_class AS pc INNER JOIN pg_constraint AS pgc ON (pgc.conrelid = pc.oid) WHERE pc.relname = 'pt1' order by 1,2;
+ relname | conname | contype | conislocal | coninhcount | connoinherit
+ ---------+---------+---------+------------+-------------+--------------
+ pt1 | pt1chk1 | c | t | 0 | t
+ pt1 | pt1chk2 | c | t | 0 | f
+ (2 rows)
+
+ -- child does not inherit NO INHERIT constraints
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Check constraints:
+ "pt1chk1" CHECK (c1 > 0) NO INHERIT
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ DROP FOREIGN TABLE ft2;
+ CREATE FOREIGN TABLE ft2 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ -- child must have parent's INHERIT constraints
+ ALTER FOREIGN TABLE ft2 INHERIT pt1; -- ERROR
+ ERROR: child table is missing constraint "pt1chk2"
+ ALTER FOREIGN TABLE ft2 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '');
+ ALTER FOREIGN TABLE ft2 INHERIT pt1;
+ -- child does not inherit NO INHERIT constraints
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Check constraints:
+ "pt1chk1" CHECK (c1 > 0) NO INHERIT
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- drop constraints recursively
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk1 CASCADE;
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk2 CASCADE;
+ -- NOT VALID should be ignored for foreign tables
+ INSERT INTO pt1 VALUES (1, 'pt1'::text, '1994-01-01'::date);
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '') NOT VALID;
+ NOTICE: merging constraint "pt1chk2" with inherited definition
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text) NOT VALID
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- VALIDATE CONSTRAINT should work by ignoring foreign tables
+ ALTER TABLE pt1 VALIDATE CONSTRAINT pt1chk2;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ c1 | integer | not null | plain | 10000 |
+ c2 | text | | extended | |
+ c3 | date | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ c1 | integer | not null | | plain | |
+ c2 | text | | | extended | |
+ c3 | date | | | plain | |
+ Check constraints:
+ "pt1chk2" CHECK (c2 <> ''::text)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- cannot add an OID system column to foreign tables
+ ALTER TABLE pt1 SET WITH OIDS; -- ERROR
+ ERROR: cannot add OID column to foreign table "ft2"
+ DROP FOREIGN TABLE ft2;
+ ALTER TABLE pt1 SET WITH OIDS;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
+ ERROR: cannot inherit from relation with OIDs
+ ALTER TABLE pt1 SET WITHOUT OIDS;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ -- changes name of an attribute recursively
+ ALTER TABLE pt1 RENAME COLUMN c1 TO f1;
+ ALTER TABLE pt1 RENAME COLUMN c2 TO f2;
+ ALTER TABLE pt1 RENAME COLUMN c3 TO f3;
+ -- changes name of a constraint recursively
+ ALTER TABLE pt1 RENAME CONSTRAINT pt1chk2 TO f2_check;
+ \d+ pt1
+ Table "public.pt1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+ --------+---------+-----------+----------+--------------+-------------
+ f1 | integer | not null | plain | 10000 |
+ f2 | text | | extended | |
+ f3 | date | | plain | |
+ Check constraints:
+ "f2_check" CHECK (f2 <> ''::text)
+ Child tables: ft2
+
+ \d+ ft2
+ Foreign table "public.ft2"
+ Column | Type | Modifiers | FDW Options | Storage | Stats target | Description
+ --------+---------+-----------+-------------+----------+--------------+-------------
+ f1 | integer | not null | | plain | |
+ f2 | text | | | extended | |
+ f3 | date | | | plain | |
+ Check constraints:
+ "f2_check" CHECK (f2 <> ''::text)
+ Server: s0
+ FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+ Inherits: pt1
+
+ -- TRUNCATE should work by ignoring foreign tables
+ TRUNCATE pt1;
+ TRUNCATE ft2; -- ERROR
+ ERROR: "ft2" is not a table
+ DROP TABLE pt1 CASCADE;
+ NOTICE: drop cascades to foreign table ft2
-- IMPORT FOREIGN SCHEMA
IMPORT FOREIGN SCHEMA s1 FROM SERVER s9 INTO public; -- ERROR
ERROR: foreign-data wrapper "foo" has no handler
*** a/src/test/regress/sql/foreign_data.sql
--- b/src/test/regress/sql/foreign_data.sql
***************
*** 536,541 **** DROP TRIGGER trigtest_after_row ON foreign_schema.foreign_table_1;
--- 536,670 ----
DROP FUNCTION dummy_trigger();
+ -- Table inheritance
+ CREATE TABLE pt1 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ );
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ \d+ ft2
+ DROP FOREIGN TABLE ft2;
+ \d+ pt1
+ CREATE FOREIGN TABLE ft2 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ ft2
+ ALTER FOREIGN TABLE ft2 INHERIT pt1;
+ \d+ pt1
+ \d+ ft2
+ CREATE VIEW v1 (c1, c2, c3) AS SELECT 1, 'v1'::text, '1994-01-01'::date;
+ ALTER TABLE v1 INHERIT pt1; -- ERROR
+ DROP VIEW v1;
+
+ -- add attributes recursively
+ ALTER TABLE pt1 ADD COLUMN c4 integer;
+ ALTER TABLE pt1 ADD COLUMN c5 integer DEFAULT 0;
+ ALTER TABLE pt1 ADD COLUMN c6 integer;
+ ALTER TABLE pt1 ADD COLUMN c7 integer NOT NULL;
+ ALTER TABLE pt1 ADD COLUMN c8 integer;
+ \d+ pt1
+ \d+ ft2
+
+ -- alter attributes recursively
+ ALTER TABLE pt1 ALTER COLUMN c4 SET DEFAULT 0;
+ ALTER TABLE pt1 ALTER COLUMN c5 DROP DEFAULT;
+ ALTER TABLE pt1 ALTER COLUMN c6 SET NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN c7 DROP NOT NULL;
+ ALTER TABLE pt1 ALTER COLUMN c8 TYPE char(10) USING '0'; -- ERROR
+ ALTER TABLE pt1 ALTER COLUMN c8 TYPE char(10);
+ ALTER TABLE pt1 ALTER COLUMN c8 SET DATA TYPE text;
+ ALTER TABLE pt1 ALTER COLUMN c1 SET STATISTICS 10000;
+ ALTER TABLE pt1 ALTER COLUMN c1 SET (n_distinct = 100);
+ ALTER TABLE pt1 ALTER COLUMN c8 SET STATISTICS -1;
+ \d+ pt1
+ \d+ ft2
+
+ -- drop attributes recursively
+ ALTER TABLE pt1 DROP COLUMN c4;
+ ALTER TABLE pt1 DROP COLUMN c5;
+ ALTER TABLE pt1 DROP COLUMN c6;
+ ALTER TABLE pt1 DROP COLUMN c7;
+ ALTER TABLE pt1 DROP COLUMN c8;
+ \d+ pt1
+ \d+ ft2
+
+ -- cannot change storage mode for an attribute of foreign tables
+ ALTER TABLE pt1 ALTER COLUMN c2 SET STORAGE EXTERNAL;
+ \d+ pt1
+ \d+ ft2
+ DROP FOREIGN TABLE ft2;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ \d+ pt1
+ \d+ ft2
+ ALTER TABLE pt1 ALTER COLUMN c2 SET STORAGE EXTENDED;
+
+ -- add constraints recursively
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk1 CHECK (c1 > 0) NO INHERIT;
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '');
+ -- connoinherit should be true for NO INHERIT constraint
+ SELECT pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pgc.connoinherit FROM pg_class AS pc INNER JOIN pg_constraint AS pgc ON (pgc.conrelid = pc.oid) WHERE pc.relname = 'pt1' order by 1,2;
+ -- child does not inherit NO INHERIT constraints
+ \d+ pt1
+ \d+ ft2
+ DROP FOREIGN TABLE ft2;
+ CREATE FOREIGN TABLE ft2 (
+ c1 integer NOT NULL,
+ c2 text,
+ c3 date
+ ) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+ -- child must have parent's INHERIT constraints
+ ALTER FOREIGN TABLE ft2 INHERIT pt1; -- ERROR
+ ALTER FOREIGN TABLE ft2 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '');
+ ALTER FOREIGN TABLE ft2 INHERIT pt1;
+ -- child does not inherit NO INHERIT constraints
+ \d+ pt1
+ \d+ ft2
+
+ -- drop constraints recursively
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk1 CASCADE;
+ ALTER TABLE pt1 DROP CONSTRAINT pt1chk2 CASCADE;
+
+ -- NOT VALID should be ignored for foreign tables
+ INSERT INTO pt1 VALUES (1, 'pt1'::text, '1994-01-01'::date);
+ ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '') NOT VALID;
+ \d+ pt1
+ \d+ ft2
+ -- VALIDATE CONSTRAINT should work by ignoring foreign tables
+ ALTER TABLE pt1 VALIDATE CONSTRAINT pt1chk2;
+ \d+ pt1
+ \d+ ft2
+
+ -- cannot add an OID system column to foreign tables
+ ALTER TABLE pt1 SET WITH OIDS; -- ERROR
+ DROP FOREIGN TABLE ft2;
+ ALTER TABLE pt1 SET WITH OIDS;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value'); -- ERROR
+ ALTER TABLE pt1 SET WITHOUT OIDS;
+ CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+ SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+
+ -- changes name of an attribute recursively
+ ALTER TABLE pt1 RENAME COLUMN c1 TO f1;
+ ALTER TABLE pt1 RENAME COLUMN c2 TO f2;
+ ALTER TABLE pt1 RENAME COLUMN c3 TO f3;
+ -- changes name of a constraint recursively
+ ALTER TABLE pt1 RENAME CONSTRAINT pt1chk2 TO f2_check;
+ \d+ pt1
+ \d+ ft2
+
+ -- TRUNCATE should work by ignoring foreign tables
+ TRUNCATE pt1;
+ TRUNCATE ft2; -- ERROR
+
+ DROP TABLE pt1 CASCADE;
+
-- IMPORT FOREIGN SCHEMA
IMPORT FOREIGN SCHEMA s1 FROM SERVER s9 INTO public; -- ERROR
IMPORT FOREIGN SCHEMA s1 LIMIT TO (t1) FROM SERVER s9 INTO public; --ERROR
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2015-03-20 18:50 Tom Lane <tgl@sss.pgh.pa.us>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 0 replies; 179+ messages in thread
From: Tom Lane @ 2015-03-20 18:50 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Heikki Linnakangas <hlinnakangas@vmware.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; noah@leadboat.com; shigeru.hanada@gmail.com; pgsql-hackers
Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp> writes:
> I noticed that the latter disallows TRUNCATE on inheritance trees that
> contain at least one child foreign table. But I think it would be
> better to allow it, with the semantics that we quietly ignore the child
> foreign tables and apply the operation to the child plain tables, which
> is the same semantics as ALTER COLUMN SET STORAGE on such inheritance
> trees. Comments welcome.
I've been working through the foreign table inheritance patch, and found
the code that makes the above happen. I don't think this is a good idea
at all. In the first place, successful TRUNCATE should leave the table
empty, not "well, we'll make it empty if we feel up to that". In the
second place, someday we might want to make TRUNCATE actually work for
foreign tables (at least for FDWs that want to support it). If we did,
we would have a backwards-compatibility hazard, because suddenly a
TRUNCATE on an inheritance tree that includes a foreign table would have
different non-error effects than before.
I think we should just throw error in this case.
BTW, the SET STORAGE comparison is bogus as well. I see no reason that
we shouldn't just allow SET STORAGE on foreign tables. It's probably
not going to have any effect, but so what? And again, if we did ever
find a use for that, we'd have a compatibility problem if inherited SET
STORAGE has a pre-existing behavior that it skips foreign children.
In the same vein, I'm planning to take out the existing prohibition on
marking CHECK constraints on foreign tables NOT VALID. That likewise
creates a corner case for inheritance trees for no obviously good reason.
It was reasonable to be conservative about whether to allow that so long
as there were no side-effects; but putting warts into the behavior of
inheritance trees to preserve the prohibition is not a good outcome.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2015-03-22 17:57 Tom Lane <tgl@sss.pgh.pa.us>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 4 replies; 179+ messages in thread
From: Tom Lane @ 2015-03-22 17:57 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp> writes:
> [ fdw-inh-8.patch ]
I've committed this with some substantial rearrangements, notably:
* I thought that if we were doing this at all, we should go all the way
and allow foreign tables to be both inheritance parents and children.
* As I mentioned earlier, I got rid of a few unnecessary restrictions on
foreign tables so as to avoid introducing warts into inheritance behavior.
In particular, we now allow NOT VALID CHECK constraints (and hence ALTER
... VALIDATE CONSTRAINT), ALTER SET STORAGE, and ALTER SET WITH/WITHOUT
OIDS. These are probably no-ops anyway for foreign tables, though
conceivably an FDW might choose to implement some behavior for STORAGE
or OIDs.
* I did not like the EXPLAIN changes at all; in the first place they
resulted in invalid JSON output (there could be multiple fields of the
Update plan object with identical labels), and in the second place it
seemed like a bad idea to rely on FDWs to change the behavior of their
ExplainModifyTarget functions. I've refactored that so that explain.c
remains responsible for getting the grouping right. Also, as I said
earlier, it seemed like a good idea to produce subgroups identifying
all the target tables not only the foreign ones.
* I fooled around with the PlanRowMark changes some more, mainly with
the idea that we might soon allow FDWs to use rowmark methods other than
ROW_MARK_COPY. The planner now has just one place where a rel's rowmark
method is chosen, so as to centralize anything we need to do there.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2015-03-22 18:39 Robert Haas <robertmhaas@gmail.com>
parent: Tom Lane <tgl@sss.pgh.pa.us>
3 siblings, 1 reply; 179+ messages in thread
From: Robert Haas @ 2015-03-22 18:39 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On Sun, Mar 22, 2015 at 1:57 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp> writes:
>> [ fdw-inh-8.patch ]
>
> I've committed this with some substantial rearrangements, notably:
I'm really glad this is going in! Thanks to to Shigeru Hanada and
Etsuro Fujita for working on this, to you (Tom) for putting in the
time to get it committed, and of course to the reviewers Ashutosh
Bapat and Kyotaro Horiguchi for their time and effort.
In a way, I believe we can think of this as the beginnings of a
sharding story for PostgreSQL. A lot more work is needed, of course
-- join and aggregate pushdown are high on my personal list -- but
it's a start.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2015-03-23 06:01 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 0 replies; 179+ messages in thread
From: Ashutosh Bapat @ 2015-03-23 06:01 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On Mon, Mar 23, 2015 at 12:09 AM, Robert Haas <robertmhaas@gmail.com> wrote:
> On Sun, Mar 22, 2015 at 1:57 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> > Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp> writes:
> >> [ fdw-inh-8.patch ]
> >
> > I've committed this with some substantial rearrangements, notably:
>
> I'm really glad this is going in! Thanks to to Shigeru Hanada and
> Etsuro Fujita for working on this, to you (Tom) for putting in the
> time to get it committed, and of course to the reviewers Ashutosh
> Bapat and Kyotaro Horiguchi for their time and effort.
>
> In a way, I believe we can think of this as the beginnings of a
> sharding story for PostgreSQL. A lot more work is needed, of course
> -- join and aggregate pushdown are high on my personal list -- but
> it's a start.
>
>
+1.
> --
> Robert Haas
> EnterpriseDB: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2015-03-24 02:15 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Tom Lane <tgl@sss.pgh.pa.us>
3 siblings, 0 replies; 179+ messages in thread
From: Etsuro Fujita @ 2015-03-24 02:15 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On 2015/03/23 2:57, Tom Lane wrote:
> I've committed this with some substantial rearrangements, notably:
Thanks for taking the time to committing the patch!
Thanks for the work, Hanada-san! And thank you everyone for the reviews
and comments, especially Ashutosh, Horiguchi-san and Noah!
> * I fooled around with the PlanRowMark changes some more, mainly with
> the idea that we might soon allow FDWs to use rowmark methods other than
> ROW_MARK_COPY. The planner now has just one place where a rel's rowmark
> method is chosen, so as to centralize anything we need to do there.
Will work on this issue.
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2015-04-14 10:49 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Tom Lane <tgl@sss.pgh.pa.us>
3 siblings, 2 replies; 179+ messages in thread
From: Etsuro Fujita @ 2015-04-14 10:49 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On 2015/03/23 2:57, Tom Lane wrote:
> Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp> writes:
>> [ fdw-inh-8.patch ]
>
> I've committed this with some substantial rearrangements, notably:
>
> * I thought that if we were doing this at all, we should go all the way
> and allow foreign tables to be both inheritance parents and children.
I found that when setting a foreign table to be the parent of an
inheritance set that only contains foreign tables, SELECT FOR UPDATE on
the inheritance parent fails with a can't-happen error condition. Here
is an example:
$ createdb mydb
$ psql mydb
psql (9.5devel)
Type "help" for help.
mydb=# create table t1 (c1 int);
CREATE TABLE
mydb=# create table t2 (c1 int);
CREATE TABLE
$ psql postgres
psql (9.5devel)
Type "help" for help.
postgres=# create extension postgres_fdw;
CREATE EXTENSION
postgres=# create server myserver foreign data wrapper postgres_fdw
options (dbname 'mydb');
CREATE SERVER
postgres=# create user mapping for current_user server myserver;
CREATE USER MAPPING
postgres=# create foreign table ft1 (c1 int) server myserver options
(table_name 't1');
CREATE FOREIGN TABLE
postgres=# create foreign table ft2 (c1 int) server myserver options
(table_name 't2');
CREATE FOREIGN TABLE
postgres=# alter foreign table ft2 inherit ft1;
ALTER FOREIGN TABLE
postgres=# select * from ft1 for update;
ERROR: could not find junk tableoid1 column
I think this is a bug. Attached is a patch fixing this issue.
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] preptlist.patch (11.4K, ../../552CF0B6.8010006@lab.ntt.co.jp/2-preptlist.patch)
download | inline diff:
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 3193,3218 **** select * from bar where f1 in (select f1 from foo) for update;
QUERY PLAN
----------------------------------------------------------------------------------------------
LockRows
! Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
-> Hash Join
! Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
Hash Cond: (bar.f1 = foo.f1)
-> Append
-> Seq Scan on public.bar
! Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*
-> Foreign Scan on public.bar2
! Output: bar2.f1, bar2.f2, bar2.ctid, bar2.tableoid, bar2.*
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
-> Hash
! Output: foo.ctid, foo.tableoid, foo.*, foo.f1
-> HashAggregate
! Output: foo.ctid, foo.tableoid, foo.*, foo.f1
Group Key: foo.f1
-> Append
-> Seq Scan on public.foo
! Output: foo.ctid, foo.tableoid, foo.*, foo.f1
-> Foreign Scan on public.foo2
! Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
(22 rows)
--- 3193,3218 ----
QUERY PLAN
----------------------------------------------------------------------------------------------
LockRows
! Output: bar.f1, bar.f2, bar.ctid, bar.*, bar.tableoid, foo.ctid, foo.*, foo.tableoid
-> Hash Join
! Output: bar.f1, bar.f2, bar.ctid, bar.*, bar.tableoid, foo.ctid, foo.*, foo.tableoid
Hash Cond: (bar.f1 = foo.f1)
-> Append
-> Seq Scan on public.bar
! Output: bar.f1, bar.f2, bar.ctid, bar.*, bar.tableoid
-> Foreign Scan on public.bar2
! Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
-> Hash
! Output: foo.ctid, foo.*, foo.tableoid, foo.f1
-> HashAggregate
! Output: foo.ctid, foo.*, foo.tableoid, foo.f1
Group Key: foo.f1
-> Append
-> Seq Scan on public.foo
! Output: foo.ctid, foo.*, foo.tableoid, foo.f1
-> Foreign Scan on public.foo2
! Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
(22 rows)
***************
*** 3230,3255 **** select * from bar where f1 in (select f1 from foo) for share;
QUERY PLAN
----------------------------------------------------------------------------------------------
LockRows
! Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
-> Hash Join
! Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
Hash Cond: (bar.f1 = foo.f1)
-> Append
-> Seq Scan on public.bar
! Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*
-> Foreign Scan on public.bar2
! Output: bar2.f1, bar2.f2, bar2.ctid, bar2.tableoid, bar2.*
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE
-> Hash
! Output: foo.ctid, foo.tableoid, foo.*, foo.f1
-> HashAggregate
! Output: foo.ctid, foo.tableoid, foo.*, foo.f1
Group Key: foo.f1
-> Append
-> Seq Scan on public.foo
! Output: foo.ctid, foo.tableoid, foo.*, foo.f1
-> Foreign Scan on public.foo2
! Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
(22 rows)
--- 3230,3255 ----
QUERY PLAN
----------------------------------------------------------------------------------------------
LockRows
! Output: bar.f1, bar.f2, bar.ctid, bar.*, bar.tableoid, foo.ctid, foo.*, foo.tableoid
-> Hash Join
! Output: bar.f1, bar.f2, bar.ctid, bar.*, bar.tableoid, foo.ctid, foo.*, foo.tableoid
Hash Cond: (bar.f1 = foo.f1)
-> Append
-> Seq Scan on public.bar
! Output: bar.f1, bar.f2, bar.ctid, bar.*, bar.tableoid
-> Foreign Scan on public.bar2
! Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE
-> Hash
! Output: foo.ctid, foo.*, foo.tableoid, foo.f1
-> HashAggregate
! Output: foo.ctid, foo.*, foo.tableoid, foo.f1
Group Key: foo.f1
-> Append
-> Seq Scan on public.foo
! Output: foo.ctid, foo.*, foo.tableoid, foo.f1
-> Foreign Scan on public.foo2
! Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
(22 rows)
***************
*** 3272,3308 **** update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
Foreign Update on public.bar2
Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
-> Hash Join
! Output: bar.f1, (bar.f2 + 100), bar.ctid, foo.ctid, foo.tableoid, foo.*
Hash Cond: (bar.f1 = foo.f1)
-> Seq Scan on public.bar
Output: bar.f1, bar.f2, bar.ctid
-> Hash
! Output: foo.ctid, foo.tableoid, foo.*, foo.f1
-> HashAggregate
! Output: foo.ctid, foo.tableoid, foo.*, foo.f1
Group Key: foo.f1
-> Append
-> Seq Scan on public.foo
! Output: foo.ctid, foo.tableoid, foo.*, foo.f1
-> Foreign Scan on public.foo2
! Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
-> Hash Join
! Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.tableoid, foo.*
Hash Cond: (bar2.f1 = foo.f1)
-> Foreign Scan on public.bar2
Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
-> Hash
! Output: foo.ctid, foo.tableoid, foo.*, foo.f1
-> HashAggregate
! Output: foo.ctid, foo.tableoid, foo.*, foo.f1
Group Key: foo.f1
-> Append
-> Seq Scan on public.foo
! Output: foo.ctid, foo.tableoid, foo.*, foo.f1
-> Foreign Scan on public.foo2
! Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
(37 rows)
--- 3272,3308 ----
Foreign Update on public.bar2
Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
-> Hash Join
! Output: bar.f1, (bar.f2 + 100), bar.ctid, foo.ctid, foo.*, foo.tableoid
Hash Cond: (bar.f1 = foo.f1)
-> Seq Scan on public.bar
Output: bar.f1, bar.f2, bar.ctid
-> Hash
! Output: foo.ctid, foo.*, foo.tableoid, foo.f1
-> HashAggregate
! Output: foo.ctid, foo.*, foo.tableoid, foo.f1
Group Key: foo.f1
-> Append
-> Seq Scan on public.foo
! Output: foo.ctid, foo.*, foo.tableoid, foo.f1
-> Foreign Scan on public.foo2
! Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
-> Hash Join
! Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid
Hash Cond: (bar2.f1 = foo.f1)
-> Foreign Scan on public.bar2
Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
-> Hash
! Output: foo.ctid, foo.*, foo.tableoid, foo.f1
-> HashAggregate
! Output: foo.ctid, foo.*, foo.tableoid, foo.f1
Group Key: foo.f1
-> Append
-> Seq Scan on public.foo
! Output: foo.ctid, foo.*, foo.tableoid, foo.f1
-> Foreign Scan on public.foo2
! Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
(37 rows)
*** a/src/backend/optimizer/prep/preptlist.c
--- b/src/backend/optimizer/prep/preptlist.c
***************
*** 107,129 **** preprocess_targetlist(PlannerInfo *root, List *tlist)
pstrdup(resname),
true);
tlist = lappend(tlist, tle);
-
- /* if parent of inheritance tree, need the tableoid too */
- if (rc->isParent)
- {
- var = makeVar(rc->rti,
- TableOidAttributeNumber,
- OIDOID,
- -1,
- InvalidOid,
- 0);
- snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId);
- tle = makeTargetEntry((Expr *) var,
- list_length(tlist) + 1,
- pstrdup(resname),
- true);
- tlist = lappend(tlist, tle);
- }
}
if (rc->allMarkTypes & (1 << ROW_MARK_COPY))
{
--- 107,112 ----
***************
*** 139,144 **** preprocess_targetlist(PlannerInfo *root, List *tlist)
--- 122,144 ----
true);
tlist = lappend(tlist, tle);
}
+
+ /* if parent of inheritance tree, need the tableoid too */
+ if (rc->isParent)
+ {
+ var = makeVar(rc->rti,
+ TableOidAttributeNumber,
+ OIDOID,
+ -1,
+ InvalidOid,
+ 0);
+ snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId);
+ tle = makeTargetEntry((Expr *) var,
+ list_length(tlist) + 1,
+ pstrdup(resname),
+ true);
+ tlist = lappend(tlist, tle);
+ }
}
/*
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2015-04-14 17:31 Jim Nasby <Jim.Nasby@BlueTreble.com>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: Jim Nasby @ 2015-04-14 17:31 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On 4/14/15 5:49 AM, Etsuro Fujita wrote:
> postgres=# create foreign table ft1 (c1 int) server myserver options
> (table_name 't1');
> CREATE FOREIGN TABLE
> postgres=# create foreign table ft2 (c1 int) server myserver options
> (table_name 't2');
> CREATE FOREIGN TABLE
> postgres=# alter foreign table ft2 inherit ft1;
> ALTER FOREIGN TABLE
> postgres=# select * from ft1 for update;
> ERROR: could not find junk tableoid1 column
>
> I think this is a bug. Attached is a patch fixing this issue.
What happens when the foreign side breaks the inheritance? Does the FDW
somehow know to check that fact for each query?
What do you gain from having the local table have inheritance?
Basically, I think we have to be very careful about implementing
features that imply the local side knows something about the persisted
state of the remote side (in this case, whether there's inheritance).
Anything like that sets us up for synchronization problems.
--
Jim Nasby, Data Architect, Blue Treble Consulting
Data in Trouble? Get it in Treble! http://BlueTreble.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2015-04-14 18:52 Alvaro Herrera <alvherre@2ndquadrant.com>
parent: Jim Nasby <Jim.Nasby@BlueTreble.com>
0 siblings, 2 replies; 179+ messages in thread
From: Alvaro Herrera @ 2015-04-14 18:52 UTC (permalink / raw)
To: Jim Nasby <Jim.Nasby@BlueTreble.com>; +Cc: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; Tom Lane <tgl@sss.pgh.pa.us>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
Jim Nasby wrote:
> On 4/14/15 5:49 AM, Etsuro Fujita wrote:
> > postgres=# create foreign table ft1 (c1 int) server myserver options
> > (table_name 't1');
> > CREATE FOREIGN TABLE
> > postgres=# create foreign table ft2 (c1 int) server myserver options
> > (table_name 't2');
> > CREATE FOREIGN TABLE
> > postgres=# alter foreign table ft2 inherit ft1;
> > ALTER FOREIGN TABLE
> > postgres=# select * from ft1 for update;
> > ERROR: could not find junk tableoid1 column
> >
> > I think this is a bug. Attached is a patch fixing this issue.
>
> What happens when the foreign side breaks the inheritance? Does the FDW
> somehow know to check that fact for each query?
This is a meaningless question. The remote tables don't have to have an
inheritance relationship already; only the local side sees them as
connected.
I think the real question is whether we're now (I mean after this patch)
emitting useless tableoid columns that we didn't previously have. I
think the answer is yes, and if so I think we need a smaller comb to fix
the problem. This one seems rather large.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2015-04-15 00:35 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: Alvaro Herrera <alvherre@2ndquadrant.com>
1 sibling, 2 replies; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2015-04-15 00:35 UTC (permalink / raw)
To: alvherre@2ndquadrant.com; +Cc: Jim.Nasby@BlueTreble.com; fujita.etsuro@lab.ntt.co.jp; tgl@sss.pgh.pa.us; ashutosh.bapat@enterprisedb.com; hlinnakangas@vmware.com; noah@leadboat.com; shigeru.hanada@gmail.com; pgsql-hackers
Hi,
Before suppressing the symptom, I doubt the necessity and/or
validity of giving foreign tables an ability to be a parent. Is
there any reasonable usage for the ability?
I think we should choose to inhibit foreign tables from becoming
a parent rather than leaving it allowed then taking measures for
the consequent symptom.
regards,
At Tue, 14 Apr 2015 15:52:18 -0300, Alvaro Herrera <alvherre@2ndquadrant.com> wrote in <20150414185218.GX4369@alvh.no-ip.org>
> Jim Nasby wrote:
> > On 4/14/15 5:49 AM, Etsuro Fujita wrote:
> > > postgres=# create foreign table ft1 (c1 int) server myserver options
> > > (table_name 't1');
> > > CREATE FOREIGN TABLE
> > > postgres=# create foreign table ft2 (c1 int) server myserver options
> > > (table_name 't2');
> > > CREATE FOREIGN TABLE
> > > postgres=# alter foreign table ft2 inherit ft1;
> > > ALTER FOREIGN TABLE
> > > postgres=# select * from ft1 for update;
> > > ERROR: could not find junk tableoid1 column
> > >
> > > I think this is a bug. Attached is a patch fixing this issue.
> >
> > What happens when the foreign side breaks the inheritance? Does the FDW
> > somehow know to check that fact for each query?
>
> This is a meaningless question. The remote tables don't have to have an
> inheritance relationship already; only the local side sees them as
> connected.
>
> I think the real question is whether we're now (I mean after this patch)
> emitting useless tableoid columns that we didn't previously have. I
> think the answer is yes, and if so I think we need a smaller comb to fix
> the problem. This one seems rather large.
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2015-04-16 03:20 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Alvaro Herrera <alvherre@2ndquadrant.com>
1 sibling, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2015-04-16 03:20 UTC (permalink / raw)
To: Alvaro Herrera <alvherre@2ndquadrant.com>; Jim Nasby <Jim.Nasby@BlueTreble.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On 2015/04/15 3:52, Alvaro Herrera wrote:
>> On 4/14/15 5:49 AM, Etsuro Fujita wrote:
>>> postgres=# create foreign table ft1 (c1 int) server myserver options
>>> (table_name 't1');
>>> CREATE FOREIGN TABLE
>>> postgres=# create foreign table ft2 (c1 int) server myserver options
>>> (table_name 't2');
>>> CREATE FOREIGN TABLE
>>> postgres=# alter foreign table ft2 inherit ft1;
>>> ALTER FOREIGN TABLE
>>> postgres=# select * from ft1 for update;
>>> ERROR: could not find junk tableoid1 column
>>>
>>> I think this is a bug. Attached is a patch fixing this issue.
> I think the real question is whether we're now (I mean after this patch)
> emitting useless tableoid columns that we didn't previously have. I
> think the answer is yes, and if so I think we need a smaller comb to fix
> the problem. This one seems rather large.
My answer for that would be *no* because I think tableoid would be
needed for EvalPlanQual checking in more complex SELECT FOR UPDATE on
the inheritance or UPDATE/DELETE involving the inheritance as a source
table. Also, if we allow the FDW to change the behavior of SELECT FOR
UPDATE so as to match the local semantics exactly, which I'm working on
in another thread, I think tableoid would also be needed for the actual
row locking.
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2015-04-16 06:18 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 0 replies; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2015-04-16 06:18 UTC (permalink / raw)
To: fujita.etsuro@lab.ntt.co.jp; +Cc: alvherre@2ndquadrant.com; Jim.Nasby@BlueTreble.com; tgl@sss.pgh.pa.us; ashutosh.bapat@enterprisedb.com; hlinnakangas@vmware.com; noah@leadboat.com; shigeru.hanada@gmail.com; pgsql-hackers
Hello,
At Thu, 16 Apr 2015 12:20:47 +0900, Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp> wrote in <552F2A8F.2090406@lab.ntt.co.jp>
> On 2015/04/15 3:52, Alvaro Herrera wrote:
> >> On 4/14/15 5:49 AM, Etsuro Fujita wrote:
> >>> postgres=# create foreign table ft1 (c1 int) server myserver options
> >>> (table_name 't1');
> >>> CREATE FOREIGN TABLE
> >>> postgres=# create foreign table ft2 (c1 int) server myserver options
> >>> (table_name 't2');
> >>> CREATE FOREIGN TABLE
> >>> postgres=# alter foreign table ft2 inherit ft1;
> >>> ALTER FOREIGN TABLE
> >>> postgres=# select * from ft1 for update;
> >>> ERROR: could not find junk tableoid1 column
> >>>
> >>> I think this is a bug. Attached is a patch fixing this issue.
>
> > I think the real question is whether we're now (I mean after this
> > patch)
> > emitting useless tableoid columns that we didn't previously have. I
> > think the answer is yes, and if so I think we need a smaller comb to
> > fix
> > the problem. This one seems rather large.
>
> My answer for that would be *no* because I think tableoid would be
> needed for EvalPlanQual checking in more complex SELECT FOR UPDATE on
> the inheritance or UPDATE/DELETE involving the inheritance as a source
> table. Also, if we allow the FDW to change the behavior of SELECT FOR
> UPDATE so as to match the local semantics exactly, which I'm working
> on in another thread, I think tableoid would also be needed for the
> actual row locking.
Given the parent foreign talbes, surely they need tableoids for
such usage. The patch preserves the condition rc->isParent so it
newly affects exactly only parent foreign tables for now.
Before the parent foreign tables introduced, ROW_MARK_COPY and
RTE_RELATION are mutually exclusive so didn't need, or cannot
have tableoid. But now it intorduces an rte with ROW_MARK_COPY &
RTE_RELATION and there seems no reason for parent tables in any
kind not to have tableoid. After such consideration, I came to
think that the patch is a reasonable fix, not mere a workaround.
Thoughts?
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2015-04-16 07:05 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Tom Lane <tgl@sss.pgh.pa.us>
3 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2015-04-16 07:05 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On 2015/03/23 2:57, Tom Lane wrote:
> Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp> writes:
>> [ fdw-inh-8.patch ]
>
> I've committed this with some substantial rearrangements, notably:
> * As I mentioned earlier, I got rid of a few unnecessary restrictions on
> foreign tables so as to avoid introducing warts into inheritance behavior.
> In particular, we now allow NOT VALID CHECK constraints (and hence ALTER
> ... VALIDATE CONSTRAINT), ALTER SET STORAGE, and ALTER SET WITH/WITHOUT
> OIDS. These are probably no-ops anyway for foreign tables, though
> conceivably an FDW might choose to implement some behavior for STORAGE
> or OIDs.
I agree with you on this point. However, ISTM there is a bug in
handling OIDs on foreign tables; while we now allow for ALTER SET
WITH/WITHOUT OIDS, we still don't allow the default_with_oids parameter
for foreign tables. I think that since CREATE FOREIGN TABLE should be
consistent with ALTER FOREIGN TABLE, we should also allow the parameter
for foreign tables. Attached is a patch for that.
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] tablecmd.patch (677B, ../../552F5F49.6000105@lab.ntt.co.jp/2-tablecmd.patch)
download | inline diff:
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 580,586 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
descriptor = BuildDescForRelation(schema);
localHasOids = interpretOidsOption(stmt->options,
! (relkind == RELKIND_RELATION));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
--- 580,587 ----
descriptor = BuildDescForRelation(schema);
localHasOids = interpretOidsOption(stmt->options,
! (relkind == RELKIND_RELATION ||
! relkind == RELKIND_FOREIGN_TABLE));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2015-04-16 21:43 David Fetter <david@fetter.org>
parent: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: David Fetter @ 2015-04-16 21:43 UTC (permalink / raw)
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: alvherre@2ndquadrant.com; Jim.Nasby@BlueTreble.com; fujita.etsuro@lab.ntt.co.jp; tgl@sss.pgh.pa.us; ashutosh.bapat@enterprisedb.com; hlinnakangas@vmware.com; noah@leadboat.com; shigeru.hanada@gmail.com; pgsql-hackers
On Wed, Apr 15, 2015 at 09:35:05AM +0900, Kyotaro HORIGUCHI wrote:
> Hi,
>
> Before suppressing the symptom, I doubt the necessity and/or
> validity of giving foreign tables an ability to be a parent. Is
> there any reasonable usage for the ability?
>
> I think we should choose to inhibit foreign tables from becoming
> a parent rather than leaving it allowed then taking measures for
> the consequent symptom.
I have a use case for having foreign tables as non-leaf nodes in a
partitioning hierarchy, namely geographic. One might have a table at
HQ called foo_world, then partitions under it called foo_jp, foo_us,
etc., in one level, foo_us_ca, foo_us_pa, etc. in the next level, and
on down, each in general in a separate data center.
Is there something essential about having non-leaf nodes as foreign
tables that's a problem here?
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2015-04-20 08:20 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Etsuro Fujita @ 2015-04-20 08:20 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On 2015/04/16 16:05, Etsuro Fujita wrote:
> On 2015/03/23 2:57, Tom Lane wrote:
>> Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp> writes:
>>> [ fdw-inh-8.patch ]
>>
>> I've committed this with some substantial rearrangements, notably:
>
>> * As I mentioned earlier, I got rid of a few unnecessary restrictions on
>> foreign tables so as to avoid introducing warts into inheritance behavior.
>> In particular, we now allow NOT VALID CHECK constraints (and hence ALTER
>> ... VALIDATE CONSTRAINT), ALTER SET STORAGE, and ALTER SET WITH/WITHOUT
>> OIDS. These are probably no-ops anyway for foreign tables, though
>> conceivably an FDW might choose to implement some behavior for STORAGE
>> or OIDs.
>
> I agree with you on this point. However, ISTM there is a bug in
> handling OIDs on foreign tables; while we now allow for ALTER SET
> WITH/WITHOUT OIDS, we still don't allow the default_with_oids parameter
> for foreign tables. I think that since CREATE FOREIGN TABLE should be
> consistent with ALTER FOREIGN TABLE, we should also allow the parameter
> for foreign tables. Attached is a patch for that.
I also updated docs. Attached is an updated version of the patch.
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] tablecmd-v2.patch (2.2K, ../../5534B6DF.5060601@lab.ntt.co.jp/2-tablecmd-v2.patch)
download | inline diff:
*** a/doc/src/sgml/config.sgml
--- b/doc/src/sgml/config.sgml
***************
*** 6745,6752 **** dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
</term>
<listitem>
<para>
! This controls whether <command>CREATE TABLE</command> and
! <command>CREATE TABLE AS</command> include an OID column in
newly-created tables, if neither <literal>WITH OIDS</literal>
nor <literal>WITHOUT OIDS</literal> is specified. It also
determines whether OIDs will be included in tables created by
--- 6745,6753 ----
</term>
<listitem>
<para>
! This controls whether <command>CREATE TABLE</command>,
! <command>CREATE TABLE AS</command> and
! <command>CREATE FOREIGN TABLE</command> include an OID column in
newly-created tables, if neither <literal>WITH OIDS</literal>
nor <literal>WITHOUT OIDS</literal> is specified. It also
determines whether OIDs will be included in tables created by
*** a/doc/src/sgml/ref/create_foreign_table.sgml
--- b/doc/src/sgml/ref/create_foreign_table.sgml
***************
*** 293,298 **** CHECK ( <replaceable class="PARAMETER">expression</replaceable> )
--- 293,304 ----
responsibility to ensure that the constraint definition matches
reality.
</para>
+
+ <para>
+ To add OIDs to the table created by <command>CREATE FOREIGN TABLE</command>,
+ enable the <xref linkend="guc-default-with-oids"> configuration
+ variable.
+ </para>
</refsect1>
<refsect1 id="SQL-CREATEFOREIGNTABLE-examples">
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 580,586 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
descriptor = BuildDescForRelation(schema);
localHasOids = interpretOidsOption(stmt->options,
! (relkind == RELKIND_RELATION));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
--- 580,587 ----
descriptor = BuildDescForRelation(schema);
localHasOids = interpretOidsOption(stmt->options,
! (relkind == RELKIND_RELATION ||
! relkind == RELKIND_FOREIGN_TABLE));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2015-04-22 15:34 Stephen Frost <sfrost@snowman.net>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
1 sibling, 1 reply; 179+ messages in thread
From: Stephen Frost @ 2015-04-22 15:34 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
Etsuro,
* Etsuro Fujita (fujita.etsuro@lab.ntt.co.jp) wrote:
> postgres=# select * from ft1 for update;
> ERROR: could not find junk tableoid1 column
>
> I think this is a bug. Attached is a patch fixing this issue.
Pushed, thanks!
Stephen
Attachments:
[application/pgp-signature] signature.asc (818B, ../../20150422153408.GO30322@tamriel.snowman.net/2-signature.asc)
download
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2015-04-22 21:00 Robert Haas <robertmhaas@gmail.com>
parent: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
1 sibling, 0 replies; 179+ messages in thread
From: Robert Haas @ 2015-04-22 21:00 UTC (permalink / raw)
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; +Cc: Álvaro Herrera <alvherre@2ndquadrant.com>; Jim Nasby <Jim.Nasby@bluetreble.com>; Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; Tom Lane <tgl@sss.pgh.pa.us>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On Tue, Apr 14, 2015 at 8:35 PM, Kyotaro HORIGUCHI
<horiguchi.kyotaro@lab.ntt.co.jp> wrote:
> Before suppressing the symptom, I doubt the necessity and/or
> validity of giving foreign tables an ability to be a parent. Is
> there any reasonable usage for the ability?
Gee, I don't see why that would be unreasonable or invalid
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2015-04-23 00:10 Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
parent: David Fetter <david@fetter.org>
0 siblings, 0 replies; 179+ messages in thread
From: Kyotaro HORIGUCHI @ 2015-04-23 00:10 UTC (permalink / raw)
To: david@fetter.org; +Cc: alvherre@2ndquadrant.com; Jim.Nasby@BlueTreble.com; fujita.etsuro@lab.ntt.co.jp; tgl@sss.pgh.pa.us; ashutosh.bapat@enterprisedb.com; hlinnakangas@vmware.com; noah@leadboat.com; shigeru.hanada@gmail.com; pgsql-hackers
Hello,
At Thu, 16 Apr 2015 14:43:33 -0700, David Fetter <david@fetter.org> wrote in <20150416214333.GA797@fetter.org>
> On Wed, Apr 15, 2015 at 09:35:05AM +0900, Kyotaro HORIGUCHI wrote:
> > Hi,
> >
> > Before suppressing the symptom, I doubt the necessity and/or
> > validity of giving foreign tables an ability to be a parent. Is
> > there any reasonable usage for the ability?
...
> I have a use case for having foreign tables as non-leaf nodes in a
> partitioning hierarchy, namely geographic.
Ah, I see. I understood the case of intermediate nodes. I agree
that it is quite natural.
> One might have a table at
> HQ called foo_world, then partitions under it called foo_jp, foo_us,
> etc., in one level, foo_us_ca, foo_us_pa, etc. in the next level, and
> on down, each in general in a separate data center.
>
> Is there something essential about having non-leaf nodes as foreign
> tables that's a problem here?
No. I'm convinced of the necessity. Sorry for the noise.
At Wed, 22 Apr 2015 17:00:10 -0400, Robert Haas <robertmhaas@gmail.com> wrote in <CA+TgmobZVHp3D9wWCV8QJc+qGDu7=tEKNCbXOwijZKhjuCmRWg@mail.gmail.com>
> Gee, I don't see why that would be unreasonable or invalid
Hmm. Yes, as mentioned above, there's no reason to refuse
non-leaf foregin tables. I didn't understood the real cause of
the problem and thought that not allowing foreign *root* tables
seem better than tweaking elsewhere. But that thought found to be
totally a garbage :(
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2015-04-23 02:45 Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
parent: Stephen Frost <sfrost@snowman.net>
0 siblings, 0 replies; 179+ messages in thread
From: Etsuro Fujita @ 2015-04-23 02:45 UTC (permalink / raw)
To: Stephen Frost <sfrost@snowman.net>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On 2015/04/23 0:34, Stephen Frost wrote:
> * Etsuro Fujita (fujita.etsuro@lab.ntt.co.jp) wrote:
>> postgres=# select * from ft1 for update;
>> ERROR: could not find junk tableoid1 column
>>
>> I think this is a bug. Attached is a patch fixing this issue.
>
> Pushed, thanks!
Thank you.
Best regards,
Etsuro Fujita
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2015-04-26 00:47 Tom Lane <tgl@sss.pgh.pa.us>
parent: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
0 siblings, 1 reply; 179+ messages in thread
From: Tom Lane @ 2015-04-26 00:47 UTC (permalink / raw)
To: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp> writes:
> On 2015/04/16 16:05, Etsuro Fujita wrote:
>> I agree with you on this point. However, ISTM there is a bug in
>> handling OIDs on foreign tables; while we now allow for ALTER SET
>> WITH/WITHOUT OIDS, we still don't allow the default_with_oids parameter
>> for foreign tables. I think that since CREATE FOREIGN TABLE should be
>> consistent with ALTER FOREIGN TABLE, we should also allow the parameter
>> for foreign tables. Attached is a patch for that.
> I also updated docs. Attached is an updated version of the patch.
I believe that we intentionally did not do this, and here is why not:
existing pg_dump files assume that default_with_oids doesn't affect any
relation type except plain tables. pg_backup_archiver.c only bothers
to change the GUC when about to dump a plain table, and otherwise leaves
it at its previous value. That means if we apply a patch like this, it's
entirely possible that pg_dump/pg_restore will result in foreign tables
accidentally acquiring OID columns.
Since default_with_oids is really only meant as a backwards-compatibility
hack, I don't personally have a problem with restricting it to act only on
plain tables. However, it might be a good idea to explicitly document
this interaction in a code comment to prevent anyone from re-inventing
this idea... I'll go do that.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2015-04-26 08:36 Andres Freund <andres@anarazel.de>
parent: Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 1 reply; 179+ messages in thread
From: Andres Freund @ 2015-04-26 08:36 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
On 2015-04-25 20:47:04 -0400, Tom Lane wrote:
> Since default_with_oids is really only meant as a backwards-compatibility
> hack, I don't personally have a problem with restricting it to act only on
> plain tables.
FWIW, I think we're getting pretty close to the point, or are there
even, where we can remove default_with_oids. So not adding complications
because of it sounds good to me.
Greetings,
Andres Freund
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
* Re: inherit support for foreign tables
@ 2015-04-26 13:25 Tom Lane <tgl@sss.pgh.pa.us>
parent: Andres Freund <andres@anarazel.de>
0 siblings, 0 replies; 179+ messages in thread
From: Tom Lane @ 2015-04-26 13:25 UTC (permalink / raw)
To: Andres Freund <andres@anarazel.de>; +Cc: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>; Heikki Linnakangas <hlinnakangas@vmware.com>; Noah Misch <noah@leadboat.com>; Shigeru Hanada <shigeru.hanada@gmail.com>; pgsql-hackers
Andres Freund <andres@anarazel.de> writes:
> FWIW, I think we're getting pretty close to the point, or are there
> even, where we can remove default_with_oids. So not adding complications
> because of it sounds good to me.
Well, pg_dump uses it --- so the argument about not breaking old dump
scripts would apply to any attempt to remove it entirely. But I don't
have a problem with saying that its semantics are frozen.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 179+ messages in thread
end of thread, other threads:[~2015-04-26 13:25 UTC | newest]
Thread overview: 179+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2013-11-14 04:51 inherit support for foreign tables Shigeru Hanada <shigeru.hanada@gmail.com>
2013-11-14 05:28 ` Tom Lane <tgl@sss.pgh.pa.us>
2013-11-18 14:36 ` Robert Haas <robertmhaas@gmail.com>
2013-11-18 14:55 ` Tom Lane <tgl@sss.pgh.pa.us>
2014-01-14 09:24 ` Shigeru Hanada <shigeru.hanada@gmail.com>
2014-01-14 20:40 ` Shigeru Hanada <shigeru.hanada@gmail.com>
2014-01-21 01:42 ` KaiGai Kohei <kaigai@ak.jp.nec.com>
2014-01-21 02:44 ` Shigeru Hanada <shigeru.hanada@gmail.com>
2014-01-21 05:24 ` KaiGai Kohei <kaigai@ak.jp.nec.com>
2014-01-21 19:09 ` Robert Haas <robertmhaas@gmail.com>
2014-01-21 20:00 ` Tom Lane <tgl@sss.pgh.pa.us>
2014-01-21 20:13 ` Robert Haas <robertmhaas@gmail.com>
2014-01-27 05:51 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-01-27 08:06 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-01-27 12:52 ` Shigeru Hanada <shigeru.hanada@gmail.com>
2014-01-30 05:01 ` Shigeru Hanada <shigeru.hanada@gmail.com>
2014-01-30 16:04 ` Tom Lane <tgl@sss.pgh.pa.us>
2014-01-30 21:48 ` Robert Haas <robertmhaas@gmail.com>
2014-01-30 22:05 ` Tom Lane <tgl@sss.pgh.pa.us>
2014-01-31 00:56 ` Robert Haas <robertmhaas@gmail.com>
2014-01-31 15:21 ` Stephen Frost <sfrost@snowman.net>
2014-01-31 15:30 ` Tom Lane <tgl@sss.pgh.pa.us>
2014-01-31 18:05 ` Stephen Frost <sfrost@snowman.net>
2014-02-03 03:15 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-02-04 11:56 ` Robert Haas <robertmhaas@gmail.com>
2014-02-05 02:20 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-02-07 12:31 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-02-10 12:00 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-02-14 11:00 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-02-25 10:45 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-02-26 03:48 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2014-03-10 08:29 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2014-03-11 05:07 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2014-03-11 05:43 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-03-13 13:00 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-03-17 08:30 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2014-03-18 09:38 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2014-03-20 12:59 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-03-24 01:47 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-03-26 08:14 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2014-03-27 01:49 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-03-27 08:09 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2014-03-27 08:55 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-03-28 04:28 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2014-03-31 08:48 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-03-27 12:53 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-03-31 10:06 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-04-02 12:25 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-05-12 06:52 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-06-20 08:04 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2014-06-23 09:35 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2014-06-24 07:30 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-06-30 06:52 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-06-30 08:47 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2014-06-30 10:47 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-06-30 11:17 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2014-07-01 02:09 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-07-01 06:13 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2014-07-01 06:55 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-07-01 07:04 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2014-07-01 07:30 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-07-01 08:08 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2014-07-01 02:36 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2014-07-01 06:27 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2014-06-30 13:48 ` Tom Lane <tgl@sss.pgh.pa.us>
2014-07-01 02:10 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-08-06 11:43 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-08-08 09:51 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-07-01 06:04 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2014-07-10 09:12 ` Shigeru Hanada <shigeru.hanada@gmail.com>
2014-07-11 06:50 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-07-11 07:18 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-07-02 02:23 ` Noah Misch <noah@leadboat.com>
2014-08-20 11:11 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-08-22 02:51 ` Noah Misch <noah@leadboat.com>
2014-08-22 03:58 ` Alvaro Herrera <alvherre@2ndquadrant.com>
2014-08-22 09:38 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-08-28 09:00 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-09-02 05:22 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2014-09-10 19:32 ` Heikki Linnakangas <hlinnakangas@vmware.com>
2014-09-11 09:22 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-09-11 10:46 ` Heikki Linnakangas <hlinnakangas@vmware.com>
2014-09-11 11:30 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-09-11 11:51 ` Heikki Linnakangas <hlinnakangas@vmware.com>
2014-09-12 07:30 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-10-14 11:00 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-10-21 08:40 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-10-24 06:15 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-11-07 05:57 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2014-11-07 12:01 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-11-12 11:04 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2014-11-17 07:55 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-11-18 09:09 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2014-11-19 05:44 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-11-13 06:23 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2014-11-13 06:50 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-11-13 06:51 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2014-11-17 08:55 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2014-11-27 10:22 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-11-28 09:14 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2014-12-02 02:59 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-12-03 10:35 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2014-12-03 10:39 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2014-12-04 03:35 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-12-04 04:30 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2014-12-04 04:48 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2014-12-06 15:51 ` Noah Misch <noah@leadboat.com>
2014-12-08 06:17 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2014-12-08 12:10 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-12-06 17:02 ` David Fetter <david@fetter.org>
2014-12-08 02:15 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-12-10 03:18 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-12-10 05:47 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2014-12-11 03:09 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-12-11 05:54 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2014-12-11 10:30 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-12-15 01:35 ` Michael Paquier <michael.paquier@gmail.com>
2014-12-17 22:04 ` Tom Lane <tgl@sss.pgh.pa.us>
2014-12-18 01:49 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-12-22 11:20 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-12-22 15:36 ` Tom Lane <tgl@sss.pgh.pa.us>
2014-12-26 06:10 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2015-01-15 07:35 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2015-02-13 07:03 ` Michael Paquier <michael.paquier@gmail.com>
2015-02-20 06:59 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2015-03-22 17:57 ` Tom Lane <tgl@sss.pgh.pa.us>
2015-03-22 18:39 ` Robert Haas <robertmhaas@gmail.com>
2015-03-23 06:01 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2015-03-24 02:15 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2015-04-14 10:49 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2015-04-14 17:31 ` Jim Nasby <Jim.Nasby@BlueTreble.com>
2015-04-14 18:52 ` Alvaro Herrera <alvherre@2ndquadrant.com>
2015-04-15 00:35 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2015-04-16 21:43 ` David Fetter <david@fetter.org>
2015-04-23 00:10 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2015-04-22 21:00 ` Robert Haas <robertmhaas@gmail.com>
2015-04-16 03:20 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2015-04-16 06:18 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2015-04-22 15:34 ` Stephen Frost <sfrost@snowman.net>
2015-04-23 02:45 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2015-04-16 07:05 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2015-04-20 08:20 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2015-04-26 00:47 ` Tom Lane <tgl@sss.pgh.pa.us>
2015-04-26 08:36 ` Andres Freund <andres@anarazel.de>
2015-04-26 13:25 ` Tom Lane <tgl@sss.pgh.pa.us>
2015-03-20 18:50 ` Tom Lane <tgl@sss.pgh.pa.us>
2014-10-16 07:39 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-11-07 07:54 ` furuyao@pm.nttdata.co.jp
2014-11-07 08:17 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-08-21 09:30 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-03-25 12:48 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-02-18 05:47 ` Shigeru Hanada <shigeru.hanada@gmail.com>
2014-02-18 10:24 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-02-19 03:12 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2014-02-19 10:40 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-02-20 06:47 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2014-02-20 10:55 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-02-21 02:38 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-02-21 06:23 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2014-02-21 07:33 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-02-21 08:01 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2014-02-21 07:03 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2014-02-18 10:29 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2014-02-18 14:49 ` Tom Lane <tgl@sss.pgh.pa.us>
2014-02-19 00:16 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2014-02-19 07:17 ` Shigeru Hanada <shigeru.hanada@gmail.com>
2014-02-20 07:05 ` Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
2014-01-27 15:33 ` David Fetter <david@fetter.org>
2014-01-27 15:55 ` Atri Sharma <atri.jiit@gmail.com>
2014-01-28 04:07 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-01-28 04:34 ` Kouhei Kaigai <kaigai@ak.jp.nec.com>
2014-01-30 05:06 ` Atri Sharma <atri.jiit@gmail.com>
2014-01-14 22:35 ` Jim Nasby <jim@nasby.net>
2014-01-23 12:59 [Review] inherit support for foreign tables Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-01-25 02:27 ` Shigeru Hanada <shigeru.hanada@gmail.com>
2014-01-27 03:59 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-01-27 12:49 ` Shigeru Hanada <shigeru.hanada@gmail.com>
2014-01-28 13:01 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2014-01-31 02:55 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox