/*------------------------------------------------------------------------- * * dropcmds.c * handle various "DROP" operations * * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * src/backend/commands/dropcmds.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/htup_details.h" #include "access/table.h" #include "access/xact.h" #include "catalog/dependency.h" #include "catalog/namespace.h" #include "catalog/objectaddress.h" #include "catalog/pg_class.h" #include "catalog/pg_proc.h" #include "commands/defrem.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "parser/parse_type.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/syscache.h" static void does_not_exist_skipping(ObjectType objtype, Node *object); static bool owningrel_does_not_exist_skipping(List *object, const char **msg, char **name); static bool schema_does_not_exist_skipping(List *object, const char **msg, char **name); static bool type_in_list_does_not_exist_skipping(List *typenames, const char **msg, char **name); /* * Drop one or more objects. * * We don't currently handle all object types here. Relations, for example, * require special handling, because (for example) indexes have additional * locking requirements. * * We look up all the objects first, and then delete them in a single * performMultipleDeletions() call. This avoids unnecessary DROP RESTRICT * errors if there are dependencies between them. */ void RemoveObjects(DropStmt *stmt) { ObjectAddresses *objects; ListCell *cell1; objects = new_object_addresses(); foreach(cell1, stmt->objects) { ObjectAddress address; Node *object = lfirst(cell1); Relation relation = NULL; Oid namespaceId; /* Get an ObjectAddress for the object. */ address = get_object_address(stmt->removeType, object, &relation, AccessExclusiveLock, stmt->missing_ok); /* * Issue NOTICE if supplied object was not found. Note this is only * relevant in the missing_ok case, because otherwise * get_object_address would have thrown an error. */ if (!OidIsValid(address.objectId)) { Assert(stmt->missing_ok); does_not_exist_skipping(stmt->removeType, object); continue; } /* * Although COMMENT ON FUNCTION, SECURITY LABEL ON FUNCTION, etc. are * happy to operate on an aggregate as on any other function, we have * historically not allowed this for DROP FUNCTION. */ if (stmt->removeType == OBJECT_FUNCTION) { if (get_func_prokind(address.objectId) == PROKIND_AGGREGATE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is an aggregate function", NameListToString(castNode(ObjectWithArgs, object)->objname)), errhint("Use DROP AGGREGATE to drop aggregate functions."))); } /* Check permissions. */ namespaceId = get_object_namespace(&address); if (!OidIsValid(namespaceId) || !pg_namespace_ownercheck(namespaceId, GetUserId())) check_object_ownership(GetUserId(), stmt->removeType, address, object, relation); /* * Make note if a temporary namespace has been accessed in this * transaction. */ if (OidIsValid(namespaceId) && isTempNamespace(namespaceId)) MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE; /* Release any relcache reference count, but keep lock until commit. */ if (relation) table_close(relation, NoLock); add_exact_object_address(&address, objects); } /* Here we really delete them. */ performMultipleDeletions(objects, stmt->behavior, 0); free_object_addresses(objects); } /* * owningrel_does_not_exist_skipping * Subroutine for RemoveObjects * * After determining that a specification for a rule or trigger returns that * the specified object does not exist, test whether its owning relation, and * its schema, exist or not; if they do, return false --- the trigger or rule * itself is missing instead. If the owning relation or its schema do not * exist, fill the error message format string and name, and return true. */ static bool owningrel_does_not_exist_skipping(List *object, const char **msg, char **name) { List *parent_object; RangeVar *parent_rel; parent_object = list_truncate(list_copy(object), list_length(object) - 1); if (schema_does_not_exist_skipping(parent_object, msg, name)) return true; parent_rel = makeRangeVarFromNameList(parent_object); if (!OidIsValid(RangeVarGetRelid(parent_rel, NoLock, true))) { *msg = gettext_noop("relation \"%s\" does not exist, skipping"); *name = NameListToString(parent_object); return true; } return false; } /* * schema_does_not_exist_skipping * Subroutine for RemoveObjects * * After determining that a specification for a schema-qualifiable object * refers to an object that does not exist, test whether the specified schema * exists or not. If no schema was specified, or if the schema does exist, * return false -- the object itself is missing instead. If the specified * schema does not exist, fill the error message format string and the * specified schema name, and return true. */ static bool schema_does_not_exist_skipping(List *object, const char **msg, char **name) { RangeVar *rel; rel = makeRangeVarFromNameList(object); if (rel->schemaname != NULL && !OidIsValid(LookupNamespaceNoError(rel->schemaname))) { *msg = gettext_noop("schema \"%s\" does not exist, skipping"); *name = rel->schemaname; return true; } return false; } /* * type_in_list_does_not_exist_skipping * Subroutine for RemoveObjects * * After determining that a specification for a function, cast, aggregate or * operator returns that the specified object does not exist, test whether the * involved datatypes, and their schemas, exist or not; if they do, return * false --- the original object itself is missing instead. If the datatypes * or schemas do not exist, fill the error message format string and the * missing name, and return true. * * First parameter is a list of TypeNames. */ static bool type_in_list_does_not_exist_skipping(List *typenames, const char **msg, char **name) { ListCell *l; foreach(l, typenames) { TypeName *typeName = lfirst_node(TypeName, l); if (typeName != NULL) { if (!OidIsValid(LookupTypeNameOid(NULL, typeName, true))) { /* type doesn't exist, try to find why */ if (schema_does_not_exist_skipping(typeName->names, msg, name)) return true; *msg = gettext_noop("type \"%s\" does not exist, skipping"); *name = TypeNameToString(typeName); return true; } } } return false; } /* * does_not_exist_skipping * Subroutine for RemoveObjects * * Generate a NOTICE stating that the named object was not found, and is * being skipped. This is only relevant when "IF EXISTS" is used; otherwise, * get_object_address() in RemoveObjects would have thrown an ERROR. */ static void does_not_exist_skipping(ObjectType objtype, Node *object) { const char *msg = NULL; char *name = NULL; char *args = NULL; switch (objtype) { case OBJECT_ACCESS_METHOD: msg = gettext_noop("access method \"%s\" does not exist, skipping"); name = strVal((Value *) object); break; case OBJECT_TYPE: case OBJECT_DOMAIN: { TypeName *typ = castNode(TypeName, object); if (!schema_does_not_exist_skipping(typ->names, &msg, &name)) { msg = gettext_noop("type \"%s\" does not exist, skipping"); name = TypeNameToString(typ); } } break; case OBJECT_COLLATION: if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name)) { msg = gettext_noop("collation \"%s\" does not exist, skipping"); name = NameListToString(castNode(List, object)); } break; case OBJECT_CONVERSION: if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name)) { msg = gettext_noop("conversion \"%s\" does not exist, skipping"); name = NameListToString(castNode(List, object)); } break; case OBJECT_SCHEMA: msg = gettext_noop("schema \"%s\" does not exist, skipping"); name = strVal((Value *) object); break; case OBJECT_STATISTIC_EXT: if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name)) { msg = gettext_noop("statistics object \"%s\" does not exist, skipping"); name = NameListToString(castNode(List, object)); } break; case OBJECT_TSPARSER: if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name)) { msg = gettext_noop("text search parser \"%s\" does not exist, skipping"); name = NameListToString(castNode(List, object)); } break; case OBJECT_TSDICTIONARY: if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name)) { msg = gettext_noop("text search dictionary \"%s\" does not exist, skipping"); name = NameListToString(castNode(List, object)); } break; case OBJECT_TSTEMPLATE: if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name)) { msg = gettext_noop("text search template \"%s\" does not exist, skipping"); name = NameListToString(castNode(List, object)); } break; case OBJECT_TSCONFIGURATION: if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name)) { msg = gettext_noop("text search configuration \"%s\" does not exist, skipping"); name = NameListToString(castNode(List, object)); } break; case OBJECT_EXTENSION: msg = gettext_noop("extension \"%s\" does not exist, skipping"); name = strVal((Value *) object); break; case OBJECT_FUNCTION: { ObjectWithArgs *owa = castNode(ObjectWithArgs, object); if (!schema_does_not_exist_skipping(owa->objname, &msg, &name) && !type_in_list_does_not_exist_skipping(owa->objargs, &msg, &name)) { msg = gettext_noop("function %s(%s) does not exist, skipping"); name = NameListToString(owa->objname); args = TypeNameListToString(owa->objargs); } break; } case OBJECT_PROCEDURE: { ObjectWithArgs *owa = castNode(ObjectWithArgs, object); if (!schema_does_not_exist_skipping(owa->objname, &msg, &name) && !type_in_list_does_not_exist_skipping(owa->objargs, &msg, &name)) { msg = gettext_noop("procedure %s(%s) does not exist, skipping"); name = NameListToString(owa->objname); args = TypeNameListToString(owa->objargs); } break; } case OBJECT_ROUTINE: { ObjectWithArgs *owa = castNode(ObjectWithArgs, object); if (!schema_does_not_exist_skipping(owa->objname, &msg, &name) && !type_in_list_does_not_exist_skipping(owa->objargs, &msg, &name)) { msg = gettext_noop("routine %s(%s) does not exist, skipping"); name = NameListToString(owa->objname); args = TypeNameListToString(owa->objargs); } break; } case OBJECT_AGGREGATE: { ObjectWithArgs *owa = castNode(ObjectWithArgs, object); if (!schema_does_not_exist_skipping(owa->objname, &msg, &name) && !type_in_list_does_not_exist_skipping(owa->objargs, &msg, &name)) { msg = gettext_noop("aggregate %s(%s) does not exist, skipping"); name = NameListToString(owa->objname); args = TypeNameListToString(owa->objargs); } break; } case OBJECT_OPERATOR: { ObjectWithArgs *owa = castNode(ObjectWithArgs, object); if (!schema_does_not_exist_skipping(owa->objname, &msg, &name) && !type_in_list_does_not_exist_skipping(owa->objargs, &msg, &name)) { msg = gettext_noop("operator %s does not exist, skipping"); name = NameListToString(owa->objname); } break; } case OBJECT_LANGUAGE: msg = gettext_noop("language \"%s\" does not exist, skipping"); name = strVal((Value *) object); break; case OBJECT_CAST: { if (!type_in_list_does_not_exist_skipping(list_make1(linitial(castNode(List, object))), &msg, &name) && !type_in_list_does_not_exist_skipping(list_make1(lsecond(castNode(List, object))), &msg, &name)) { /* XXX quote or no quote? */ msg = gettext_noop("cast from type %s to type %s does not exist, skipping"); name = TypeNameToString(linitial_node(TypeName, castNode(List, object))); args = TypeNameToString(lsecond_node(TypeName, castNode(List, object))); } } break; case OBJECT_TRANSFORM: if (!type_in_list_does_not_exist_skipping(list_make1(linitial(castNode(List, object))), &msg, &name)) { msg = gettext_noop("transform for type %s language \"%s\" does not exist, skipping"); name = TypeNameToString(linitial_node(TypeName, castNode(List, object))); args = strVal(lsecond(castNode(List, object))); } break; case OBJECT_TRIGGER: if (!owningrel_does_not_exist_skipping(castNode(List, object), &msg, &name)) { msg = gettext_noop("trigger \"%s\" for relation \"%s\" does not exist, skipping"); name = strVal(llast(castNode(List, object))); args = NameListToString(list_truncate(list_copy(castNode(List, object)), list_length(castNode(List, object)) - 1)); } break; case OBJECT_POLICY: if (!owningrel_does_not_exist_skipping(castNode(List, object), &msg, &name)) { msg = gettext_noop("policy \"%s\" for relation \"%s\" does not exist, skipping"); name = strVal(llast(castNode(List, object))); args = NameListToString(list_truncate(list_copy(castNode(List, object)), list_length(castNode(List, object)) - 1)); } break; case OBJECT_EVENT_TRIGGER: msg = gettext_noop("event trigger \"%s\" does not exist, skipping"); name = strVal((Value *) object); break; case OBJECT_RULE: if (!owningrel_does_not_exist_skipping(castNode(List, object), &msg, &name)) { msg = gettext_noop("rule \"%s\" for relation \"%s\" does not exist, skipping"); name = strVal(llast(castNode(List, object))); args = NameListToString(list_truncate(list_copy(castNode(List, object)), list_length(castNode(List, object)) - 1)); } break; case OBJECT_FDW: msg = gettext_noop("foreign-data wrapper \"%s\" does not exist, skipping"); name = strVal((Value *) object); break; case OBJECT_FOREIGN_SERVER: msg = gettext_noop("server \"%s\" does not exist, skipping"); name = strVal((Value *) object); break; case OBJECT_OPCLASS: { List *opcname = list_copy_tail(castNode(List, object), 1); if (!schema_does_not_exist_skipping(opcname, &msg, &name)) { msg = gettext_noop("operator class \"%s\" does not exist for access method \"%s\", skipping"); name = NameListToString(opcname); args = strVal(linitial(castNode(List, object))); } } break; case OBJECT_OPFAMILY: { List *opfname = list_copy_tail(castNode(List, object), 1); if (!schema_does_not_exist_skipping(opfname, &msg, &name)) { msg = gettext_noop("operator family \"%s\" does not exist for access method \"%s\", skipping"); name = NameListToString(opfname); args = strVal(linitial(castNode(List, object))); } } break; case OBJECT_PUBLICATION: msg = gettext_noop("publication \"%s\" does not exist, skipping"); name = strVal((Value *) object); break; default: elog(ERROR, "unrecognized object type: %d", (int) objtype); break; } if (!args) ereport(NOTICE, (errmsg(msg, name))); else ereport(NOTICE, (errmsg(msg, name, args))); }