From 890192e99af5db1d15d5bb73f3f1044faa1d2758 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Wed, 11 Mar 2015 17:01:13 -0300 Subject: [PATCH] Support user mappings in get_object_address Since commit 72dd233d3ef we were trying to obtain object addressing information in sql_drop event triggers, but that caused failures when the drops involved user mappings. This addition enables that to work again. Naturally, pg_get_object_address can work with these objects now, too. I toyed with the idea of removing DropUserMappingStmt as a node and using DropStmt instead in the DropUserMappingStmt grammar production, but that didn't go very well: for one thing the messages thrown by the specific code are specialized (you get "server not found" if you specify the wrong server, instead of a generic "user mapping for ... not found" which you'd get it we were to merge this with RemoveObjects --- unless we added even more special cases). For another thing, it would require to pass RoleSpec nodes through the objname/objargs representation used by RemoveObjects, which works in isolation, but gets messy when pg_get_object_address is involved. So I dropped this part for now. Reviewed by Stephen Frost. --- src/backend/catalog/objectaddress.c | 78 +++++++++++++++++++- src/backend/commands/event_trigger.c | 1 + src/include/nodes/parsenodes.h | 1 + src/test/regress/expected/event_trigger.out | 9 ++- src/test/regress/expected/object_address.out | 19 +++-- src/test/regress/sql/event_trigger.sql | 5 +- src/test/regress/sql/object_address.sql | 9 ++- 7 files changed, 109 insertions(+), 13 deletions(-) diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index 541912bac5..67c14020e5 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -520,7 +520,7 @@ ObjectTypeMap[] = /* OCLASS_FOREIGN_SERVER */ { "server", OBJECT_FOREIGN_SERVER }, /* OCLASS_USER_MAPPING */ - { "user mapping", -1 }, /* unmapped */ + { "user mapping", OBJECT_USER_MAPPING }, /* OCLASS_DEFACL */ { "default acl", -1 }, /* unmapped */ /* OCLASS_EXTENSION */ @@ -555,6 +555,8 @@ static ObjectAddress get_object_address_type(ObjectType objtype, List *objname, bool missing_ok); static ObjectAddress get_object_address_opcf(ObjectType objtype, List *objname, List *objargs, bool missing_ok); +static ObjectAddress get_object_address_usermapping(List *objname, + List *objargs, bool missing_ok); static const ObjectPropertyType *get_object_property_data(Oid class_id); static void getRelationDescription(StringInfo buffer, Oid relid); @@ -769,6 +771,10 @@ get_object_address(ObjectType objtype, List *objname, List *objargs, address.objectId = get_ts_config_oid(objname, missing_ok); address.objectSubId = 0; break; + case OBJECT_USER_MAPPING: + address = get_object_address_usermapping(objname, objargs, + missing_ok); + break; default: elog(ERROR, "unrecognized objtype: %d", (int) objtype); /* placate compiler, in case it thinks elog might return */ @@ -1372,6 +1378,75 @@ get_object_address_opcf(ObjectType objtype, return address; } +/* + * Find the ObjectAddress for a user mapping. + */ +static ObjectAddress +get_object_address_usermapping(List *objname, List *objargs, bool missing_ok) +{ + ObjectAddress address; + Oid userid; + char *username; + char *servername; + ForeignServer *server; + HeapTuple tp; + + ObjectAddressSet(address, UserMappingRelationId, InvalidOid); + + /* fetch string names from input lists, for error messages */ + username = strVal(linitial(objname)); + servername = strVal(linitial(objargs)); + + /* look up pg_authid OID of mapped user; InvalidOid if PUBLIC */ + if (strcmp(username, "public") == 0) + userid = InvalidOid; + else + { + tp = SearchSysCache1(AUTHNAME, + CStringGetDatum(username)); + if (!HeapTupleIsValid(tp)) + { + if (!missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("user mapping for user \"%s\" in server \"%s\" does not exist", + username, servername))); + return address; + } + userid = HeapTupleGetOid(tp); + ReleaseSysCache(tp); + } + + /* Now look up the pg_user_mapping tuple */ + server = GetForeignServerByName(servername, true); + if (!server) + { + if (!missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("server \"%s\" does not exist", servername))); + return address; + } + tp = SearchSysCache2(USERMAPPINGUSERSERVER, + ObjectIdGetDatum(userid), + ObjectIdGetDatum(server->serverid)); + if (!HeapTupleIsValid(tp)) + { + if (!missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("user mapping for user \"%s\" in server \"%s\" does not exist", + username, servername))); + return address; + } + + address.objectId = HeapTupleGetOid(tp); + + ReleaseSysCache(tp); + + return address; +} + /* * Convert an array of TEXT into a List of string Values, as emitted by the * parser, which is what get_object_address uses as input. @@ -1523,6 +1598,7 @@ pg_get_object_address(PG_FUNCTION_ARGS) case OBJECT_OPCLASS: case OBJECT_OPFAMILY: case OBJECT_CAST: + case OBJECT_USER_MAPPING: if (list_length(args) != 1) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index f573c9ce70..4e446bd25c 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -1092,6 +1092,7 @@ EventTriggerSupportsObjectType(ObjectType obtype) case OBJECT_TSPARSER: case OBJECT_TSTEMPLATE: case OBJECT_TYPE: + case OBJECT_USER_MAPPING: case OBJECT_VIEW: return true; } diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index c226b039cf..1279aca882 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1268,6 +1268,7 @@ typedef enum ObjectType OBJECT_TSPARSER, OBJECT_TSTEMPLATE, OBJECT_TYPE, + OBJECT_USER_MAPPING, OBJECT_VIEW } ObjectType; diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out index 7f7dd820ff..b87d503436 100644 --- a/src/test/regress/expected/event_trigger.out +++ b/src/test/regress/expected/event_trigger.out @@ -110,6 +110,12 @@ revoke all on table event_trigger_fire1 from public; NOTICE: test_event_trigger: ddl_command_end REVOKE drop table event_trigger_fire1; NOTICE: test_event_trigger: ddl_command_end DROP TABLE +create foreign data wrapper useless; +NOTICE: test_event_trigger: ddl_command_end CREATE FOREIGN DATA WRAPPER +create server useless_server foreign data wrapper useless; +NOTICE: test_event_trigger: ddl_command_end CREATE SERVER +create user mapping for regression_bob server useless_server; +NOTICE: test_event_trigger: ddl_command_end CREATE USER MAPPING -- alter owner to non-superuser should fail alter event trigger regress_event_trigger owner to regression_bob; ERROR: permission denied to change owner of event trigger "regress_event_trigger" @@ -125,10 +131,11 @@ alter event trigger regress_event_trigger rename to regress_event_trigger3; -- should fail, doesn't exist any more drop event trigger regress_event_trigger; ERROR: event trigger "regress_event_trigger" does not exist --- should fail, regression_bob owns regress_event_trigger2/3 +-- should fail, regression_bob owns some objects drop role regression_bob; ERROR: role "regression_bob" cannot be dropped because some objects depend on it DETAIL: owner of event trigger regress_event_trigger3 +owner of user mapping for regression_bob on server useless_server -- cleanup before next test -- these are all OK; the second one should emit a NOTICE drop event trigger if exists regress_event_trigger2; diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out index dcf1b46ecd..e72abda90a 100644 --- a/src/test/regress/expected/object_address.out +++ b/src/test/regress/expected/object_address.out @@ -28,6 +28,8 @@ CREATE DOMAIN addr_nsp.gendomain AS int4 CONSTRAINT domconstr CHECK (value > 0); CREATE FUNCTION addr_nsp.trig() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN END; $$; CREATE TRIGGER t BEFORE INSERT ON addr_nsp.gentable FOR EACH ROW EXECUTE PROCEDURE addr_nsp.trig(); CREATE POLICY genpol ON addr_nsp.gentable; +CREATE SERVER "integer" FOREIGN DATA WRAPPER addr_fdw; +CREATE USER MAPPING FOR regtest_addr_user SERVER "integer"; -- test some error cases SELECT pg_get_object_address('stone', '{}', '{}'); ERROR: unrecognized object type "stone" @@ -42,8 +44,7 @@ DECLARE BEGIN FOR objtype IN VALUES ('toast table'), ('index column'), ('sequence column'), ('toast table column'), ('view column'), ('materialized view column'), - ('operator of access method'), ('function of access method'), - ('user mapping') + ('operator of access method'), ('function of access method') LOOP BEGIN PERFORM pg_get_object_address(objtype, '{one}', '{}'); @@ -61,7 +62,6 @@ WARNING: error for view column: unsupported object type "view column" WARNING: error for materialized view column: unsupported object type "materialized view column" WARNING: error for operator of access method: unsupported object type "operator of access method" WARNING: error for function of access method: unsupported object type "function of access method" -WARNING: error for user mapping: unsupported object type "user mapping" DO $$ DECLARE objtype text; @@ -77,7 +77,7 @@ BEGIN ('operator'), ('operator class'), ('operator family'), ('rule'), ('trigger'), ('text search parser'), ('text search dictionary'), ('text search template'), ('text search configuration'), - ('policy') + ('policy'), ('user mapping') LOOP FOR names IN VALUES ('{eins}'), ('{addr_nsp, zwei}'), ('{eins, zwei, drei}') LOOP @@ -249,6 +249,12 @@ WARNING: error for policy,{addr_nsp,zwei},{}: relation "addr_nsp" does not exis WARNING: error for policy,{addr_nsp,zwei},{integer}: relation "addr_nsp" does not exist WARNING: error for policy,{eins,zwei,drei},{}: schema "eins" does not exist WARNING: error for policy,{eins,zwei,drei},{integer}: schema "eins" does not exist +WARNING: error for user mapping,{eins},{}: argument list length must be exactly 1 +WARNING: error for user mapping,{eins},{integer}: user mapping for user "eins" in server "integer" does not exist +WARNING: error for user mapping,{addr_nsp,zwei},{}: argument list length must be exactly 1 +WARNING: error for user mapping,{addr_nsp,zwei},{integer}: user mapping for user "addr_nsp" in server "integer" does not exist +WARNING: error for user mapping,{eins,zwei,drei},{}: argument list length must be exactly 1 +WARNING: error for user mapping,{eins,zwei,drei},{integer}: user mapping for user "eins" in server "integer" does not exist -- these object types cannot be qualified names SELECT pg_get_object_address('language', '{one}', '{}'); ERROR: language "one" does not exist @@ -334,7 +340,7 @@ WITH objects (type, name, args) AS (VALUES -- tablespace ('foreign-data wrapper', '{addr_fdw}', '{}'), ('server', '{addr_fserv}', '{}'), - -- user mapping + ('user mapping', '{regtest_addr_user}', '{integer}'), -- extension -- event trigger ('policy', '{addr_nsp, gentable, genpol}', '{}') @@ -365,6 +371,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.subobjid)).*, foreign table | addr_nsp | genftable | addr_nsp.genftable | t role | | regtest_addr_user | regtest_addr_user | t server | | addr_fserv | addr_fserv | t + user mapping | | | regtest_addr_user on server integer | t foreign-data wrapper | | addr_fdw | addr_fdw | t default value | | | for addr_nsp.gentable.b | t cast | | | (bigint AS integer) | t @@ -384,7 +391,7 @@ SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.subobjid)).*, text search parser | addr_nsp | addr_ts_prs | addr_nsp.addr_ts_prs | t text search configuration | addr_nsp | addr_ts_conf | addr_nsp.addr_ts_conf | t text search template | addr_nsp | addr_ts_temp | addr_nsp.addr_ts_temp | t -(35 rows) +(36 rows) --- --- Cleanup resources diff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql index bfe0433a57..bcfeb3a869 100644 --- a/src/test/regress/sql/event_trigger.sql +++ b/src/test/regress/sql/event_trigger.sql @@ -107,6 +107,9 @@ grant all on table event_trigger_fire1 to public; comment on table event_trigger_fire1 is 'here is a comment'; revoke all on table event_trigger_fire1 from public; drop table event_trigger_fire1; +create foreign data wrapper useless; +create server useless_server foreign data wrapper useless; +create user mapping for regression_bob server useless_server; -- alter owner to non-superuser should fail alter event trigger regress_event_trigger owner to regression_bob; @@ -124,7 +127,7 @@ alter event trigger regress_event_trigger rename to regress_event_trigger3; -- should fail, doesn't exist any more drop event trigger regress_event_trigger; --- should fail, regression_bob owns regress_event_trigger2/3 +-- should fail, regression_bob owns some objects drop role regression_bob; -- cleanup before next test diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql index 9fc27d8f6e..b714b529c8 100644 --- a/src/test/regress/sql/object_address.sql +++ b/src/test/regress/sql/object_address.sql @@ -32,6 +32,8 @@ CREATE DOMAIN addr_nsp.gendomain AS int4 CONSTRAINT domconstr CHECK (value > 0); CREATE FUNCTION addr_nsp.trig() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN END; $$; CREATE TRIGGER t BEFORE INSERT ON addr_nsp.gentable FOR EACH ROW EXECUTE PROCEDURE addr_nsp.trig(); CREATE POLICY genpol ON addr_nsp.gentable; +CREATE SERVER "integer" FOREIGN DATA WRAPPER addr_fdw; +CREATE USER MAPPING FOR regtest_addr_user SERVER "integer"; -- test some error cases SELECT pg_get_object_address('stone', '{}', '{}'); @@ -45,8 +47,7 @@ DECLARE BEGIN FOR objtype IN VALUES ('toast table'), ('index column'), ('sequence column'), ('toast table column'), ('view column'), ('materialized view column'), - ('operator of access method'), ('function of access method'), - ('user mapping') + ('operator of access method'), ('function of access method') LOOP BEGIN PERFORM pg_get_object_address(objtype, '{one}', '{}'); @@ -72,7 +73,7 @@ BEGIN ('operator'), ('operator class'), ('operator family'), ('rule'), ('trigger'), ('text search parser'), ('text search dictionary'), ('text search template'), ('text search configuration'), - ('policy') + ('policy'), ('user mapping') LOOP FOR names IN VALUES ('{eins}'), ('{addr_nsp, zwei}'), ('{eins, zwei, drei}') LOOP @@ -154,7 +155,7 @@ WITH objects (type, name, args) AS (VALUES -- tablespace ('foreign-data wrapper', '{addr_fdw}', '{}'), ('server', '{addr_fserv}', '{}'), - -- user mapping + ('user mapping', '{regtest_addr_user}', '{integer}'), -- extension -- event trigger ('policy', '{addr_nsp, gentable, genpol}', '{}')