diff options
author | Peter Eisentraut <peter_e@gmx.net> | 2008-12-19 16:25:19 +0000 |
---|---|---|
committer | Peter Eisentraut <peter_e@gmx.net> | 2008-12-19 16:25:19 +0000 |
commit | cae565e503c42a0942ca1771665243b4453c5770 (patch) | |
tree | 625121907a64d7716686a0be5f9e302fdfc42916 /src/backend/commands/foreigncmds.c | |
parent | 1eec10a2de3925ef791904835e2437d1efe97139 (diff) | |
download | postgresql-cae565e503c42a0942ca1771665243b4453c5770.tar.gz postgresql-cae565e503c42a0942ca1771665243b4453c5770.zip |
SQL/MED catalog manipulation facilities
This doesn't do any remote or external things yet, but it gives modules
like plproxy and dblink a standardized and future-proof system for
managing their connection information.
Martin Pihlak and Peter Eisentraut
Diffstat (limited to 'src/backend/commands/foreigncmds.c')
-rw-r--r-- | src/backend/commands/foreigncmds.c | 1102 |
1 files changed, 1102 insertions, 0 deletions
diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c new file mode 100644 index 00000000000..224a8f004e6 --- /dev/null +++ b/src/backend/commands/foreigncmds.c @@ -0,0 +1,1102 @@ +/*------------------------------------------------------------------------- + * + * foreigncmds.c + * foreign-data wrapper/server creation/manipulation commands + * + * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * $PostgreSQL: pgsql/src/backend/commands/foreigncmds.c,v 1.1 2008/12/19 16:25:17 petere 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_type.h" +#include "catalog/pg_user_mapping.h" +#include "commands/defrem.h" +#include "foreign/foreign.h" +#include "miscadmin.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. + * + * 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; + text *t; + + foreach (cell, options) + { + DefElem *def = lfirst(cell); + const char *value = ""; + Size len; + + 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 the list of OptionDefElem into list of generic options. + * The result is converted to array of text suitable for storing in + * options. + * + * This is used by CREATE/ALTER of FOREIGN DATA WRAPPER/SERVER/USER + * MAPPING. In the ALTER cases, oldOptions is the current text array + * of options. + */ +static Datum +transformGenericOptions(Datum oldOptions, + List *optionDefList, + GenericOptionFlags flags, + ForeignDataWrapper *fdw, + OptionListValidatorFunc validateOptionList) +{ + List *resultOptions = untransformRelOptions(oldOptions); + ListCell *optcell; + + foreach (optcell, optionDefList) + { + ListCell *cell; + ListCell *prev = NULL; + OptionDefElem *od = lfirst(optcell); + + /* + * Find the element in resultOptions. We need this for + * validation in all cases. + */ + foreach (cell, resultOptions) + { + DefElem *def = lfirst(cell); + + if (strcmp(def->defname, od->def->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. + */ + + switch (od->alter_op) + { + case ALTER_OPT_DROP: + if (!cell) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("option \"%s\" not found", + od->def->defname))); + resultOptions = list_delete_cell(resultOptions, cell, prev); + break; + + case ALTER_OPT_SET: + if (!cell) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("option \"%s\" not found", + od->def->defname))); + lfirst(cell) = od->def; + break; + + case ALTER_OPT_ADD: + if (cell) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("option \"%s\" provided more than once", + od->def->defname))); + resultOptions = lappend(resultOptions, od->def); + break; + + default: + elog(ERROR, "unrecognized action %d on option \"%s\"", + od->alter_op, od->def->defname); + break; + } + } + + if (validateOptionList) + validateOptionList(fdw, flags, resultOptions); + + return optionListToArray(resultOptions); +} + + +/* + * 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); +} + + +/* + * 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; + Datum fdwoptions = InvalidOid; + Oid ownerId; + ForeignDataWrapperLibrary *fdwlib; + + /* 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(nulls, false, Natts_pg_foreign_data_wrapper); + + values[Anum_pg_foreign_data_wrapper_fdwname - 1] = + DirectFunctionCall1(namein, CStringGetDatum(stmt->fdwname)); + values[Anum_pg_foreign_data_wrapper_fdwowner - 1] = ObjectIdGetDatum(ownerId); + values[Anum_pg_foreign_data_wrapper_fdwlibrary - 1] = CStringGetTextDatum(stmt->library); + nulls[Anum_pg_foreign_data_wrapper_fdwacl - 1] = true; + + /* + * See if the FDW library loads at all. We also might want to use it + * later for validating the options. + */ + fdwlib = GetForeignDataWrapperLibrary(stmt->library); + + fdwoptions = transformGenericOptions(0, stmt->options, FdwOpt, NULL, + fdwlib->validateOptionList); + + if (OidIsValid(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); + + 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; + ForeignDataWrapperLibrary *fdwlib; + + /* 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->library) + { + /* + * New library specified -- load to see if valid. + */ + fdwlib = GetForeignDataWrapperLibrary(stmt->library); + + repl_val[Anum_pg_foreign_data_wrapper_fdwlibrary - 1] = CStringGetTextDatum(stmt->library); + repl_repl[Anum_pg_foreign_data_wrapper_fdwlibrary - 1] = true; + + /* + * It could be that the options for the FDW, SERVER and USER MAPPING + * are no longer valid with the new library. Warn about this. + */ + ereport(WARNING, + (errmsg("changing the foreign-data wrapper library can cause " + "the options for dependent objects to become invalid"))); + } + else + { + /* + * No LIBRARY clause specified, but we need to load it for validating + * options. + */ + datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID, + tp, + Anum_pg_foreign_data_wrapper_fdwlibrary, + &isnull); + fdwlib = GetForeignDataWrapperLibrary(TextDatumGetCString(datum)); + } + + /* + * Options specified, validate and update. + */ + if (stmt->options) + { + /* Extract the current options */ + datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID, + tp, + Anum_pg_foreign_data_wrapper_fdwoptions, + &isnull); + + /* Transform the options */ + datum = transformGenericOptions(datum, stmt->options, FdwOpt, + NULL, fdwlib->validateOptionList); + + if (OidIsValid(datum)) + repl_val[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = ObjectIdGetDatum(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 = InvalidOid; + 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(nulls, false, Natts_pg_foreign_server); + + 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(0, stmt->options, ServerOpt, fdw, + fdw->lib->validateOptionList); + + if (OidIsValid(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); + + /* Prepare the options array */ + datum = transformGenericOptions(datum, stmt->options, ServerOpt, + fdw, fdw->lib->validateOptionList); + + if (OidIsValid(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); +} + + +/* + * Create user mapping + */ +void +CreateUserMapping(CreateUserMappingStmt *stmt) +{ + Relation rel; + Datum useoptions = InvalidOid; + Datum values[Natts_pg_user_mapping]; + bool nulls[Natts_pg_user_mapping]; + HeapTuple tuple; + Oid useId; + Oid umId; + Oid ownerId; + ObjectAddress myself; + ObjectAddress referenced; + ForeignServer *srv; + ForeignDataWrapper *fdw; + + ownerId = GetUserId(); + + useId = GetUserOidFromMapping(stmt->username, false); + + /* + * Check that the server exists and that the we own it. + */ + srv = GetForeignServerByName(stmt->servername, false); + + if (!pg_foreign_server_ownercheck(srv->serverid, ownerId)) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER, + 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(nulls, false, Natts_pg_user_mapping); + + values[Anum_pg_user_mapping_umuser - 1] = ObjectIdGetDatum(useId); + values[Anum_pg_user_mapping_umserver - 1] = ObjectIdGetDatum(srv->serverid); + + /* Add user options */ + useoptions = transformGenericOptions(0, stmt->options, UserMappingOpt, + fdw, fdw->lib->validateOptionList); + + if (OidIsValid(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)))); + } + + /* + * Must be owner of the server to alter user mapping. + */ + if (!pg_foreign_server_ownercheck(srv->serverid, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER, + 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); + + /* Prepare the options array */ + datum = transformGenericOptions(datum, stmt->options, UserMappingOpt, + fdw, fdw->lib->validateOptionList); + + if (OidIsValid(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; + } + + /* + * Only allow DROP if we own the server. + */ + if (!pg_foreign_server_ownercheck(srv->serverid, GetUserId())) + { + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER, + 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); +} |