/*------------------------------------------------------------------------- * * foreigncmds.c * foreign-data wrapper/server creation/manipulation commands * * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * * * IDENTIFICATION * $PostgreSQL: pgsql/src/backend/commands/foreigncmds.c,v 1.8 2009/06/11 14:48:55 momjian Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/heapam.h" #include "access/reloptions.h" #include "catalog/catalog.h" #include "catalog/dependency.h" #include "catalog/indexing.h" #include "catalog/pg_foreign_data_wrapper.h" #include "catalog/pg_foreign_server.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "catalog/pg_user_mapping.h" #include "commands/defrem.h" #include "foreign/foreign.h" #include "miscadmin.h" #include "parser/parse_func.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/syscache.h" /* * Convert a DefElem list to the text array format that is used in * pg_foreign_data_wrapper, pg_foreign_server, and pg_user_mapping. * Returns the array in the form of a Datum, or PointerGetDatum(NULL) * if the list is empty. * * Note: The array is usually stored to database without further * processing, hence any validation should be done before this * conversion. */ static Datum optionListToArray(List *options) { ArrayBuildState *astate = NULL; ListCell *cell; foreach(cell, options) { DefElem *def = lfirst(cell); const char *value; Size len; text *t; value = defGetString(def); len = VARHDRSZ + strlen(def->defname) + 1 + strlen(value); t = palloc(len + 1); SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", def->defname, value); astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); } if (astate) return makeArrayResult(astate, CurrentMemoryContext); return PointerGetDatum(NULL); } /* * Transform a list of DefElem into text array format. This is substantially * the same thing as optionListToArray(), except we recognize SET/ADD/DROP * actions for modifying an existing list of options, which is passed in * Datum form as oldOptions. Also, if fdwvalidator isn't InvalidOid * it specifies a validator function to call on the result. * * Returns the array in the form of a Datum, or PointerGetDatum(NULL) * if the list is empty. * * This is used by CREATE/ALTER of FOREIGN DATA WRAPPER/SERVER/USER MAPPING. */ static Datum transformGenericOptions(Datum oldOptions, List *options, Oid fdwvalidator) { List *resultOptions = untransformRelOptions(oldOptions); ListCell *optcell; Datum result; foreach(optcell, options) { DefElem *od = lfirst(optcell); ListCell *cell; ListCell *prev = NULL; /* * Find the element in resultOptions. We need this for validation in * all cases. Also identify the previous element. */ foreach(cell, resultOptions) { DefElem *def = lfirst(cell); if (strcmp(def->defname, od->defname) == 0) break; else prev = cell; } /* * It is possible to perform multiple SET/DROP actions on the same * option. The standard permits this, as long as the options to be * added are unique. Note that an unspecified action is taken to be * ADD. */ switch (od->defaction) { case DEFELEM_DROP: if (!cell) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("option \"%s\" not found", od->defname))); resultOptions = list_delete_cell(resultOptions, cell, prev); break; case DEFELEM_SET: if (!cell) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("option \"%s\" not found", od->defname))); lfirst(cell) = od; break; case DEFELEM_ADD: case DEFELEM_UNSPEC: if (cell) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("option \"%s\" provided more than once", od->defname))); resultOptions = lappend(resultOptions, od); break; default: elog(ERROR, "unrecognized action %d on option \"%s\"", (int) od->defaction, od->defname); break; } } result = optionListToArray(resultOptions); if (fdwvalidator) OidFunctionCall2(fdwvalidator, result, (Datum) 0); return result; } /* * Convert the user mapping user name to OID */ static Oid GetUserOidFromMapping(const char *username, bool missing_ok) { if (!username) /* PUBLIC user mapping */ return InvalidOid; if (strcmp(username, "current_user") == 0) /* map to the owner */ return GetUserId(); /* map to provided user */ return missing_ok ? get_roleid(username) : get_roleid_checked(username); } /* * Change foreign-data wrapper owner. * * Allow this only for superusers; also the new owner must be a * superuser. */ void AlterForeignDataWrapperOwner(const char *name, Oid newOwnerId) { HeapTuple tup; Relation rel; Oid fdwId; Form_pg_foreign_data_wrapper form; /* Must be a superuser to change a FDW owner */ if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to change owner of foreign-data wrapper \"%s\"", name), errhint("Must be superuser to change owner of a foreign-data wrapper."))); /* New owner must also be a superuser */ if (!superuser_arg(newOwnerId)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to change owner of foreign-data wrapper \"%s\"", name), errhint("The owner of a foreign-data wrapper must be a superuser."))); rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock); tup = SearchSysCacheCopy(FOREIGNDATAWRAPPERNAME, CStringGetDatum(name), 0, 0, 0); if (!HeapTupleIsValid(tup)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("foreign-data wrapper \"%s\" does not exist", name))); fdwId = HeapTupleGetOid(tup); form = (Form_pg_foreign_data_wrapper) GETSTRUCT(tup); if (form->fdwowner != newOwnerId) { form->fdwowner = newOwnerId; simple_heap_update(rel, &tup->t_self, tup); CatalogUpdateIndexes(rel, tup); /* Update owner dependency reference */ changeDependencyOnOwner(ForeignDataWrapperRelationId, fdwId, newOwnerId); } heap_close(rel, NoLock); heap_freetuple(tup); } /* * Change foreign server owner */ void AlterForeignServerOwner(const char *name, Oid newOwnerId) { HeapTuple tup; Relation rel; Oid srvId; AclResult aclresult; Form_pg_foreign_server form; rel = heap_open(ForeignServerRelationId, RowExclusiveLock); tup = SearchSysCacheCopy(FOREIGNSERVERNAME, CStringGetDatum(name), 0, 0, 0); if (!HeapTupleIsValid(tup)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("server \"%s\" does not exist", name))); srvId = HeapTupleGetOid(tup); form = (Form_pg_foreign_server) GETSTRUCT(tup); if (form->srvowner != newOwnerId) { /* Superusers can always do it */ if (!superuser()) { /* Must be owner */ if (!pg_foreign_server_ownercheck(srvId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER, name); /* Must be able to become new owner */ check_is_member_of_role(GetUserId(), newOwnerId); /* New owner must have USAGE privilege on foreign-data wrapper */ aclresult = pg_foreign_data_wrapper_aclcheck(form->srvfdw, newOwnerId, ACL_USAGE); if (aclresult != ACLCHECK_OK) { ForeignDataWrapper *fdw = GetForeignDataWrapper(form->srvfdw); aclcheck_error(aclresult, ACL_KIND_FDW, fdw->fdwname); } } form->srvowner = newOwnerId; simple_heap_update(rel, &tup->t_self, tup); CatalogUpdateIndexes(rel, tup); /* Update owner dependency reference */ changeDependencyOnOwner(ForeignServerRelationId, HeapTupleGetOid(tup), newOwnerId); } heap_close(rel, NoLock); heap_freetuple(tup); } /* * Convert a validator function name passed from the parser to an Oid. */ static Oid lookup_fdw_validator_func(List *validator) { Oid funcargtypes[2]; funcargtypes[0] = TEXTARRAYOID; funcargtypes[1] = OIDOID; return LookupFuncName(validator, 2, funcargtypes, false); /* return value is ignored, so we don't check the type */ } /* * Create a foreign-data wrapper */ void CreateForeignDataWrapper(CreateFdwStmt *stmt) { Relation rel; Datum values[Natts_pg_foreign_data_wrapper]; bool nulls[Natts_pg_foreign_data_wrapper]; HeapTuple tuple; Oid fdwId; Oid fdwvalidator; Datum fdwoptions; Oid ownerId; /* Must be super user */ if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to create foreign-data wrapper \"%s\"", stmt->fdwname), errhint("Must be superuser to create a foreign-data wrapper."))); /* For now the owner cannot be specified on create. Use effective user ID. */ ownerId = GetUserId(); /* * Check that there is no other foreign-data wrapper by this name. */ if (GetForeignDataWrapperByName(stmt->fdwname, true) != NULL) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("foreign-data wrapper \"%s\" already exists", stmt->fdwname))); /* * Insert tuple into pg_foreign_data_wrapper. */ rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock); memset(values, 0, sizeof(values)); memset(nulls, false, sizeof(nulls)); values[Anum_pg_foreign_data_wrapper_fdwname - 1] = DirectFunctionCall1(namein, CStringGetDatum(stmt->fdwname)); values[Anum_pg_foreign_data_wrapper_fdwowner - 1] = ObjectIdGetDatum(ownerId); if (stmt->validator) fdwvalidator = lookup_fdw_validator_func(stmt->validator); else fdwvalidator = InvalidOid; values[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = fdwvalidator; nulls[Anum_pg_foreign_data_wrapper_fdwacl - 1] = true; fdwoptions = transformGenericOptions(PointerGetDatum(NULL), stmt->options, fdwvalidator); if (PointerIsValid(DatumGetPointer(fdwoptions))) values[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = fdwoptions; else nulls[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = true; tuple = heap_form_tuple(rel->rd_att, values, nulls); fdwId = simple_heap_insert(rel, tuple); CatalogUpdateIndexes(rel, tuple); heap_freetuple(tuple); if (fdwvalidator) { ObjectAddress myself; ObjectAddress referenced; myself.classId = ForeignDataWrapperRelationId; myself.objectId = fdwId; myself.objectSubId = 0; referenced.classId = ProcedureRelationId; referenced.objectId = fdwvalidator; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } recordDependencyOnOwner(ForeignDataWrapperRelationId, fdwId, ownerId); heap_close(rel, NoLock); } /* * Alter foreign-data wrapper */ void AlterForeignDataWrapper(AlterFdwStmt *stmt) { Relation rel; HeapTuple tp; Datum repl_val[Natts_pg_foreign_data_wrapper]; bool repl_null[Natts_pg_foreign_data_wrapper]; bool repl_repl[Natts_pg_foreign_data_wrapper]; Oid fdwId; bool isnull; Datum datum; Oid fdwvalidator; /* Must be super user */ if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to alter foreign-data wrapper \"%s\"", stmt->fdwname), errhint("Must be superuser to alter a foreign-data wrapper."))); tp = SearchSysCacheCopy(FOREIGNDATAWRAPPERNAME, CStringGetDatum(stmt->fdwname), 0, 0, 0); if (!HeapTupleIsValid(tp)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("foreign-data wrapper \"%s\" does not exist", stmt->fdwname))); fdwId = HeapTupleGetOid(tp); memset(repl_val, 0, sizeof(repl_val)); memset(repl_null, false, sizeof(repl_null)); memset(repl_repl, false, sizeof(repl_repl)); if (stmt->change_validator) { fdwvalidator = stmt->validator ? lookup_fdw_validator_func(stmt->validator) : InvalidOid; repl_val[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = ObjectIdGetDatum(fdwvalidator); repl_repl[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = true; /* * It could be that the options for the FDW, SERVER and USER MAPPING * are no longer valid with the new validator. Warn about this. */ if (stmt->validator) ereport(WARNING, (errmsg("changing the foreign-data wrapper validator can cause " "the options for dependent objects to become invalid"))); } else { /* * Validator is not changed, but we need it for validating options. */ datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID, tp, Anum_pg_foreign_data_wrapper_fdwvalidator, &isnull); Assert(!isnull); fdwvalidator = DatumGetObjectId(datum); } /* * Options specified, validate and update. */ if (stmt->options) { /* Extract the current options */ datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID, tp, Anum_pg_foreign_data_wrapper_fdwoptions, &isnull); if (isnull) datum = PointerGetDatum(NULL); /* Transform the options */ datum = transformGenericOptions(datum, stmt->options, fdwvalidator); if (PointerIsValid(DatumGetPointer(datum))) repl_val[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = datum; else repl_null[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = true; repl_repl[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = true; } /* Everything looks good - update the tuple */ rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock); tp = heap_modify_tuple(tp, RelationGetDescr(rel), repl_val, repl_null, repl_repl); simple_heap_update(rel, &tp->t_self, tp); CatalogUpdateIndexes(rel, tp); heap_close(rel, RowExclusiveLock); heap_freetuple(tp); } /* * Drop foreign-data wrapper */ void RemoveForeignDataWrapper(DropFdwStmt *stmt) { Oid fdwId; ObjectAddress object; fdwId = GetForeignDataWrapperOidByName(stmt->fdwname, true); if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to drop foreign-data wrapper \"%s\"", stmt->fdwname), errhint("Must be superuser to drop a foreign-data wrapper."))); if (!OidIsValid(fdwId)) { if (!stmt->missing_ok) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("foreign-data wrapper \"%s\" does not exist", stmt->fdwname))); /* IF EXISTS specified, just note it */ ereport(NOTICE, (errmsg("foreign-data wrapper \"%s\" does not exist, skipping", stmt->fdwname))); return; } /* * Do the deletion */ object.classId = ForeignDataWrapperRelationId; object.objectId = fdwId; object.objectSubId = 0; performDeletion(&object, stmt->behavior); } /* * Drop foreign-data wrapper by OID */ void RemoveForeignDataWrapperById(Oid fdwId) { HeapTuple tp; Relation rel; rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock); tp = SearchSysCache(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwId), 0, 0, 0); if (!HeapTupleIsValid(tp)) elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwId); simple_heap_delete(rel, &tp->t_self); ReleaseSysCache(tp); heap_close(rel, RowExclusiveLock); } /* * Create a foreign server */ void CreateForeignServer(CreateForeignServerStmt *stmt) { Relation rel; Datum srvoptions; Datum values[Natts_pg_foreign_server]; bool nulls[Natts_pg_foreign_server]; HeapTuple tuple; Oid srvId; Oid ownerId; AclResult aclresult; ObjectAddress myself; ObjectAddress referenced; ForeignDataWrapper *fdw; /* For now the owner cannot be specified on create. Use effective user ID. */ ownerId = GetUserId(); /* * Check that there is no other foreign server by this name. */ if (GetForeignServerByName(stmt->servername, true) != NULL) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("server \"%s\" already exists", stmt->servername))); /* * Check that the FDW exists and that we have USAGE on it. Also get the * actual FDW for option validation etc. */ fdw = GetForeignDataWrapperByName(stmt->fdwname, false); 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_server. */ rel = heap_open(ForeignServerRelationId, RowExclusiveLock); memset(values, 0, sizeof(values)); memset(nulls, false, sizeof(nulls)); values[Anum_pg_foreign_server_srvname - 1] = DirectFunctionCall1(namein, CStringGetDatum(stmt->servername)); values[Anum_pg_foreign_server_srvowner - 1] = ObjectIdGetDatum(ownerId); values[Anum_pg_foreign_server_srvfdw - 1] = ObjectIdGetDatum(fdw->fdwid); /* Add server type if supplied */ if (stmt->servertype) values[Anum_pg_foreign_server_srvtype - 1] = CStringGetTextDatum(stmt->servertype); else nulls[Anum_pg_foreign_server_srvtype - 1] = true; /* Add server version if supplied */ if (stmt->version) values[Anum_pg_foreign_server_srvversion - 1] = CStringGetTextDatum(stmt->version); else nulls[Anum_pg_foreign_server_srvversion - 1] = true; /* Start with a blank acl */ nulls[Anum_pg_foreign_server_srvacl - 1] = true; /* Add server options */ srvoptions = transformGenericOptions(PointerGetDatum(NULL), stmt->options, fdw->fdwvalidator); if (PointerIsValid(DatumGetPointer(srvoptions))) values[Anum_pg_foreign_server_srvoptions - 1] = srvoptions; else nulls[Anum_pg_foreign_server_srvoptions - 1] = true; tuple = heap_form_tuple(rel->rd_att, values, nulls); srvId = simple_heap_insert(rel, tuple); CatalogUpdateIndexes(rel, tuple); heap_freetuple(tuple); /* Add dependency on FDW and owner */ myself.classId = ForeignServerRelationId; myself.objectId = srvId; myself.objectSubId = 0; referenced.classId = ForeignDataWrapperRelationId; referenced.objectId = fdw->fdwid; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); recordDependencyOnOwner(ForeignServerRelationId, srvId, ownerId); heap_close(rel, NoLock); } /* * Alter foreign server */ void AlterForeignServer(AlterForeignServerStmt *stmt) { Relation rel; HeapTuple tp; Datum repl_val[Natts_pg_foreign_server]; bool repl_null[Natts_pg_foreign_server]; bool repl_repl[Natts_pg_foreign_server]; Oid srvId; Form_pg_foreign_server srvForm; tp = SearchSysCacheCopy(FOREIGNSERVERNAME, CStringGetDatum(stmt->servername), 0, 0, 0); if (!HeapTupleIsValid(tp)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("server \"%s\" does not exist", stmt->servername))); srvId = HeapTupleGetOid(tp); srvForm = (Form_pg_foreign_server) GETSTRUCT(tp); /* * Only owner or a superuser can ALTER a SERVER. */ if (!pg_foreign_server_ownercheck(srvId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER, stmt->servername); memset(repl_val, 0, sizeof(repl_val)); memset(repl_null, false, sizeof(repl_null)); memset(repl_repl, false, sizeof(repl_repl)); if (stmt->has_version) { /* * Change the server VERSION string. */ if (stmt->version) repl_val[Anum_pg_foreign_server_srvversion - 1] = CStringGetTextDatum(stmt->version); else repl_null[Anum_pg_foreign_server_srvversion - 1] = true; repl_repl[Anum_pg_foreign_server_srvversion - 1] = true; } if (stmt->options) { ForeignDataWrapper *fdw = GetForeignDataWrapper(srvForm->srvfdw); Datum datum; bool isnull; /* Extract the current srvoptions */ datum = SysCacheGetAttr(FOREIGNSERVEROID, tp, Anum_pg_foreign_server_srvoptions, &isnull); if (isnull) datum = PointerGetDatum(NULL); /* Prepare the options array */ datum = transformGenericOptions(datum, stmt->options, fdw->fdwvalidator); if (PointerIsValid(DatumGetPointer(datum))) repl_val[Anum_pg_foreign_server_srvoptions - 1] = datum; else repl_null[Anum_pg_foreign_server_srvoptions - 1] = true; repl_repl[Anum_pg_foreign_server_srvoptions - 1] = true; } /* Everything looks good - update the tuple */ rel = heap_open(ForeignServerRelationId, RowExclusiveLock); tp = heap_modify_tuple(tp, RelationGetDescr(rel), repl_val, repl_null, repl_repl); simple_heap_update(rel, &tp->t_self, tp); CatalogUpdateIndexes(rel, tp); heap_close(rel, RowExclusiveLock); heap_freetuple(tp); } /* * Drop foreign server */ void RemoveForeignServer(DropForeignServerStmt *stmt) { Oid srvId; ObjectAddress object; srvId = GetForeignServerOidByName(stmt->servername, true); if (!OidIsValid(srvId)) { /* Server not found, complain or notice */ if (!stmt->missing_ok) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("server \"%s\" does not exist", stmt->servername))); /* IF EXISTS specified, just note it */ ereport(NOTICE, (errmsg("server \"%s\" does not exist, skipping", stmt->servername))); return; } /* Only allow DROP if the server is owned by the user. */ if (!pg_foreign_server_ownercheck(srvId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER, stmt->servername); object.classId = ForeignServerRelationId; object.objectId = srvId; object.objectSubId = 0; performDeletion(&object, stmt->behavior); } /* * Drop foreign server by OID */ void RemoveForeignServerById(Oid srvId) { HeapTuple tp; Relation rel; rel = heap_open(ForeignServerRelationId, RowExclusiveLock); tp = SearchSysCache(FOREIGNSERVEROID, ObjectIdGetDatum(srvId), 0, 0, 0); if (!HeapTupleIsValid(tp)) elog(ERROR, "cache lookup failed for foreign server %u", srvId); simple_heap_delete(rel, &tp->t_self); ReleaseSysCache(tp); heap_close(rel, RowExclusiveLock); } /* * Common routine to check permission for user-mapping-related DDL * commands. We allow server owners to operate on any mapping, and * users to operate on their own mapping. */ static void user_mapping_ddl_aclcheck(Oid umuserid, Oid serverid, const char *servername) { Oid curuserid = GetUserId(); if (!pg_foreign_server_ownercheck(serverid, curuserid)) { if (umuserid == curuserid) { AclResult aclresult; aclresult = pg_foreign_server_aclcheck(serverid, curuserid, ACL_USAGE); if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_FOREIGN_SERVER, servername); } else aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER, servername); } } /* * Create user mapping */ void CreateUserMapping(CreateUserMappingStmt *stmt) { Relation rel; Datum useoptions; Datum values[Natts_pg_user_mapping]; bool nulls[Natts_pg_user_mapping]; HeapTuple tuple; Oid useId; Oid umId; ObjectAddress myself; ObjectAddress referenced; ForeignServer *srv; ForeignDataWrapper *fdw; useId = GetUserOidFromMapping(stmt->username, false); /* Check that the server exists. */ srv = GetForeignServerByName(stmt->servername, false); user_mapping_ddl_aclcheck(useId, srv->serverid, stmt->servername); /* * Check that the user mapping is unique within server. */ umId = GetSysCacheOid(USERMAPPINGUSERSERVER, ObjectIdGetDatum(useId), ObjectIdGetDatum(srv->serverid), 0, 0); if (OidIsValid(umId)) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("user mapping \"%s\" already exists for server %s", MappingUserName(useId), stmt->servername))); fdw = GetForeignDataWrapper(srv->fdwid); /* * Insert tuple into pg_user_mapping. */ rel = heap_open(UserMappingRelationId, RowExclusiveLock); memset(values, 0, sizeof(values)); memset(nulls, false, sizeof(nulls)); values[Anum_pg_user_mapping_umuser - 1] = ObjectIdGetDatum(useId); values[Anum_pg_user_mapping_umserver - 1] = ObjectIdGetDatum(srv->serverid); /* Add user options */ useoptions = transformGenericOptions(PointerGetDatum(NULL), stmt->options, fdw->fdwvalidator); if (PointerIsValid(DatumGetPointer(useoptions))) values[Anum_pg_user_mapping_umoptions - 1] = useoptions; else nulls[Anum_pg_user_mapping_umoptions - 1] = true; tuple = heap_form_tuple(rel->rd_att, values, nulls); umId = simple_heap_insert(rel, tuple); CatalogUpdateIndexes(rel, tuple); heap_freetuple(tuple); /* Add dependency on the server */ myself.classId = UserMappingRelationId; myself.objectId = umId; myself.objectSubId = 0; referenced.classId = ForeignServerRelationId; referenced.objectId = srv->serverid; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); if (OidIsValid(useId)) /* Record the mapped user dependency */ recordDependencyOnOwner(UserMappingRelationId, umId, useId); heap_close(rel, NoLock); } /* * Alter user mapping */ void AlterUserMapping(AlterUserMappingStmt *stmt) { Relation rel; HeapTuple tp; Datum repl_val[Natts_pg_user_mapping]; bool repl_null[Natts_pg_user_mapping]; bool repl_repl[Natts_pg_user_mapping]; Oid useId; Oid umId; ForeignServer *srv; useId = GetUserOidFromMapping(stmt->username, false); srv = GetForeignServerByName(stmt->servername, false); umId = GetSysCacheOid(USERMAPPINGUSERSERVER, ObjectIdGetDatum(useId), ObjectIdGetDatum(srv->serverid), 0, 0); if (!OidIsValid(umId)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("user mapping \"%s\" does not exist for the server", MappingUserName(useId)))); user_mapping_ddl_aclcheck(useId, srv->serverid, stmt->servername); tp = SearchSysCacheCopy(USERMAPPINGOID, ObjectIdGetDatum(umId), 0, 0, 0); if (!HeapTupleIsValid(tp)) elog(ERROR, "cache lookup failed for user mapping %u", umId); memset(repl_val, 0, sizeof(repl_val)); memset(repl_null, false, sizeof(repl_null)); memset(repl_repl, false, sizeof(repl_repl)); if (stmt->options) { ForeignDataWrapper *fdw; Datum datum; bool isnull; /* * Process the options. */ fdw = GetForeignDataWrapper(srv->fdwid); datum = SysCacheGetAttr(USERMAPPINGUSERSERVER, tp, Anum_pg_user_mapping_umoptions, &isnull); if (isnull) datum = PointerGetDatum(NULL); /* Prepare the options array */ datum = transformGenericOptions(datum, stmt->options, fdw->fdwvalidator); if (PointerIsValid(DatumGetPointer(datum))) repl_val[Anum_pg_user_mapping_umoptions - 1] = datum; else repl_null[Anum_pg_user_mapping_umoptions - 1] = true; repl_repl[Anum_pg_user_mapping_umoptions - 1] = true; } /* Everything looks good - update the tuple */ rel = heap_open(UserMappingRelationId, RowExclusiveLock); tp = heap_modify_tuple(tp, RelationGetDescr(rel), repl_val, repl_null, repl_repl); simple_heap_update(rel, &tp->t_self, tp); CatalogUpdateIndexes(rel, tp); heap_close(rel, RowExclusiveLock); heap_freetuple(tp); } /* * Drop user mapping */ void RemoveUserMapping(DropUserMappingStmt *stmt) { ObjectAddress object; Oid useId; Oid umId; ForeignServer *srv; useId = GetUserOidFromMapping(stmt->username, stmt->missing_ok); srv = GetForeignServerByName(stmt->servername, true); if (stmt->username && !OidIsValid(useId)) { /* * IF EXISTS specified, role not found and not public. Notice this and * leave. */ elog(NOTICE, "role \"%s\" does not exist, skipping", stmt->username); return; } if (!srv) { if (!stmt->missing_ok) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("server \"%s\" does not exist", stmt->servername))); /* IF EXISTS, just note it */ ereport(NOTICE, (errmsg("server does not exist, skipping"))); return; } umId = GetSysCacheOid(USERMAPPINGUSERSERVER, ObjectIdGetDatum(useId), ObjectIdGetDatum(srv->serverid), 0, 0); if (!OidIsValid(umId)) { if (!stmt->missing_ok) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("user mapping \"%s\" does not exist for the server", MappingUserName(useId)))); /* IF EXISTS specified, just note it */ ereport(NOTICE, (errmsg("user mapping \"%s\" does not exist for the server, skipping", MappingUserName(useId)))); return; } user_mapping_ddl_aclcheck(useId, srv->serverid, srv->servername); /* * Do the deletion */ object.classId = UserMappingRelationId; object.objectId = umId; object.objectSubId = 0; performDeletion(&object, DROP_CASCADE); } /* * Drop user mapping by OID. This is called to clean up dependencies. */ void RemoveUserMappingById(Oid umId) { HeapTuple tp; Relation rel; rel = heap_open(UserMappingRelationId, RowExclusiveLock); tp = SearchSysCache(USERMAPPINGOID, ObjectIdGetDatum(umId), 0, 0, 0); if (!HeapTupleIsValid(tp)) elog(ERROR, "cache lookup failed for user mapping %u", umId); simple_heap_delete(rel, &tp->t_self); ReleaseSysCache(tp); heap_close(rel, RowExclusiveLock); }