diff options
Diffstat (limited to 'src/backend')
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, |