Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Haas2011-01-02 04:48:11 +0000
committerRobert Haas2011-01-02 04:48:11 +0000
commit0d692a0dc9f0e532c67c577187fe5d7d323cb95b (patch)
tree5177be3794b8ffa768a3cd852221425bd2a74347 /src/backend
parent6600d5e91c754789002ed794c18cb856c190f58f (diff)
Basic foreign table support.
Foreign tables are a core component of SQL/MED. This commit does not provide a working SQL/MED infrastructure, because foreign tables cannot yet be queried. Support for foreign table scans will need to be added in a future patch. However, this patch creates the necessary system catalog structure, syntax support, and support for ancillary operations such as COMMENT and SECURITY LABEL. Shigeru Hanada, heavily revised by Robert Haas
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/access/common/reloptions.c5
-rw-r--r--src/backend/access/heap/heapam.c3
-rw-r--r--src/backend/catalog/Makefile1
-rw-r--r--src/backend/catalog/aclchk.c101
-rw-r--r--src/backend/catalog/dependency.c10
-rw-r--r--src/backend/catalog/heap.c33
-rw-r--r--src/backend/catalog/information_schema.sql73
-rw-r--r--src/backend/catalog/objectaddress.c8
-rw-r--r--src/backend/catalog/system_views.sql5
-rw-r--r--src/backend/commands/alter.c3
-rw-r--r--src/backend/commands/analyze.c2
-rw-r--r--src/backend/commands/comment.c17
-rw-r--r--src/backend/commands/copy.c11
-rw-r--r--src/backend/commands/foreigncmds.c93
-rw-r--r--src/backend/commands/seclabel.c6
-rw-r--r--src/backend/commands/tablecmds.c346
-rw-r--r--src/backend/commands/vacuum.c2
-rw-r--r--src/backend/executor/execMain.c6
-rw-r--r--src/backend/nodes/copyfuncs.c36
-rw-r--r--src/backend/nodes/equalfuncs.c15
-rw-r--r--src/backend/nodes/outfuncs.c14
-rw-r--r--src/backend/optimizer/util/plancat.c6
-rw-r--r--src/backend/parser/gram.y137
-rw-r--r--src/backend/parser/parse_utilcmd.c7
-rw-r--r--src/backend/tcop/utility.c47
-rw-r--r--src/backend/utils/adt/acl.c4
-rw-r--r--src/backend/utils/cache/syscache.c12
27 files changed, 871 insertions, 132 deletions
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index eb6ccc6582c..d2e0531e354 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -782,6 +782,9 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, Oid amoptions)
case RELKIND_INDEX:
options = index_reloptions(amoptions, datum, false);
break;
+ case RELKIND_FOREIGN_TABLE:
+ options = NULL;
+ break;
default:
Assert(false); /* can't get here */
options = NULL; /* keep compiler quiet */
@@ -1174,7 +1177,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
case RELKIND_RELATION:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
- /* sequences, composite types and views are not supported */
+ /* other relkinds are not supported */
return NULL;
}
}
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index ac744e4187e..25d9fdea3a0 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -1060,7 +1060,8 @@ relation_close(Relation relation, LOCKMODE lockmode)
*
* This is essentially relation_open plus check that the relation
* is not an index nor a composite type. (The caller should also
- * check that it's not a view before assuming it has storage.)
+ * check that it's not a view or foreign table before assuming it has
+ * storage.)
* ----------------
*/
Relation
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 6a47f398ed8..17f4cc6cfc9 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -38,6 +38,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
pg_ts_parser.h pg_ts_template.h \
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
+ pg_foreign_table.h \
pg_default_acl.h pg_seclabel.h \
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 01105aae1e6..e07db507c09 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -272,6 +272,9 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs,
case ACL_KIND_FOREIGN_SERVER:
whole_mask = ACL_ALL_RIGHTS_FOREIGN_SERVER;
break;
+ case ACL_KIND_FOREIGN_TABLE:
+ whole_mask = ACL_ALL_RIGHTS_FOREIGN_TABLE;
+ break;
default:
elog(ERROR, "unrecognized object kind: %d", objkind);
/* not reached, but keep compiler quiet */
@@ -475,6 +478,10 @@ ExecuteGrantStmt(GrantStmt *stmt)
all_privileges = ACL_ALL_RIGHTS_FOREIGN_SERVER;
errormsg = gettext_noop("invalid privilege type %s for foreign server");
break;
+ case ACL_OBJECT_FOREIGN_TABLE:
+ all_privileges = ACL_ALL_RIGHTS_FOREIGN_TABLE;
+ errormsg = gettext_noop("invalid privilege type %s for foreign table");
+ break;
default:
elog(ERROR, "unrecognized GrantStmt.objtype: %d",
(int) stmt->objtype);
@@ -545,6 +552,7 @@ ExecGrantStmt_oids(InternalGrant *istmt)
{
case ACL_OBJECT_RELATION:
case ACL_OBJECT_SEQUENCE:
+ case ACL_OBJECT_FOREIGN_TABLE:
ExecGrant_Relation(istmt);
break;
case ACL_OBJECT_DATABASE:
@@ -594,6 +602,7 @@ objectNamesToOids(GrantObjectType objtype, List *objnames)
{
case ACL_OBJECT_RELATION:
case ACL_OBJECT_SEQUENCE:
+ case ACL_OBJECT_FOREIGN_TABLE:
foreach(cell, objnames)
{
RangeVar *relvar = (RangeVar *) lfirst(cell);
@@ -718,11 +727,13 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
switch (objtype)
{
case ACL_OBJECT_RELATION:
- /* Process both regular tables and views */
+ /* Process regular tables, views and foreign tables */
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_FOREIGN_TABLE);
+ objects = list_concat(objects, objs);
break;
case ACL_OBJECT_SEQUENCE:
objs = getRelationsInNamespace(namespaceId, RELKIND_SEQUENCE);
@@ -1689,11 +1700,21 @@ ExecGrant_Relation(InternalGrant *istmt)
errmsg("\"%s\" is not a sequence",
NameStr(pg_class_tuple->relname))));
- /* Adjust the default permissions based on whether it is a sequence */
+ /* Used GRANT FOREIGN TABLE on a non-foreign-table? */
+ if (istmt->objtype == ACL_OBJECT_FOREIGN_TABLE &&
+ pg_class_tuple->relkind != RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is not a foreign table",
+ NameStr(pg_class_tuple->relname))));
+
+ /* Adjust the default permissions based on object type */
if (istmt->all_privs && istmt->privileges == ACL_NO_RIGHTS)
{
if (pg_class_tuple->relkind == RELKIND_SEQUENCE)
this_privileges = ACL_ALL_RIGHTS_SEQUENCE;
+ else if (pg_class_tuple->relkind == RELKIND_FOREIGN_TABLE)
+ this_privileges = ACL_ALL_RIGHTS_FOREIGN_TABLE;
else
this_privileges = ACL_ALL_RIGHTS_RELATION;
}
@@ -1729,6 +1750,16 @@ ExecGrant_Relation(InternalGrant *istmt)
this_privileges &= (AclMode) ACL_ALL_RIGHTS_SEQUENCE;
}
}
+ else if (pg_class_tuple->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ if (this_privileges & ~((AclMode) ACL_ALL_RIGHTS_FOREIGN_TABLE))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_GRANT_OPERATION),
+ errmsg("foreign table \"%s\" only supports SELECT privileges",
+ NameStr(pg_class_tuple->relname))));
+ }
+ }
else
{
if (this_privileges & ~((AclMode) ACL_ALL_RIGHTS_RELATION))
@@ -1781,9 +1812,18 @@ ExecGrant_Relation(InternalGrant *istmt)
&isNull);
if (isNull)
{
- old_acl = acldefault(pg_class_tuple->relkind == RELKIND_SEQUENCE ?
- ACL_OBJECT_SEQUENCE : ACL_OBJECT_RELATION,
- ownerId);
+ switch (pg_class_tuple->relkind)
+ {
+ case RELKIND_SEQUENCE:
+ old_acl = acldefault(ACL_OBJECT_SEQUENCE, ownerId);
+ break;
+ case RELKIND_FOREIGN_TABLE:
+ old_acl = acldefault(ACL_OBJECT_FOREIGN_TABLE, ownerId);
+ break;
+ default:
+ old_acl = acldefault(ACL_OBJECT_RELATION, ownerId);
+ break;
+ }
/* There are no old member roles according to the catalogs */
noldmembers = 0;
oldmembers = NULL;
@@ -1812,12 +1852,26 @@ ExecGrant_Relation(InternalGrant *istmt)
bool replaces[Natts_pg_class];
int nnewmembers;
Oid *newmembers;
+ AclObjectKind aclkind;
/* Determine ID to do the grant as, and available grant options */
select_best_grantor(GetUserId(), this_privileges,
old_acl, ownerId,
&grantorId, &avail_goptions);
+ switch (pg_class_tuple->relkind)
+ {
+ case RELKIND_SEQUENCE:
+ aclkind = ACL_KIND_SEQUENCE;
+ break;
+ case RELKIND_FOREIGN_TABLE:
+ aclkind = ACL_KIND_FOREIGN_TABLE;
+ break;
+ default:
+ aclkind = ACL_KIND_CLASS;
+ break;
+ }
+
/*
* Restrict the privileges to what we can actually grant, and emit
* the standards-mandated warning and error messages.
@@ -1825,9 +1879,7 @@ ExecGrant_Relation(InternalGrant *istmt)
this_privileges =
restrict_and_check_grant(istmt->is_grant, avail_goptions,
istmt->all_privs, this_privileges,
- relOid, grantorId,
- pg_class_tuple->relkind == RELKIND_SEQUENCE
- ? ACL_KIND_SEQUENCE : ACL_KIND_CLASS,
+ relOid, grantorId, aclkind,
NameStr(pg_class_tuple->relname),
0, NULL);
@@ -1909,6 +1961,16 @@ ExecGrant_Relation(InternalGrant *istmt)
this_privileges &= (AclMode) ACL_SELECT;
}
+ else if (pg_class_tuple->relkind == RELKIND_FOREIGN_TABLE &&
+ this_privileges & ~((AclMode) ACL_SELECT))
+ {
+ /* Foreign tables have the same restriction as sequences. */
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_GRANT_OPERATION),
+ errmsg("foreign table \"%s\" only supports SELECT column privileges",
+ NameStr(pg_class_tuple->relname))));
+ this_privileges &= (AclMode) ACL_SELECT;
+ }
expand_col_privileges(col_privs->cols, relOid,
this_privileges,
@@ -3080,7 +3142,9 @@ static const char *const no_priv_msg[MAX_ACL_KIND] =
/* ACL_KIND_FDW */
gettext_noop("permission denied for foreign-data wrapper %s"),
/* ACL_KIND_FOREIGN_SERVER */
- gettext_noop("permission denied for foreign server %s")
+ gettext_noop("permission denied for foreign server %s"),
+ /* ACL_KIND_FOREIGN_TABLE */
+ gettext_noop("permission denied for foreign table %s"),
};
static const char *const not_owner_msg[MAX_ACL_KIND] =
@@ -3120,7 +3184,9 @@ static const char *const not_owner_msg[MAX_ACL_KIND] =
/* ACL_KIND_FDW */
gettext_noop("must be owner of foreign-data wrapper %s"),
/* ACL_KIND_FOREIGN_SERVER */
- gettext_noop("must be owner of foreign server %s")
+ gettext_noop("must be owner of foreign server %s"),
+ /* ACL_KIND_FOREIGN_TABLE */
+ gettext_noop("must be owner of foreign table %s"),
};
@@ -3410,9 +3476,18 @@ pg_class_aclmask(Oid table_oid, Oid roleid,
if (isNull)
{
/* No ACL, so build default ACL */
- acl = acldefault(classForm->relkind == RELKIND_SEQUENCE ?
- ACL_OBJECT_SEQUENCE : ACL_OBJECT_RELATION,
- ownerId);
+ switch (classForm->relkind)
+ {
+ case RELKIND_SEQUENCE:
+ acl = acldefault(ACL_OBJECT_SEQUENCE, ownerId);
+ break;
+ case RELKIND_FOREIGN_TABLE:
+ acl = acldefault(ACL_OBJECT_FOREIGN_TABLE, ownerId);
+ break;
+ default:
+ acl = acldefault(ACL_OBJECT_RELATION, ownerId);
+ break;
+ }
aclDatum = (Datum) 0;
}
else
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 7627c7dd20b..ec8eb74954a 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -36,6 +36,7 @@
#include "catalog/pg_depend.h"
#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_foreign_server.h"
+#include "catalog/pg_foreign_table.h"
#include "catalog/pg_language.h"
#include "catalog/pg_largeobject.h"
#include "catalog/pg_namespace.h"
@@ -152,6 +153,7 @@ static const Oid object_classes[MAX_OCLASS] = {
ForeignDataWrapperRelationId, /* OCLASS_FDW */
ForeignServerRelationId, /* OCLASS_FOREIGN_SERVER */
UserMappingRelationId, /* OCLASS_USER_MAPPING */
+ ForeignTableRelationId, /* OCLASS_FOREIGN_TABLE */
DefaultAclRelationId /* OCLASS_DEFACL */
};
@@ -2072,6 +2074,10 @@ getObjectClass(const ObjectAddress *object)
case UserMappingRelationId:
return OCLASS_USER_MAPPING;
+ case ForeignTableRelationId:
+ Assert(object->objectSubId == 0);
+ return OCLASS_FOREIGN_TABLE;
+
case DefaultAclRelationId:
return OCLASS_DEFACL;
}
@@ -2754,6 +2760,10 @@ getRelationDescription(StringInfo buffer, Oid relid)
appendStringInfo(buffer, _("composite type %s"),
relname);
break;
+ case RELKIND_FOREIGN_TABLE:
+ appendStringInfo(buffer, _("foreign table %s"),
+ relname);
+ break;
default:
/* shouldn't get here */
appendStringInfo(buffer, _("relation %s"),
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 7cdff403d8d..4c55db7e3cc 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -43,6 +43,7 @@
#include "catalog/objectaccess.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_constraint.h"
+#include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_statistic.h"
@@ -269,6 +270,7 @@ heap_create(const char *relname,
{
case RELKIND_VIEW:
case RELKIND_COMPOSITE_TYPE:
+ case RELKIND_FOREIGN_TABLE:
create_storage = false;
/*
@@ -987,7 +989,8 @@ heap_create_with_catalog(const char *relname,
/* Use binary-upgrade overrides if applicable */
if (OidIsValid(binary_upgrade_next_heap_relfilenode) &&
(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_COMPOSITE_TYPE))
+ relkind == RELKIND_VIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
{
relid = binary_upgrade_next_heap_relfilenode;
binary_upgrade_next_heap_relfilenode = InvalidOid;
@@ -1012,6 +1015,7 @@ heap_create_with_catalog(const char *relname,
{
case RELKIND_RELATION:
case RELKIND_VIEW:
+ case RELKIND_FOREIGN_TABLE:
relacl = get_user_default_acl(ACL_OBJECT_RELATION, ownerid,
relnamespace);
break;
@@ -1049,10 +1053,12 @@ heap_create_with_catalog(const char *relname,
* Decide whether to create an array type over the relation's rowtype. We
* do not create any array types for system catalogs (ie, those made
* during initdb). We create array types for regular relations, views,
- * and composite types ... but not, eg, for toast tables or sequences.
+ * composite types and foreign tables ... but not, eg, for toast tables or
+ * sequences.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
relkind == RELKIND_VIEW ||
+ relkind == RELKIND_FOREIGN_TABLE ||
relkind == RELKIND_COMPOSITE_TYPE))
new_array_oid = AssignTypeArrayOid();
@@ -1590,10 +1596,31 @@ heap_drop_with_catalog(Oid relid)
RelationGetRelationName(rel))));
/*
+ * Delete pg_foreign_table tuple first.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(ForeignTableRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for foreign table %u", relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+ }
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
- rel->rd_rel->relkind != RELKIND_COMPOSITE_TYPE)
+ rel->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
+ rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
{
RelationDropStorage(rel);
}
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index c4f9d4d5c43..090c10c3220 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -465,7 +465,7 @@ CREATE VIEW column_domain_usage AS
AND a.attrelid = c.oid
AND a.atttypid = t.oid
AND t.typtype = 'd'
- AND c.relkind IN ('r', 'v')
+ AND c.relkind IN ('r', 'v', 'f')
AND a.attnum > 0
AND NOT a.attisdropped
AND pg_has_role(t.typowner, 'USAGE');
@@ -504,7 +504,7 @@ CREATE VIEW column_privileges AS
pr_c.relowner
FROM (SELECT oid, relname, relnamespace, relowner, (aclexplode(relacl)).*
FROM pg_class
- WHERE relkind IN ('r', 'v')
+ WHERE relkind IN ('r', 'v', 'f')
) pr_c (oid, relname, relnamespace, relowner, grantor, grantee, prtype, grantable),
pg_attribute a
WHERE a.attrelid = pr_c.oid
@@ -526,7 +526,7 @@ CREATE VIEW column_privileges AS
) pr_a (attrelid, attname, grantor, grantee, prtype, grantable),
pg_class c
WHERE pr_a.attrelid = c.oid
- AND relkind IN ('r','v')
+ AND relkind IN ('r', 'v', 'f')
) x,
pg_namespace nc,
pg_authid u_grantor,
@@ -569,7 +569,7 @@ CREATE VIEW column_udt_usage AS
WHERE a.attrelid = c.oid
AND a.atttypid = t.oid
AND nc.oid = c.relnamespace
- AND a.attnum > 0 AND NOT a.attisdropped AND c.relkind in ('r', 'v')
+ AND a.attnum > 0 AND NOT a.attisdropped AND c.relkind in ('r', 'v', 'f')
AND pg_has_role(coalesce(bt.typowner, t.typowner), 'USAGE');
GRANT SELECT ON column_udt_usage TO PUBLIC;
@@ -692,7 +692,7 @@ CREATE VIEW columns AS
AND nc.oid = c.relnamespace
AND (NOT pg_is_other_temp_schema(nc.oid))
- AND a.attnum > 0 AND NOT a.attisdropped AND c.relkind in ('r', 'v')
+ AND a.attnum > 0 AND NOT a.attisdropped AND c.relkind in ('r', 'v', 'f')
AND (pg_has_role(c.relowner, 'USAGE')
OR has_column_privilege(c.oid, a.attnum,
@@ -1793,6 +1793,7 @@ CREATE VIEW tables AS
CASE WHEN nc.oid = pg_my_temp_schema() THEN 'LOCAL TEMPORARY'
WHEN c.relkind = 'r' THEN 'BASE TABLE'
WHEN c.relkind = 'v' THEN 'VIEW'
+ WHEN c.relkind = 'f' THEN 'FOREIGN TABLE'
ELSE null END
AS character_data) AS table_type,
@@ -1817,7 +1818,7 @@ CREATE VIEW tables AS
FROM pg_namespace nc JOIN pg_class c ON (nc.oid = c.relnamespace)
LEFT JOIN (pg_type t JOIN pg_namespace nt ON (t.typnamespace = nt.oid)) ON (c.reloftype = t.oid)
- WHERE c.relkind IN ('r', 'v')
+ WHERE c.relkind IN ('r', 'v', 'f')
AND (NOT pg_is_other_temp_schema(nc.oid))
AND (pg_has_role(c.relowner, 'USAGE')
OR has_table_privilege(c.oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER')
@@ -2129,7 +2130,7 @@ CREATE VIEW view_column_usage AS
AND dt.refclassid = 'pg_catalog.pg_class'::regclass
AND dt.refobjid = t.oid
AND t.relnamespace = nt.oid
- AND t.relkind IN ('r', 'v')
+ AND t.relkind IN ('r', 'v', 'f')
AND t.oid = a.attrelid
AND dt.refobjsubid = a.attnum
AND pg_has_role(t.relowner, 'USAGE');
@@ -2199,7 +2200,7 @@ CREATE VIEW view_table_usage AS
AND dt.refclassid = 'pg_catalog.pg_class'::regclass
AND dt.refobjid = t.oid
AND t.relnamespace = nt.oid
- AND t.relkind IN ('r', 'v')
+ AND t.relkind IN ('r', 'v', 'f')
AND pg_has_role(t.relowner, 'USAGE');
GRANT SELECT ON view_table_usage TO PUBLIC;
@@ -2344,7 +2345,7 @@ CREATE VIEW element_types AS
'TABLE'::text, a.attnum, a.atttypid
FROM pg_class c, pg_attribute a
WHERE c.oid = a.attrelid
- AND c.relkind IN ('r', 'v')
+ AND c.relkind IN ('r', 'v', 'f')
AND attnum > 0 AND NOT attisdropped
UNION ALL
@@ -2481,6 +2482,60 @@ CREATE VIEW foreign_servers AS
GRANT SELECT ON foreign_servers TO PUBLIC;
+/* Base view for foreign tables */
+CREATE VIEW _pg_foreign_tables AS
+ SELECT
+ CAST(current_database() AS sql_identifier) AS foreign_table_catalog,
+ n.nspname AS foreign_table_schema,
+ c.relname AS foreign_table_name,
+ t.ftoptions AS ftoptions,
+ CAST(current_database() AS sql_identifier) AS foreign_server_catalog,
+ CAST(srvname AS sql_identifier) AS foreign_server_name,
+ CAST(u.rolname AS sql_identifier) AS authorization_identifier
+ FROM pg_foreign_table t, pg_foreign_server s, pg_foreign_data_wrapper w,
+ pg_authid u, pg_namespace n, pg_class c
+ WHERE w.oid = s.srvfdw
+ AND u.oid = c.relowner
+ AND (pg_has_role(c.relowner, 'USAGE')
+ OR has_table_privilege(c.oid, 'SELECT')
+ OR has_any_column_privilege(c.oid, 'SELECT'))
+ AND n.oid = c.relnamespace
+ AND c.oid = t.ftrelid
+ AND c.relkind = 'f'
+ AND s.oid = t.ftserver;
+
+
+/*
+ * 24.8
+ * FOREIGN_TABLE_OPTIONS view
+ */
+CREATE VIEW foreign_table_options AS
+ SELECT foreign_table_catalog,
+ foreign_table_schema,
+ foreign_table_name,
+ CAST((pg_options_to_table(t.ftoptions)).option_name AS sql_identifier) AS option_name,
+ CAST((pg_options_to_table(t.ftoptions)).option_value AS character_data) AS option_value
+ FROM _pg_foreign_tables t;
+
+GRANT SELECT ON TABLE foreign_table_options TO PUBLIC;
+
+
+/*
+ * 24.9
+ * FOREIGN_TABLES view
+ */
+CREATE VIEW foreign_tables AS
+ SELECT foreign_table_catalog,
+ foreign_table_schema,
+ foreign_table_name,
+ foreign_server_catalog,
+ foreign_server_name
+ FROM _pg_foreign_tables;
+
+GRANT SELECT ON foreign_tables TO PUBLIC;
+
+
+
/* Base view for user mappings */
CREATE VIEW _pg_user_mappings AS
SELECT um.oid,
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index a81d1198e4f..c4608f7f173 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -111,6 +111,7 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
case OBJECT_SEQUENCE:
case OBJECT_TABLE:
case OBJECT_VIEW:
+ case OBJECT_FOREIGN_TABLE:
relation =
get_relation_by_qualified_name(objtype, objname, lockmode);
address.classId = RelationRelationId;
@@ -369,6 +370,13 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
errmsg("\"%s\" is not a view",
RelationGetRelationName(relation))));
break;
+ case OBJECT_FOREIGN_TABLE:
+ if (relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is not a foreign table",
+ RelationGetRelationName(relation))));
+ break;
default:
elog(ERROR, "unrecognized objtype: %d", (int) objtype);
break;
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 8e25c9890af..ded046d9d06 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -167,8 +167,9 @@ CREATE VIEW pg_seclabels AS
SELECT
l.objoid, l.classoid, l.objsubid,
CASE WHEN rel.relkind = 'r' THEN 'table'::text
- WHEN rel.relkind = 'v' THEN 'view'::text
- WHEN rel.relkind = 'S' THEN 'sequence'::text END AS objtype,
+ WHEN rel.relkind = 'v' THEN 'view'::text
+ WHEN rel.relkind = 'S' THEN 'sequence'::text
+ WHEN rel.relkind = 'f' THEN 'foreign table'::text END AS objtype,
rel.relnamespace AS objnamespace,
CASE WHEN pg_table_is_visible(rel.oid)
THEN quote_ident(rel.relname)
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 5c93acb8b4e..6a9b21d01fe 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -95,6 +95,7 @@ ExecRenameStmt(RenameStmt *stmt)
case OBJECT_COLUMN:
case OBJECT_ATTRIBUTE:
case OBJECT_TRIGGER:
+ case OBJECT_FOREIGN_TABLE:
{
Oid relid;
@@ -108,6 +109,7 @@ ExecRenameStmt(RenameStmt *stmt)
case OBJECT_SEQUENCE:
case OBJECT_VIEW:
case OBJECT_INDEX:
+ case OBJECT_FOREIGN_TABLE:
{
/*
* RENAME TABLE requires that we (still) hold
@@ -206,6 +208,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt)
case OBJECT_SEQUENCE:
case OBJECT_TABLE:
case OBJECT_VIEW:
+ case OBJECT_FOREIGN_TABLE:
CheckRelationOwnership(stmt->relation, true);
AlterTableNamespace(stmt->relation, stmt->newschema,
stmt->objectType, AccessExclusiveLock);
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index bd6f9651310..7bc5f111f4d 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -187,7 +187,7 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt,
/* No need for a WARNING if we already complained during VACUUM */
if (!(vacstmt->options & VACOPT_VACUUM))
ereport(WARNING,
- (errmsg("skipping \"%s\" --- cannot analyze indexes, views, or special system tables",
+ (errmsg("skipping \"%s\" --- cannot analyze non-tables or special system tables",
RelationGetRelationName(onerel))));
relation_close(onerel, ShareUpdateExclusiveLock);
return;
diff --git a/src/backend/commands/comment.c b/src/backend/commands/comment.c
index 2c4242f78fa..2e8c4df9272 100644
--- a/src/backend/commands/comment.c
+++ b/src/backend/commands/comment.c
@@ -91,6 +91,7 @@ CommentObject(CommentStmt *stmt)
case OBJECT_SEQUENCE:
case OBJECT_TABLE:
case OBJECT_VIEW:
+ case OBJECT_FOREIGN_TABLE:
if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
RelationGetRelationName(relation));
@@ -574,18 +575,20 @@ CheckAttributeComment(Relation relation)
RelationGetRelationName(relation));
/*
- * Allow comments only on columns of tables, views, and composite types
- * (which are the only relkinds for which pg_dump will dump per-column
- * comments). In particular we wish to disallow comments on index
- * columns, because the naming of an index's columns may change across PG
- * versions, so dumping per-column comments could create reload failures.
+ * Allow comments only on columns of tables, views, composite types, and
+ * foreign tables (which are the only relkinds for which pg_dump will dump
+ * per-column comments). In particular we wish to disallow comments on
+ * index columns, because the naming of an index's columns may change
+ * across PG versions, so dumping per-column comments could create reload
+ * failures.
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_VIEW &&
- relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE)
+ relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("\"%s\" is not a table, view, or composite type",
+ errmsg("\"%s\" is not a table, view, composite type, or foreign table",
RelationGetRelationName(relation))));
}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 2d06ea2030e..841bf220c76 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1235,6 +1235,12 @@ DoCopyTo(CopyState cstate)
errmsg("cannot copy from view \"%s\"",
RelationGetRelationName(cstate->rel)),
errhint("Try the COPY (SELECT ...) TO variant.")));
+ else if (cstate->rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from foreign table \"%s\"",
+ RelationGetRelationName(cstate->rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
else if (cstate->rel->rd_rel->relkind == RELKIND_SEQUENCE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1708,6 +1714,11 @@ CopyFrom(CopyState cstate)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy to view \"%s\"",
RelationGetRelationName(cstate->rel))));
+ else if (cstate->rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy to foreign table \"%s\"",
+ RelationGetRelationName(cstate->rel))));
else if (cstate->rel->rd_rel->relkind == RELKIND_SEQUENCE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c
index db5017bcac6..2774fc52ba4 100644
--- a/src/backend/commands/foreigncmds.c
+++ b/src/backend/commands/foreigncmds.c
@@ -14,6 +14,7 @@
#include "postgres.h"
#include "access/heapam.h"
+#include "access/xact.h"
#include "access/reloptions.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
@@ -21,6 +22,7 @@
#include "catalog/objectaccess.h"
#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_foreign_server.h"
+#include "catalog/pg_foreign_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "catalog/pg_user_mapping.h"
@@ -88,7 +90,7 @@ optionListToArray(List *options)
*
* This is used by CREATE/ALTER of FOREIGN DATA WRAPPER/SERVER/USER MAPPING.
*/
-static Datum
+Datum
transformGenericOptions(Oid catalogId,
Datum oldOptions,
List *options,
@@ -1158,3 +1160,92 @@ RemoveUserMappingById(Oid umId)
heap_close(rel, RowExclusiveLock);
}
+
+/*
+ * Create a foreign table
+ * call after DefineRelation().
+ */
+void
+CreateForeignTable(CreateForeignTableStmt *stmt, Oid relid)
+{
+ Relation ftrel;
+ Datum ftoptions;
+ Datum values[Natts_pg_foreign_table];
+ bool nulls[Natts_pg_foreign_table];
+ HeapTuple tuple;
+ Oid ftId;
+ AclResult aclresult;
+ ObjectAddress myself;
+ ObjectAddress referenced;
+ Oid ownerId;
+ ForeignDataWrapper *fdw;
+ ForeignServer *server;
+
+ /*
+ * Advance command counter to ensure the pg_attribute tuple visible; the
+ * tuple might be updated to add constraints in previous step.
+ */
+ CommandCounterIncrement();
+
+ /*
+ * For now the owner cannot be specified on create. Use effective user ID.
+ */
+ ownerId = GetUserId();
+
+ /*
+ * Check that the foreign server exists and that we have USAGE on it. Also
+ * get the actual FDW for option validation etc.
+ */
+ server = GetForeignServerByName(stmt->servername, false);
+ aclresult = pg_foreign_server_aclcheck(server->serverid, ownerId, ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_FOREIGN_SERVER, server->servername);
+
+ fdw = GetForeignDataWrapper(server->fdwid);
+
+ aclresult = pg_foreign_data_wrapper_aclcheck(fdw->fdwid, ownerId, ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_FDW, fdw->fdwname);
+
+ /*
+ * Insert tuple into pg_foreign_table.
+ */
+ ftrel = heap_open(ForeignTableRelationId, RowExclusiveLock);
+
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ values[Anum_pg_foreign_table_ftrelid - 1] = ObjectIdGetDatum(relid);
+ values[Anum_pg_foreign_table_ftserver - 1] = ObjectIdGetDatum(server->serverid);
+ /* Add table generic options */
+ ftoptions = transformGenericOptions(ForeignTableRelationId,
+ PointerGetDatum(NULL),
+ stmt->options,
+ fdw->fdwvalidator);
+
+ if (PointerIsValid(DatumGetPointer(ftoptions)))
+ values[Anum_pg_foreign_table_ftoptions - 1] = ftoptions;
+ else
+ nulls[Anum_pg_foreign_table_ftoptions - 1] = true;
+
+ tuple = heap_form_tuple(ftrel->rd_att, values, nulls);
+
+ /* pg_foreign_table don't have OID */
+ ftId = simple_heap_insert(ftrel, tuple);
+
+ CatalogUpdateIndexes(ftrel, tuple);
+
+ heap_freetuple(tuple);
+
+ /* Add pg_class dependency on the server */
+ myself.classId = RelationRelationId;
+ myself.objectId = relid;
+ myself.objectSubId = 0;
+
+ referenced.classId = ForeignServerRelationId;
+ referenced.objectId = server->serverid;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ heap_close(ftrel, NoLock);
+}
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 670b9e3e51c..b927e76abd2 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -103,6 +103,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
case OBJECT_SEQUENCE:
case OBJECT_TABLE:
case OBJECT_VIEW:
+ case OBJECT_FOREIGN_TABLE:
if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
RelationGetRelationName(relation));
@@ -365,10 +366,11 @@ CheckAttributeSecLabel(Relation relation)
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_VIEW &&
- relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE)
+ relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("\"%s\" is not a table, view, or composite type",
+ errmsg("\"%s\" is not a table, view, composite type, or foreign table",
RelationGetRelationName(relation))));
}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 576dfb6b0d1..f3bd565b986 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
#include "catalog/objectaccess.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_depend.h"
+#include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_namespace.h"
@@ -48,6 +49,7 @@
#include "commands/trigger.h"
#include "commands/typecmds.h"
#include "executor/executor.h"
+#include "foreign/foreign.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
@@ -219,9 +221,21 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
gettext_noop("type \"%s\" does not exist, skipping"),
gettext_noop("\"%s\" is not a type"),
gettext_noop("Use DROP TYPE to remove a type.")},
+ {RELKIND_FOREIGN_TABLE,
+ ERRCODE_UNDEFINED_OBJECT,
+ gettext_noop("foreign table \"%s\" does not exist"),
+ gettext_noop("foreign table \"%s\" does not exist, skipping"),
+ gettext_noop("\"%s\" is not a foreign table"),
+ gettext_noop("Use DROP FOREIGN TABLE to remove a foreign table.")},
{'\0', 0, NULL, NULL, NULL, NULL}
};
+/* Alter table target-type flags for ATSimplePermissions */
+#define ATT_TABLE 0x0001
+#define ATT_VIEW 0x0002
+#define ATT_INDEX 0x0004
+#define ATT_COMPOSITE_TYPE 0x0008
+#define ATT_FOREIGN_TABLE 0x0010
static void truncate_check_rel(Relation rel);
static List *MergeAttributes(List *schema, List *supers, char relpersistence,
@@ -264,8 +278,8 @@ static void ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
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 ATSimplePermissions(Relation rel, bool allowView, bool allowType);
-static void ATSimplePermissionsRelationOrIndex(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 ATOneLevelRecursion(List **wqueue, Relation rel,
@@ -338,6 +352,8 @@ static void ATExecEnableDisableRule(Relation rel, char *rulename,
static void ATPrepAddInherit(Relation child_rel);
static void ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode);
static void ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode);
+static void ATExecGenericOptions(Relation rel, List *options);
+
static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
ForkNumber forkNum, char relpersistence);
static const char *storage_name(char c);
@@ -396,6 +412,10 @@ 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 on foreign tables are not supported")));
/*
* Security check: disallow creating temp tables from security-restricted
@@ -519,6 +539,11 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
{
RawColumnDefault *rawEnt;
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("default values on foreign tables are not supported")));
+
Assert(colDef->cooked_default == NULL);
rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
@@ -675,7 +700,8 @@ DropErrorMsgWrongType(const char *relname, char wrongkind, char rightkind)
/*
* RemoveRelations
- * Implements DROP TABLE, DROP INDEX, DROP SEQUENCE, DROP VIEW
+ * Implements DROP TABLE, DROP INDEX, DROP SEQUENCE, DROP VIEW,
+ * DROP FOREIGN TABLE
*/
void
RemoveRelations(DropStmt *drop)
@@ -709,6 +735,10 @@ RemoveRelations(DropStmt *drop)
relkind = RELKIND_VIEW;
break;
+ case OBJECT_FOREIGN_TABLE:
+ relkind = RELKIND_FOREIGN_TABLE;
+ break;
+
default:
elog(ERROR, "unrecognized drop object type: %d",
(int) drop->removeType);
@@ -1991,10 +2021,11 @@ renameatt_internal(Oid myrelid,
if (relkind != RELKIND_RELATION &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
- relkind != RELKIND_INDEX)
+ relkind != RELKIND_INDEX &&
+ relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("\"%s\" is not a table, view, composite type or index",
+ errmsg("\"%s\" is not a table, view, composite type, index or foreign table",
RelationGetRelationName(targetrelation))));
/*
@@ -2154,7 +2185,7 @@ renameatt(Oid myrelid, RenameStmt *stmt)
/*
- * Execute ALTER TABLE/INDEX/SEQUENCE/VIEW RENAME
+ * Execute ALTER TABLE/INDEX/SEQUENCE/VIEW/FOREIGN TABLE RENAME
*
* Caller has already done permissions checks.
*/
@@ -2176,7 +2207,9 @@ RenameRelation(Oid myrelid, const char *newrelname, ObjectType reltype)
/*
* For compatibility with prior releases, we don't complain if ALTER TABLE
- * or ALTER INDEX is used to rename a sequence or view.
+ * or ALTER INDEX is used to rename some other type of relation. But
+ * ALTER SEQUENCE/VIEW/FOREIGN TABLE are only to be used with relations of
+ * that type.
*/
if (reltype == OBJECT_SEQUENCE && relkind != RELKIND_SEQUENCE)
ereport(ERROR,
@@ -2190,6 +2223,13 @@ RenameRelation(Oid myrelid, const char *newrelname, ObjectType reltype)
errmsg("\"%s\" is not a view",
RelationGetRelationName(targetrelation))));
+ if (reltype == OBJECT_FOREIGN_TABLE && relkind != RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is not a foreign table",
+ RelationGetRelationName(targetrelation)),
+ errhint("Use ALTER FOREIGN TABLE instead.")));
+
/*
* Don't allow ALTER TABLE on composite types. We want people to use ALTER
* TYPE for that.
@@ -2402,7 +2442,8 @@ AlterTable(AlterTableStmt *stmt)
* For mostly-historical reasons, we allow ALTER TABLE to apply to
* almost all relation types.
*/
- if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
+ if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE
+ || rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -2441,6 +2482,14 @@ AlterTable(AlterTableStmt *stmt)
RelationGetRelationName(rel))));
break;
+ case OBJECT_FOREIGN_TABLE:
+ if (rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is not a foreign table",
+ RelationGetRelationName(rel))));
+ break;
+
default:
elog(ERROR, "unrecognized object type: %d", (int) stmt->relkind);
}
@@ -2526,6 +2575,7 @@ AlterTableGetLockLevel(List *cmds)
case AT_SetTableSpace: /* must rewrite heap */
case AT_DropNotNull: /* may change some SQL plans */
case AT_SetNotNull:
+ case AT_GenericOptions:
cmd_lockmode = AccessExclusiveLock;
break;
@@ -2684,14 +2734,15 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
switch (cmd->subtype)
{
case AT_AddColumn: /* ADD COLUMN */
- ATSimplePermissions(rel, false, true);
+ ATSimplePermissions(rel,
+ ATT_TABLE|ATT_COMPOSITE_TYPE|ATT_FOREIGN_TABLE);
/* Performs own recursion */
ATPrepAddColumn(wqueue, rel, recurse, recursing, cmd, lockmode);
pass = AT_PASS_ADD_COL;
break;
case AT_AddColumnToView: /* add column via CREATE OR REPLACE
* VIEW */
- ATSimplePermissions(rel, true, false);
+ ATSimplePermissions(rel, ATT_VIEW);
/* Performs own recursion */
ATPrepAddColumn(wqueue, rel, recurse, recursing, cmd, lockmode);
pass = AT_PASS_ADD_COL;
@@ -2704,19 +2755,19 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
* substitutes default values into INSERTs before it expands
* rules.
*/
- ATSimplePermissions(rel, true, false);
+ ATSimplePermissions(rel, ATT_TABLE|ATT_VIEW);
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, false, false);
+ 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, false, false);
+ ATSimplePermissions(rel, ATT_TABLE|ATT_FOREIGN_TABLE);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_ADD_CONSTR;
@@ -2729,30 +2780,31 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break;
case AT_SetOptions: /* ALTER COLUMN SET ( options ) */
case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */
- ATSimplePermissionsRelationOrIndex(rel);
+ ATSimplePermissions(rel, ATT_TABLE|ATT_INDEX);
/* This command never recurses */
pass = AT_PASS_MISC;
break;
case AT_SetStorage: /* ALTER COLUMN SET STORAGE */
- ATSimplePermissions(rel, false, false);
+ ATSimplePermissions(rel, ATT_TABLE);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
case AT_DropColumn: /* DROP COLUMN */
- ATSimplePermissions(rel, false, true);
+ ATSimplePermissions(rel,
+ ATT_TABLE|ATT_COMPOSITE_TYPE|ATT_FOREIGN_TABLE);
ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd, lockmode);
/* Recursion occurs during execution phase */
pass = AT_PASS_DROP;
break;
case AT_AddIndex: /* ADD INDEX */
- ATSimplePermissions(rel, false, false);
+ ATSimplePermissions(rel, ATT_TABLE);
/* This command never recurses */
/* No command-specific prep needed */
pass = AT_PASS_ADD_INDEX;
break;
case AT_AddConstraint: /* ADD CONSTRAINT */
- ATSimplePermissions(rel, false, false);
+ ATSimplePermissions(rel, ATT_TABLE);
/* Recursion occurs during execution phase */
/* No command-specific prep needed except saving recurse flag */
if (recurse)
@@ -2760,7 +2812,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
pass = AT_PASS_ADD_CONSTR;
break;
case AT_DropConstraint: /* DROP CONSTRAINT */
- ATSimplePermissions(rel, false, false);
+ ATSimplePermissions(rel, ATT_TABLE);
/* Recursion occurs during execution phase */
/* No command-specific prep needed except saving recurse flag */
if (recurse)
@@ -2768,7 +2820,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
pass = AT_PASS_DROP;
break;
case AT_AlterColumnType: /* ALTER COLUMN TYPE */
- ATSimplePermissions(rel, false, true);
+ ATSimplePermissions(rel,
+ ATT_TABLE|ATT_COMPOSITE_TYPE|ATT_FOREIGN_TABLE);
/* Performs own recursion */
ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd, lockmode);
pass = AT_PASS_ALTER_TYPE;
@@ -2780,20 +2833,20 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break;
case AT_ClusterOn: /* CLUSTER ON */
case AT_DropCluster: /* SET WITHOUT CLUSTER */
- ATSimplePermissions(rel, false, false);
+ ATSimplePermissions(rel, ATT_TABLE);
/* These commands never recurse */
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
case AT_AddOids: /* SET WITH OIDS */
- ATSimplePermissions(rel, false, false);
+ ATSimplePermissions(rel, ATT_TABLE);
/* Performs own recursion */
if (!rel->rd_rel->relhasoids || recursing)
ATPrepAddOids(wqueue, rel, recurse, cmd, lockmode);
pass = AT_PASS_ADD_COL;
break;
case AT_DropOids: /* SET WITHOUT OIDS */
- ATSimplePermissions(rel, false, false);
+ ATSimplePermissions(rel, ATT_TABLE);
/* Performs own recursion */
if (rel->rd_rel->relhasoids)
{
@@ -2807,20 +2860,20 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
pass = AT_PASS_DROP;
break;
case AT_SetTableSpace: /* SET TABLESPACE */
- ATSimplePermissionsRelationOrIndex(rel);
+ ATSimplePermissions(rel, ATT_TABLE|ATT_INDEX);
/* This command never recurses */
ATPrepSetTableSpace(tab, rel, cmd->name, lockmode);
pass = AT_PASS_MISC; /* doesn't actually matter */
break;
case AT_SetRelOptions: /* SET (...) */
case AT_ResetRelOptions: /* RESET (...) */
- ATSimplePermissionsRelationOrIndex(rel);
+ ATSimplePermissions(rel, ATT_TABLE|ATT_INDEX);
/* This command never recurses */
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
case AT_AddInherit: /* INHERIT */
- ATSimplePermissions(rel, false, false);
+ ATSimplePermissions(rel, ATT_TABLE);
/* This command never recurses */
ATPrepAddInherit(rel);
pass = AT_PASS_MISC;
@@ -2837,12 +2890,21 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
case AT_EnableAlwaysRule:
case AT_EnableReplicaRule:
case AT_DisableRule:
- case AT_DropInherit: /* NO INHERIT */
- ATSimplePermissions(rel, false, false);
+ ATSimplePermissions(rel, ATT_TABLE);
/* These commands never recurse */
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_DropInherit: /* NO INHERIT */
+ ATSimplePermissions(rel, ATT_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
+ case AT_GenericOptions:
+ ATSimplePermissions(rel, ATT_FOREIGN_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3085,6 +3147,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_DropInherit:
ATExecDropInherit(rel, (RangeVar *) cmd->def, lockmode);
break;
+ case AT_GenericOptions:
+ ATExecGenericOptions(rel, (List *) cmd->def);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3111,6 +3176,10 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
{
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
+ /* Foreign tables have no storage. */
+ if (tab->relkind == RELKIND_FOREIGN_TABLE)
+ continue;
+
/*
* We only need to rewrite the table if at least one column needs to
* be recomputed, or we are adding/removing the OID column.
@@ -3564,33 +3633,36 @@ ATGetQueueEntry(List **wqueue, Relation rel)
* - Ensure that it is not a system table
*/
static void
-ATSimplePermissions(Relation rel, bool allowView, bool allowType)
+ATSimplePermissions(Relation rel, int allowed_targets)
{
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ int actual_target;
+
+ switch (rel->rd_rel->relkind)
{
- if (allowView)
- {
- if (rel->rd_rel->relkind != RELKIND_VIEW)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("\"%s\" is not a table or view",
- RelationGetRelationName(rel))));
- }
- else if (allowType)
- {
- if (rel->rd_rel->relkind != RELKIND_COMPOSITE_TYPE)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("\"%s\" is not a table or composite type",
- RelationGetRelationName(rel))));
- }
- else
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("\"%s\" is not a table",
- RelationGetRelationName(rel))));
+ case RELKIND_RELATION:
+ actual_target = ATT_TABLE;
+ break;
+ case RELKIND_VIEW:
+ actual_target = ATT_VIEW;
+ break;
+ case RELKIND_INDEX:
+ actual_target = ATT_INDEX;
+ break;
+ case RELKIND_COMPOSITE_TYPE:
+ actual_target = ATT_COMPOSITE_TYPE;
+ break;
+ case RELKIND_FOREIGN_TABLE:
+ actual_target = ATT_FOREIGN_TABLE;
+ break;
+ default:
+ actual_target = 0;
+ break;
}
+ /* Wrong target type? */
+ if ((actual_target & allowed_targets) == 0)
+ ATWrongRelkindError(rel, allowed_targets);
+
/* Permissions checks */
if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
@@ -3604,32 +3676,48 @@ ATSimplePermissions(Relation rel, bool allowView, bool allowType)
}
/*
- * ATSimplePermissionsRelationOrIndex
+ * ATWrongRelkindError
*
- * - Ensure that it is a relation or an index
- * - Ensure this user is the owner
- * - Ensure that it is not a system table
+ * Throw an error when a relation has been determined to be of the wrong
+ * type.
*/
static void
-ATSimplePermissionsRelationOrIndex(Relation rel)
+ATWrongRelkindError(Relation rel, int allowed_targets)
{
- if (rel->rd_rel->relkind != RELKIND_RELATION &&
- rel->rd_rel->relkind != RELKIND_INDEX)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("\"%s\" is not a table or index",
- RelationGetRelationName(rel))));
+ char *msg;
- /* Permissions checks */
- if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
- RelationGetRelationName(rel));
+ switch (allowed_targets)
+ {
+ case ATT_TABLE:
+ msg = _("\"%s\" is not a table");
+ break;
+ case ATT_TABLE|ATT_INDEX:
+ msg = _("\"%s\" is not a table or index");
+ break;
+ case ATT_TABLE|ATT_VIEW:
+ msg = _("\"%s\" is not a table or view");
+ break;
+ case ATT_TABLE|ATT_FOREIGN_TABLE:
+ msg = _("\"%s\" is not a table or foreign table");
+ break;
+ case ATT_TABLE|ATT_COMPOSITE_TYPE|ATT_FOREIGN_TABLE:
+ msg = _("\"%s\" is not a table, composite type, or foreign table");
+ break;
+ case ATT_VIEW:
+ msg = _("\"%s\" is not a view");
+ break;
+ case ATT_FOREIGN_TABLE:
+ msg = _("\"%s\" is not a foreign table");
+ break;
+ default:
+ /* shouldn't get here, add all necessary cases above */
+ msg = _("\"%s\" is of the wrong type");
+ break;
+ }
- if (!allowSystemTableMods && IsSystemRelation(rel))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied: \"%s\" is a system catalog",
- RelationGetRelationName(rel))));
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg(msg, RelationGetRelationName(rel))));
}
/*
@@ -4101,6 +4189,11 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
{
RawColumnDefault *rawEnt;
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("default values on foreign tables are not supported")));
+
rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
rawEnt->attnum = attribute.attnum;
rawEnt->raw_default = copyObject(colDef->raw_default);
@@ -4137,12 +4230,14 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
* returned by AddRelationNewConstraints, so that the right thing happens
* when a datatype's default applies.
*
- * We skip this step completely for views. For a view, we can only get
- * here from CREATE OR REPLACE VIEW, which historically doesn't set up
- * defaults, not even for domain-typed columns. And in any case we
- * mustn't invoke Phase 3 on a view, since it has no storage.
+ * We skip this step completely for views and foreign tables. For a view,
+ * we can only get here from CREATE OR REPLACE VIEW, which historically
+ * doesn't set up defaults, not even for domain-typed columns. And in any
+ * case we mustn't invoke Phase 3 on a view or foreign table, since they
+ * have no storage.
*/
- if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE && attribute.attnum > 0)
+ if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE
+ && relkind != RELKIND_FOREIGN_TABLE && attribute.attnum > 0)
{
defval = (Expr *) build_column_default(rel, attribute.attnum);
@@ -4692,7 +4787,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, false, true);
+ ATSimplePermissions(rel, ATT_TABLE);
/*
* get the number of the attribute
@@ -4996,7 +5091,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
- ATSimplePermissions(rel, false, false);
+ ATSimplePermissions(rel, ATT_TABLE);
/*
* Call AddRelationNewConstraints to do the work, making sure it works on
@@ -5947,7 +6042,7 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
- ATSimplePermissions(rel, false, false);
+ ATSimplePermissions(rel, ATT_TABLE);
conrel = heap_open(ConstraintRelationId, RowExclusiveLock);
@@ -6252,6 +6347,13 @@ ATPrepAlterColumnType(List **wqueue,
tab->newvals = lappend(tab->newvals, newval);
}
+ else if (tab->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ if (cmd->transform)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("ALTER TYPE USING is not supported on foreign tables")));
+ }
if (tab->relkind == RELKIND_COMPOSITE_TYPE)
{
@@ -6834,6 +6936,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
{
case RELKIND_RELATION:
case RELKIND_VIEW:
+ case RELKIND_FOREIGN_TABLE:
/* ok to change owner */
break;
case RELKIND_INDEX:
@@ -6890,7 +6993,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
default:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("\"%s\" is not a table, view, or sequence",
+ errmsg("\"%s\" is not a table, view, sequence, or foreign tabl, or foreign tablee",
NameStr(tuple_class->relname))));
}
@@ -7550,7 +7653,7 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
* Must be owner of both parent and child -- child was checked by
* ATSimplePermissions call in ATPrepCmd
*/
- ATSimplePermissions(parent_rel, false, false);
+ ATSimplePermissions(parent_rel, ATT_TABLE);
/* Permanent rels cannot inherit from temporary ones */
if (RelationUsesTempNamespace(parent_rel)
@@ -8108,6 +8211,76 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
heap_close(parent_rel, NoLock);
}
+/*
+ * ALTER FOREIGN TABLE <name> OPTIONS (...)
+ */
+static void
+ATExecGenericOptions(Relation rel, List *options)
+{
+ Relation ftrel;
+ ForeignServer *server;
+ ForeignDataWrapper *fdw;
+ HeapTuple tuple;
+ bool isnull;
+ Datum repl_val[Natts_pg_foreign_table];
+ bool repl_null[Natts_pg_foreign_table];
+ bool repl_repl[Natts_pg_foreign_table];
+ Datum datum;
+ Form_pg_foreign_table tableform;
+
+ if (options == NIL)
+ return;
+
+ ftrel = heap_open(ForeignTableRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(FOREIGNTABLEREL, rel->rd_id);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("foreign table \"%s\" does not exist",
+ RelationGetRelationName(rel))));
+ tableform = (Form_pg_foreign_table) GETSTRUCT(tuple);
+ server = GetForeignServer(tableform->ftserver);
+ fdw = GetForeignDataWrapper(server->fdwid);
+
+ memset(repl_val, 0, sizeof(repl_val));
+ memset(repl_null, false, sizeof(repl_null));
+ memset(repl_repl, false, sizeof(repl_repl));
+
+ /* Extract the current options */
+ datum = SysCacheGetAttr(FOREIGNTABLEREL,
+ tuple,
+ Anum_pg_foreign_table_ftoptions,
+ &isnull);
+ if (isnull)
+ datum = PointerGetDatum(NULL);
+
+ /* Transform the options */
+ datum = transformGenericOptions(ForeignTableRelationId,
+ datum,
+ options,
+ fdw->fdwvalidator);
+
+ if (PointerIsValid(DatumGetPointer(datum)))
+ repl_val[Anum_pg_foreign_table_ftoptions - 1] = datum;
+ else
+ repl_null[Anum_pg_foreign_table_ftoptions - 1] = true;
+
+ repl_repl[Anum_pg_foreign_table_ftoptions - 1] = true;
+
+ /* Everything looks good - update the tuple */
+
+ tuple = heap_modify_tuple(tuple, RelationGetDescr(ftrel),
+ repl_val, repl_null, repl_repl);
+
+ simple_heap_update(ftrel, &tuple->t_self, tuple);
+ CatalogUpdateIndexes(ftrel, tuple);
+
+ heap_close(ftrel, RowExclusiveLock);
+
+ heap_freetuple(tuple);
+}
+
/*
* Execute ALTER TABLE SET SCHEMA
@@ -8156,6 +8329,14 @@ AlterTableNamespace(RangeVar *relation, const char *newschema,
RelationGetRelationName(rel))));
break;
+ case OBJECT_FOREIGN_TABLE:
+ if (rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is not a foreign table",
+ RelationGetRelationName(rel))));
+ break;
+
default:
elog(ERROR, "unrecognized object type: %d", (int) stmttype);
}
@@ -8165,6 +8346,7 @@ AlterTableNamespace(RangeVar *relation, const char *newschema,
{
case RELKIND_RELATION:
case RELKIND_VIEW:
+ case RELKIND_FOREIGN_TABLE:
/* ok to change schema */
break;
case RELKIND_SEQUENCE:
@@ -8195,7 +8377,7 @@ AlterTableNamespace(RangeVar *relation, const char *newschema,
default:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("\"%s\" is not a table, view, or sequence",
+ errmsg("\"%s\" is not a table, view, sequence, or foreign table",
RelationGetRelationName(rel))));
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 410ef5ded72..82d9775b240 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -894,7 +894,7 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast, bool for_wraparound,
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
ereport(WARNING,
- (errmsg("skipping \"%s\" --- cannot vacuum indexes, views, or special system tables",
+ (errmsg("skipping \"%s\" --- cannot only non-tables or special system tables",
RelationGetRelationName(onerel))));
relation_close(onerel, lmode);
PopActiveSnapshot();
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 3eaef2a6c35..17c7ea294dc 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -934,6 +934,12 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
break;
}
break;
+ case RELKIND_FOREIGN_TABLE:
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change foreign table \"%s\"",
+ RelationGetRelationName(resultRelationDesc))));
+ break;
default:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index a968e375a6c..804f900c7eb 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2575,11 +2575,15 @@ _copyCopyStmt(CopyStmt *from)
return newnode;
}
-static CreateStmt *
-_copyCreateStmt(CreateStmt *from)
+/*
+ * CopyCreateStmtFields
+ *
+ * This function copies the fields of the CreateStmt node. It is used by
+ * copy functions for classes which inherit from CreateStmt.
+ */
+static void
+CopyCreateStmtFields(CreateStmt *from, CreateStmt *newnode)
{
- CreateStmt *newnode = makeNode(CreateStmt);
-
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
@@ -2589,6 +2593,14 @@ _copyCreateStmt(CreateStmt *from)
COPY_SCALAR_FIELD(oncommit);
COPY_STRING_FIELD(tablespacename);
COPY_SCALAR_FIELD(if_not_exists);
+}
+
+static CreateStmt *
+_copyCreateStmt(CreateStmt *from)
+{
+ CreateStmt *newnode = makeNode(CreateStmt);
+
+ CopyCreateStmtFields(from, newnode);
return newnode;
}
@@ -3297,6 +3309,19 @@ _copyDropUserMappingStmt(DropUserMappingStmt *from)
return newnode;
}
+static CreateForeignTableStmt *
+_copyCreateForeignTableStmt(CreateForeignTableStmt *from)
+{
+ CreateForeignTableStmt *newnode = makeNode(CreateForeignTableStmt);
+
+ CopyCreateStmtFields((CreateStmt *) from, (CreateStmt *) newnode);
+
+ COPY_STRING_FIELD(servername);
+ COPY_NODE_FIELD(options);
+
+ return newnode;
+}
+
static CreateTrigStmt *
_copyCreateTrigStmt(CreateTrigStmt *from)
{
@@ -4198,6 +4223,9 @@ copyObject(void *from)
case T_DropUserMappingStmt:
retval = _copyDropUserMappingStmt(from);
break;
+ case T_CreateForeignTableStmt:
+ retval = _copyCreateForeignTableStmt(from);
+ break;
case T_CreateTrigStmt:
retval = _copyCreateTrigStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 9e7c9481bd7..2ef1a33bb7b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1719,6 +1719,18 @@ _equalDropUserMappingStmt(DropUserMappingStmt *a, DropUserMappingStmt *b)
}
static bool
+_equalCreateForeignTableStmt(CreateForeignTableStmt *a, CreateForeignTableStmt *b)
+{
+ if (!_equalCreateStmt(&a->base, &b->base))
+ return false;
+
+ COMPARE_STRING_FIELD(servername);
+ COMPARE_NODE_FIELD(options);
+
+ return true;
+}
+
+static bool
_equalCreateTrigStmt(CreateTrigStmt *a, CreateTrigStmt *b)
{
COMPARE_STRING_FIELD(trigname);
@@ -2821,6 +2833,9 @@ equal(void *a, void *b)
case T_DropUserMappingStmt:
retval = _equalDropUserMappingStmt(a, b);
break;
+ case T_CreateForeignTableStmt:
+ retval = _equalCreateForeignTableStmt(a, b);
+ break;
case T_CreateTrigStmt:
retval = _equalCreateTrigStmt(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 88256636662..c85fc2d9d48 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1857,6 +1857,17 @@ _outCreateStmt(StringInfo str, CreateStmt *node)
}
static void
+_outCreateForeignTableStmt(StringInfo str, CreateForeignTableStmt *node)
+{
+ WRITE_NODE_TYPE("CREATEFOREIGNTABLESTMT");
+
+ _outCreateStmt(str, (CreateStmt *) &node->base);
+
+ WRITE_STRING_FIELD(servername);
+ WRITE_NODE_FIELD(options);
+}
+
+static void
_outIndexStmt(StringInfo str, IndexStmt *node)
{
WRITE_NODE_TYPE("INDEXSTMT");
@@ -2881,6 +2892,9 @@ _outNode(StringInfo str, void *obj)
case T_CreateStmt:
_outCreateStmt(str, obj);
break;
+ case T_CreateForeignTableStmt:
+ _outCreateForeignTableStmt(str, obj);
+ break;
case T_IndexStmt:
_outIndexStmt(str, obj);
break;
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index f0504e1397f..adbe45caec4 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -90,6 +90,12 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
*/
relation = heap_open(relationObjectId, NoLock);
+ /* Foreign table scans are not implemented yet. */
+ if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign table scans are not yet supported")));
+
rel->min_attr = FirstLowInvalidHeapAttributeNumber + 1;
rel->max_attr = RelationGetNumberOfAttributes(relation);
rel->reltablespace = RelationGetForm(relation)->reltablespace;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 8c89d5c5f74..43e8fdbd724 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -185,6 +185,7 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
+ AlterForeignTableStmt
AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
AlterRoleStmt AlterRoleSetStmt
AlterDefaultPrivilegesStmt DefACLAction
@@ -193,7 +194,8 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
CreateDomainStmt CreateGroupStmt CreateOpClassStmt
CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt
CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
- CreateFdwStmt CreateForeignServerStmt CreateAssertStmt CreateTrigStmt
+ CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
+ CreateAssertStmt CreateTrigStmt
CreateUserStmt CreateUserMappingStmt CreateRoleStmt
CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt
@@ -279,6 +281,7 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
%type <list> stmtblock stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
+ OptForeignTableElementList ForeignTableElementList
reloptions opt_reloptions
OptWith opt_distinct opt_definition func_args func_args_list
func_args_with_defaults func_args_with_defaults_list
@@ -351,6 +354,7 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
%type <vsetstmt> set_rest SetResetClause
%type <node> TableElement TypedTableElement ConstraintElem TableFuncElement
+ ForeignTableElement
%type <node> columnDef columnOptions
%type <defelt> def_elem reloption_elem old_aggr_elem
%type <node> def_arg columnElem where_clause where_or_current_clause
@@ -658,6 +662,7 @@ stmt :
| AlterEnumStmt
| AlterFdwStmt
| AlterForeignServerStmt
+ | AlterForeignTableStmt
| AlterFunctionStmt
| AlterGroupStmt
| AlterObjectSchemaStmt
@@ -686,6 +691,7 @@ stmt :
| CreateDomainStmt
| CreateFdwStmt
| CreateForeignServerStmt
+ | CreateForeignTableStmt
| CreateFunctionStmt
| CreateGroupStmt
| CreateOpClassStmt
@@ -1935,6 +1941,13 @@ alter_table_cmd:
n->def = (Node *)$2;
$$ = (Node *)n;
}
+ | alter_generic_options
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_GenericOptions;
+ n->def = (Node *)$1;
+ $$ = (Node *) n;
+ }
;
alter_column_default:
@@ -3383,6 +3396,84 @@ AlterForeignServerStmt: ALTER SERVER name foreign_server_version alter_generic_o
/*****************************************************************************
*
* QUERY:
+ * CREATE FOREIGN TABLE relname (...) SERVER name (...)
+ *
+ *****************************************************************************/
+
+CreateForeignTableStmt:
+ CREATE FOREIGN TABLE qualified_name
+ OptForeignTableElementList
+ SERVER name create_generic_options
+ {
+ CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
+ $4->relpersistence = RELPERSISTENCE_PERMANENT;
+ n->base.relation = $4;
+ n->base.tableElts = $5;
+ n->base.inhRelations = NIL;
+ n->base.if_not_exists = false;
+ /* FDW-specific data */
+ n->servername = $7;
+ n->options = $8;
+ $$ = (Node *) n;
+ }
+ | CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
+ OptForeignTableElementList
+ SERVER name create_generic_options
+ {
+ CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
+ $7->relpersistence = RELPERSISTENCE_PERMANENT;
+ n->base.relation = $7;
+ n->base.tableElts = $8;
+ n->base.inhRelations = NIL;
+ n->base.if_not_exists = true;
+ /* FDW-specific data */
+ n->servername = $10;
+ n->options = $11;
+ $$ = (Node *) n;
+ }
+ ;
+
+OptForeignTableElementList:
+ '(' ForeignTableElementList ')' { $$ = $2; }
+ | '(' ')' { $$ = NIL; }
+ ;
+
+ForeignTableElementList:
+ ForeignTableElement
+ {
+ $$ = list_make1($1);
+ }
+ | ForeignTableElementList ',' ForeignTableElement
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
+ForeignTableElement:
+ columnDef { $$ = $1; }
+ ;
+
+/*****************************************************************************
+ *
+ * QUERY:
+ * ALTER FOREIGN TABLE relname [...]
+ *
+ *****************************************************************************/
+
+AlterForeignTableStmt:
+ ALTER FOREIGN TABLE relation_expr alter_table_cmds
+ {
+ AlterTableStmt *n = makeNode(AlterTableStmt);
+ n->relation = $4;
+ n->cmds = $5;
+ n->relkind = OBJECT_FOREIGN_TABLE;
+ $$ = (Node *)n;
+ }
+ ;
+
+/*****************************************************************************
+ *
+ * QUERY:
* CREATE USER MAPPING FOR auth_ident SERVER name [OPTIONS]
*
*****************************************************************************/
@@ -4189,6 +4280,7 @@ drop_type: TABLE { $$ = OBJECT_TABLE; }
| VIEW { $$ = OBJECT_VIEW; }
| INDEX { $$ = OBJECT_INDEX; }
| TYPE_P { $$ = OBJECT_TYPE; }
+ | FOREIGN TABLE { $$ = OBJECT_FOREIGN_TABLE; }
| DOMAIN_P { $$ = OBJECT_DOMAIN; }
| CONVERSION_P { $$ = OBJECT_CONVERSION; }
| SCHEMA { $$ = OBJECT_SCHEMA; }
@@ -4247,8 +4339,8 @@ opt_restart_seqs:
* CONVERSION | LANGUAGE | OPERATOR CLASS | LARGE OBJECT |
* CAST | COLUMN | SCHEMA | TABLESPACE | ROLE |
* TEXT SEARCH PARSER | TEXT SEARCH DICTIONARY |
- * TEXT SEARCH TEMPLATE |
- * TEXT SEARCH CONFIGURATION ] <objname> |
+ * TEXT SEARCH TEMPLATE | TEXT SEARCH CONFIGURATION |
+ * FOREIGN TABLE ] <objname> |
* AGGREGATE <aggname> (arg1, ...) |
* FUNCTION <funcname> (arg1, arg2, ...) |
* OPERATOR <op> (leftoperand_typ, rightoperand_typ) |
@@ -4425,6 +4517,7 @@ comment_type:
| CONVERSION_P { $$ = OBJECT_CONVERSION; }
| TABLESPACE { $$ = OBJECT_TABLESPACE; }
| ROLE { $$ = OBJECT_ROLE; }
+ | FOREIGN TABLE { $$ = OBJECT_FOREIGN_TABLE; }
;
comment_text:
@@ -4506,6 +4599,7 @@ opt_provider: FOR ColId_or_Sconst { $$ = $2; }
security_label_type:
COLUMN { $$ = OBJECT_COLUMN; }
+ | FOREIGN TABLE { $$ = OBJECT_FOREIGN_TABLE; }
| SCHEMA { $$ = OBJECT_SCHEMA; }
| SEQUENCE { $$ = OBJECT_SEQUENCE; }
| TABLE { $$ = OBJECT_TABLE; }
@@ -4841,6 +4935,14 @@ privilege_target:
n->objs = $3;
$$ = n;
}
+ | FOREIGN TABLE qualified_name_list
+ {
+ PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
+ n->targtype = ACL_TARGET_OBJECT;
+ n->objtype = ACL_OBJECT_FOREIGN_TABLE;
+ n->objs = $3;
+ $$ = n;
+ }
| FUNCTION function_with_argtypes_list
{
PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
@@ -5927,15 +6029,35 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
n->newname = $6;
$$ = (Node *)n;
}
+ | ALTER FOREIGN TABLE relation_expr RENAME TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_FOREIGN_TABLE;
+ n->relation = $4;
+ n->subname = NULL;
+ n->newname = $7;
+ $$ = (Node *)n;
+ }
| ALTER TABLE relation_expr RENAME opt_column name TO name
{
RenameStmt *n = makeNode(RenameStmt);
n->renameType = OBJECT_COLUMN;
+ n->relationType = OBJECT_TABLE;
n->relation = $3;
n->subname = $6;
n->newname = $8;
$$ = (Node *)n;
}
+ | ALTER FOREIGN TABLE relation_expr RENAME opt_column name TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_COLUMN;
+ n->relationType = OBJECT_FOREIGN_TABLE;
+ n->relation = $4;
+ n->subname = $7;
+ n->newname = $9;
+ $$ = (Node *)n;
+ }
| ALTER TRIGGER name ON qualified_name RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
@@ -6031,6 +6153,7 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
n->renameType = OBJECT_ATTRIBUTE;
+ n->relationType = OBJECT_TYPE;
n->relation = makeRangeVarFromAnyName($3, @3, yyscanner);
n->subname = $6;
n->newname = $8;
@@ -6171,6 +6294,14 @@ AlterObjectSchemaStmt:
n->newschema = $6;
$$ = (Node *)n;
}
+ | ALTER FOREIGN TABLE relation_expr SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_FOREIGN_TABLE;
+ n->relation = $4;
+ n->newschema = $7;
+ $$ = (Node *)n;
+ }
| ALTER TYPE_P any_name SET SCHEMA name
{
AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index d75c706816b..23c60eec318 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -65,7 +65,7 @@
/* State shared by transformCreateStmt and its subroutines */
typedef struct
{
- const char *stmtType; /* "CREATE TABLE" or "ALTER TABLE" */
+ const char *stmtType; /* "CREATE [FOREIGN] TABLE" or "ALTER TABLE" */
RangeVar *relation; /* relation to create */
Relation rel; /* opened/locked rel, if ALTER */
List *inhRelations; /* relations to inherit from */
@@ -173,7 +173,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
- cxt.stmtType = "CREATE TABLE";
+ if (IsA(stmt, CreateForeignTableStmt))
+ cxt.stmtType = "CREATE FOREIGN TABLE";
+ else
+ cxt.stmtType = "CREATE TABLE";
cxt.relation = stmt->relation;
cxt.rel = NULL;
cxt.inhRelations = stmt->inhRelations;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f4e41a17652..95000377704 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -220,6 +220,7 @@ check_xact_readonly(Node *parsetree)
case T_AlterUserMappingStmt:
case T_DropUserMappingStmt:
case T_AlterTableSpaceOptionsStmt:
+ case T_CreateForeignTableStmt:
case T_SecLabelStmt:
PreventCommandIfReadOnly(CreateCommandTag(parsetree));
break;
@@ -492,6 +493,7 @@ standard_ProcessUtility(Node *parsetree,
break;
case T_CreateStmt:
+ case T_CreateForeignTableStmt:
{
List *stmts;
ListCell *l;
@@ -540,6 +542,22 @@ standard_ProcessUtility(Node *parsetree,
AlterTableCreateToastTable(relOid, toast_options);
}
+ else if (IsA(stmt, CreateForeignTableStmt))
+ {
+ /* Create the table itself */
+ relOid = DefineRelation((CreateStmt *) stmt,
+ RELKIND_FOREIGN_TABLE,
+ InvalidOid);
+
+ /*
+ * Unless "IF NOT EXISTS" was specified and the
+ * relation already exists, create the pg_foreign_table
+ * entry.
+ */
+ if (relOid != InvalidOid)
+ CreateForeignTable((CreateForeignTableStmt *) stmt,
+ relOid);
+ }
else
{
/* Recurse for anything else */
@@ -618,6 +636,7 @@ standard_ProcessUtility(Node *parsetree,
case OBJECT_SEQUENCE:
case OBJECT_VIEW:
case OBJECT_INDEX:
+ case OBJECT_FOREIGN_TABLE:
RemoveRelations(stmt);
break;
@@ -1557,6 +1576,10 @@ CreateCommandTag(Node *parsetree)
tag = "DROP USER MAPPING";
break;
+ case T_CreateForeignTableStmt:
+ tag = "CREATE FOREIGN TABLE";
+ break;
+
case T_DropStmt:
switch (((DropStmt *) parsetree)->removeType)
{
@@ -1596,6 +1619,9 @@ CreateCommandTag(Node *parsetree)
case OBJECT_TSCONFIGURATION:
tag = "DROP TEXT SEARCH CONFIGURATION";
break;
+ case OBJECT_FOREIGN_TABLE:
+ tag = "DROP FOREIGN TABLE";
+ break;
default:
tag = "???";
}
@@ -1654,6 +1680,14 @@ CreateCommandTag(Node *parsetree)
tag = "ALTER SEQUENCE";
break;
case OBJECT_COLUMN:
+ {
+ RenameStmt *stmt = (RenameStmt *) parsetree;
+ if (stmt->relationType == OBJECT_FOREIGN_TABLE)
+ tag = "ALTER FOREIGN TABLE";
+ else
+ tag = "ALTER TABLE";
+ }
+ break;
case OBJECT_TABLE:
tag = "ALTER TABLE";
break;
@@ -1666,6 +1700,9 @@ CreateCommandTag(Node *parsetree)
case OBJECT_VIEW:
tag = "ALTER VIEW";
break;
+ case OBJECT_FOREIGN_TABLE:
+ tag = "ALTER FOREIGN TABLE";
+ break;
case OBJECT_TSPARSER:
tag = "ALTER TEXT SEARCH PARSER";
break;
@@ -1736,6 +1773,9 @@ CreateCommandTag(Node *parsetree)
case OBJECT_VIEW:
tag = "ALTER VIEW";
break;
+ case OBJECT_FOREIGN_TABLE:
+ tag = "ALTER FOREIGN TABLE";
+ break;
default:
tag = "???";
break;
@@ -1796,6 +1836,9 @@ CreateCommandTag(Node *parsetree)
case OBJECT_FOREIGN_SERVER:
tag = "ALTER SERVER";
break;
+ case OBJECT_FOREIGN_TABLE:
+ tag = "ALTER FOREIGN TABLE";
+ break;
default:
tag = "???";
break;
@@ -1820,6 +1863,9 @@ CreateCommandTag(Node *parsetree)
case OBJECT_VIEW:
tag = "ALTER VIEW";
break;
+ case OBJECT_FOREIGN_TABLE:
+ tag = "ALTER FOREIGN TABLE";
+ break;
default:
tag = "???";
break;
@@ -2316,6 +2362,7 @@ GetCommandLogLevel(Node *parsetree)
break;
case T_CreateStmt:
+ case T_CreateForeignTableStmt:
lev = LOGSTMT_DDL;
break;
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 05c9c266ffa..691ba3bd95a 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -782,6 +782,10 @@ acldefault(GrantObjectType objtype, Oid ownerId)
world_default = ACL_NO_RIGHTS;
owner_default = ACL_ALL_RIGHTS_FOREIGN_SERVER;
break;
+ case ACL_OBJECT_FOREIGN_TABLE:
+ world_default = ACL_NO_RIGHTS;
+ owner_default = ACL_ALL_RIGHTS_FOREIGN_TABLE;
+ break;
default:
elog(ERROR, "unrecognized objtype: %d", (int) objtype);
world_default = ACL_NO_RIGHTS; /* keep compiler quiet */
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index cba130b55ee..191953b972c 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -35,6 +35,7 @@
#include "catalog/pg_enum.h"
#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_foreign_server.h"
+#include "catalog/pg_foreign_table.h"
#include "catalog/pg_language.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
@@ -398,6 +399,17 @@ static const struct cachedesc cacheinfo[] = {
},
32
},
+ {ForeignTableRelationId, /* FOREIGNTABLEREL */
+ ForeignTableRelidIndexId,
+ 1,
+ {
+ Anum_pg_foreign_table_ftrelid,
+ 0,
+ 0,
+ 0
+ },
+ 128
+ },
{IndexRelationId, /* INDEXRELID */
IndexRelidIndexId,
1,