/* ------------------------------------------------------------------------- * * dropcmds.cpp * handle various "DROP" operations * * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd. * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * src/gausskernel/optimizer/commands/dropcmds.cpp * * ------------------------------------------------------------------------- */ #include "postgres.h" #include "knl/knl_variable.h" #include "catalog/indexing.h" #include "utils/fmgroids.h" #include "access/heapam.h" #include "catalog/dependency.h" #include "catalog/gs_db_privilege.h" #include "catalog/namespace.h" #include "catalog/objectaddress.h" #include "catalog/pg_class.h" #include "catalog/pg_proc.h" #include "commands/defrem.h" #include "commands/trigger.h" #include "gs_policy/gs_policy_masking.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "parser/parse_type.h" #include "storage/tcap.h" #include "utils/acl.h" #include "utils/snapmgr.h" #include "utils/inval.h" #include "utils/builtins.h" #include "utils/syscache.h" static void does_not_exist_skipping(ObjectType objtype, List* objname, List* objargs, bool missing_ok); static bool schema_does_not_exist_skipping(List *object, char **msg, char **name); static bool CheckObjectDropPrivilege(ObjectType removeType, Oid objectId, const Relation relation) { AclResult aclresult = ACLCHECK_NO_PRIV; bool anyresult = false; switch (removeType) { case OBJECT_FUNCTION: aclresult = pg_proc_aclcheck(objectId, GetUserId(), ACL_DROP); break; case OBJECT_PACKAGE: aclresult = pg_package_aclcheck(objectId, GetUserId(), ACL_DROP); break; case OBJECT_SCHEMA: aclresult = pg_namespace_aclcheck(objectId, GetUserId(), ACL_DROP); break; case OBJECT_TYPE: aclresult = pg_type_aclcheck(objectId, GetUserId(), ACL_DROP); break; case OBJECT_FOREIGN_SERVER: aclresult = pg_foreign_server_aclcheck(objectId, GetUserId(), ACL_DROP); break; case OBJECT_TRIGGER: if (!IsSysSchema(RelationGetNamespace(relation))) { anyresult = HasSpecAnyPriv(GetUserId(), DROP_ANY_TRIGGER, false); } break; default: break; } return ((aclresult == ACLCHECK_OK) ? true : false) || anyresult; } static void DropExtensionInListIsSupported(List* objname) { static const char *supportList[] = { "drop", "postgis", "packages", "ndpplugin", #ifndef ENABLE_MULTIPLE_NODES "mysql_fdw", "oracle_fdw", "postgres_fdw", "dblink", "security_plugin", "db_a_parser", "db_b_parser", "db_c_parser", "db_pg_parser", "hdfs_fdw", #endif }; int len = lengthof(supportList); const char* name = strVal(linitial(objname)); #if (!defined(ENABLE_MULTIPLE_NODES)) && (!defined(ENABLE_PRIVATEGAUSS)) static const char *unsupportList[] = { "dolphin" }; int len_unsupport = lengthof(unsupportList); for (int i = 0; i < len_unsupport; i++) { if (pg_strcasecmp(name, unsupportList[i]) == 0) { if (u_sess->attr.attr_common.IsInplaceUpgrade && t_thrd.proc->workingVersionNum < DOLPHIN_ENABLE_DROP_NUM) { return; } ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("EXTENSION is not yet supported."))); } } #endif for (int i = 0; i < len; i++) { if (pg_strcasecmp(name, supportList[i]) == 0) { return; } } if (pg_strcasecmp(name, "file_fdw") == 0 && !u_sess->attr.attr_common.IsInplaceUpgrade) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("EXTENSION file_fdw does not allow to drop."))); } /* Enable DROP operation of the above objects during inplace upgrade or support_extended_features is true */ if (!u_sess->attr.attr_common.IsInplaceUpgrade && !g_instance.attr.attr_common.support_extended_features) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("EXTENSION is not yet supported."))); } } /* * @Description: 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. * @in stmt : the info of dropstmt. * @in is_securityadmin : whether the is a security administrator doing this. * @return : nothing. */ void RemoveObjects(DropStmt* stmt, bool missing_ok, bool is_securityadmin) { ObjectAddresses* objects = NULL; ListCell* cell1 = NULL; bool skip_check = false; ListCell* cell2 = NULL; Oid pkgOid = InvalidOid; objects = new_object_addresses(); foreach (cell1, stmt->objects) { ObjectAddress address; List* objname = (List*)lfirst(cell1); List* objargs = NIL; Relation relation = NULL; Oid namespaceId; if (stmt->arguments) { cell2 = (!cell2 ? list_head(stmt->arguments) : lnext(cell2)); objargs = (List*)lfirst(cell2); } /* Get an ObjectAddress for the object. */ address = get_object_address(stmt->removeType, objname, objargs, &relation, AccessExclusiveLock, stmt->missing_ok); /* Issue NOTICE if supplied object was not found. */ if (!OidIsValid(address.objectId)) { if (u_sess->attr.attr_common.xc_maintenance_mode) missing_ok = stmt->missing_ok; does_not_exist_skipping(stmt->removeType, objname, objargs, missing_ok); continue; } else if (stmt->removeType == OBJECT_EXTENSION) { DropExtensionInListIsSupported(objname); } TrForbidAccessRbObject(address.classId, address.objectId); /* * 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) { Oid funcOid = address.objectId; if (IsMaskingFunctionOid(funcOid) && !u_sess->attr.attr_common.IsInplaceUpgrade) { ereport(ERROR, (errmodule(MOD_FUNCTION), errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("function \"%s\" is a masking function, it can not be droped", NameListToString(objname)), errdetail("cannot drop masking function"))); } HeapTuple tup; bool isnull = false; tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid)); if (tup == NULL) { ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("function not found by oid:%u", address.objectId), errhint("system error"))); } Datum packageOidDatum = SysCacheGetAttr(PROCOID, tup, Anum_pg_proc_packageid, &isnull); if (!isnull) { Oid pkgFuncOid = DatumGetObjectId(packageOidDatum); if (OidIsValid(pkgFuncOid)) { ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is a function in package", NameListToString(objname)), errhint("Use DROP PACKAGE."))); } } if (!HeapTupleIsValid(tup)) /* should not happen */ ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for function %u", funcOid))); if (((Form_pg_proc)GETSTRUCT(tup))->proisagg) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is an aggregate function", NameListToString(objname)), errhint("Use DROP AGGREGATE to drop aggregate functions."))); CacheInvalidateFunction(funcOid, InvalidOid); /* send invalid message for for relation holding replaced function as trigger */ InvalidRelcacheForTriggerFunction(funcOid, ((Form_pg_proc)GETSTRUCT(tup))->prorettype); ReleaseSysCache(tup); } /* For DROP PACKAGE */ if (stmt->removeType == OBJECT_PACKAGE) { pkgOid = address.objectId; HeapTuple oldtup; ScanKeyData entry; ScanKeyInit(&entry, Anum_pg_proc_packageid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(pkgOid)); Relation pg_proc_rel = heap_open(ProcedureRelationId, RowExclusiveLock); SysScanDesc scan = systable_beginscan(pg_proc_rel, InvalidOid, false, NULL, 1, &entry); /* * do we need job status not run, we can change the owner, * now only check if have the permission to change the owner, if after change, the running job may * failed in check permission */ while ((oldtup = systable_getnext(scan)) != NULL) { HeapTuple proctup = heap_copytuple(oldtup); Oid funcOid = InvalidOid; if (HeapTupleIsValid(proctup)) { funcOid = HeapTupleGetOid(proctup); if (!OidIsValid(funcOid)) { ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmodule(MOD_PLSQL), errmsg("cache lookup failed for relid %u", funcOid))); } } else { ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmodule(MOD_PLSQL), errmsg("cache lookup failed for relid %u", funcOid))); } heap_freetuple(proctup); } systable_endscan(scan); heap_close(pg_proc_rel, RowExclusiveLock); HeapTuple tup = SearchSysCache1(PACKAGEOID, ObjectIdGetDatum(pkgOid)); if (!HeapTupleIsValid(tup)) /* should not happen */ ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for package %u", pkgOid))); CacheInvalidateFunction(InvalidOid, pkgOid); ReleaseSysCache(tup); } #if (!defined(ENABLE_MULTIPLE_NODES)) && (!defined(ENABLE_PRIVATEGAUSS)) if (stmt->removeType == OBJECT_SCHEMA && u_sess->attr.attr_sql.dolphin && strcmp(strVal(linitial(objname)), "public") == 0) { ereport(ERROR, (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), errmsg("cannot drop schema public because dolphin depends on it"))); } #endif // @Temp Table. myTempNamespace and myTempToastNamespace's owner is // bootstrap user, so can not be deleted by ordinary user. to ensuer this two // schema be deleted on session quiting, we should bypass acl check when // drop my own temp namespace if (stmt->removeType == OBJECT_SCHEMA && (address.objectId == u_sess->catalog_cxt.myTempNamespace || address.objectId == u_sess->catalog_cxt.myTempToastNamespace)) skip_check = true; /* Check permissions. */ if (!skip_check) { skip_check = CheckObjectDropPrivilege(stmt->removeType, address.objectId, relation); } namespaceId = get_object_namespace(&address); if ((!is_securityadmin) && (!skip_check) && (!OidIsValid(namespaceId) || !pg_namespace_ownercheck(namespaceId, GetUserId()))) check_object_ownership(GetUserId(), stmt->removeType, address, objname, objargs, relation); /* Release any relcache reference count, but keep lock until commit. */ if (relation) heap_close(relation, NoLock); add_exact_object_address(&address, objects); } /* Here we really delete them. */ performMultipleDeletions(objects, stmt->behavior, 0); free_object_addresses(objects); } /* * 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, 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; } /* * 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() will throw an ERROR. */ static void does_not_exist_skipping(ObjectType objtype, List* objname, List* objargs, bool missing_ok) { char* msg = NULL; char* name = NULL; char* args = NULL; List* relname = NIL; StringInfo message = makeStringInfo(); switch (objtype) { case OBJECT_TYPE: case OBJECT_DOMAIN: { /*objname migth be a TypeName list or a String list, check its node type firstly*/ Node * ptype = (Node*) linitial(objname); TypeName *typ = NULL; if (ptype->type == T_String) typ = makeTypeNameFromNameList(objname); else if (ptype->type == T_TypeName) typ = (TypeName*)linitial(objname); else ereport(ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), (errmsg("unknown type: %d",(int)ptype->type)))); if (!schema_does_not_exist_skipping(typ->names, &msg, &name)) { msg = gettext_noop("type \"%s\" does not exist"); name = TypeNameToString(typ); } } break; case OBJECT_COLLATION: msg = gettext_noop("collation \"%s\" does not exist"); name = NameListToString(objname); break; case OBJECT_CONVERSION: msg = gettext_noop("conversion \"%s\" does not exist"); name = NameListToString(objname); break; case OBJECT_SCHEMA: msg = gettext_noop("schema \"%s\" does not exist"); name = NameListToString(objname); break; case OBJECT_TSPARSER: msg = gettext_noop("text search parser \"%s\" does not exist"); name = NameListToString(objname); break; case OBJECT_TSDICTIONARY: msg = gettext_noop("text search dictionary \"%s\" does not exist"); name = NameListToString(objname); break; case OBJECT_TSTEMPLATE: msg = gettext_noop("text search template \"%s\" does not exist"); name = NameListToString(objname); break; case OBJECT_TSCONFIGURATION: msg = gettext_noop("text search configuration \"%s\" does not exist"); name = NameListToString(objname); break; case OBJECT_EXTENSION: msg = gettext_noop("extension \"%s\" does not exist"); name = NameListToString(objname); break; case OBJECT_FUNCTION: msg = gettext_noop("function %s(%s) does not exist"); name = NameListToString(objname); args = TypeNameListToString(objargs); break; case OBJECT_PACKAGE: case OBJECT_PACKAGE_BODY: msg = gettext_noop("package %s(%s) does not exist"); name = NameListToString(objname); args = TypeNameListToString(objargs); break; case OBJECT_AGGREGATE: /* Given ordered set aggregate with no direct args, aggr_args variable is modified in gram.y. So the parse of aggr_args should be changed. See gram.y for detail. */ objargs = (List*)linitial(objargs); msg = gettext_noop("aggregate %s(%s) does not exist"); name = NameListToString(objname); args = TypeNameListToString(objargs); break; case OBJECT_OPERATOR: msg = gettext_noop("operator %s does not exist"); name = NameListToString(objname); break; case OBJECT_LANGUAGE: msg = gettext_noop("language \"%s\" does not exist"); name = NameListToString(objname); break; case OBJECT_CAST: msg = gettext_noop("cast from type %s to type %s does not exist"); name = format_type_be(typenameTypeId(NULL, (TypeName*)linitial(objname))); args = format_type_be(typenameTypeId(NULL, (TypeName*)linitial(objargs))); break; case OBJECT_TRIGGER: if (list_length(objname) == 1) { msg = gettext_noop("trigger \"%s\" does not exist"); name = NameListToString(list_make1(lfirst(list_tail((List*)lfirst(list_tail(objname)))))); break; } else { msg = gettext_noop("trigger \"%s\" for table \"%s\" does not exist"); relname = list_truncate(list_copy(objname), list_length(objname) - 1); args = NameListToString(relname); name = NameListToString(lappend(relname, lfirst(list_tail((List*)lfirst(list_tail(objname)))))); break; } case OBJECT_EVENT_TRIGGER: msg = gettext_noop("event trigger \"%s\" does not exist, skipping"); name = NameListToString(objname); break; case OBJECT_RULE: msg = gettext_noop("rule \"%s\" for relation \"%s\" does not exist"); name = strVal(llast(objname)); args = NameListToString(list_truncate(list_copy(objname), list_length(objname) - 1)); break; case OBJECT_FDW: msg = gettext_noop("foreign-data wrapper \"%s\" does not exist"); name = NameListToString(objname); break; case OBJECT_FOREIGN_SERVER: msg = gettext_noop("server \"%s\" does not exist"); name = NameListToString(objname); break; case OBJECT_OPCLASS: { List *opcname = list_copy_tail(objname, 1); if (!schema_does_not_exist_skipping(opcname, &msg, &name)) { msg = gettext_noop("operator class \"%s\" does not exist for access method \"%s\""); name = NameListToString(opcname); args = strVal(linitial(objname)); } list_free_ext(opcname); } break; case OBJECT_OPFAMILY: { List *opfname = list_copy_tail(objname, 1); if (!schema_does_not_exist_skipping(opfname, &msg, &name)) { msg = gettext_noop("operator family \"%s\" does not exist for access method \"%s\""); name = NameListToString(opfname); args = strVal(linitial(objname)); } list_free_ext(opfname); } break; case OBJECT_DATA_SOURCE: msg = gettext_noop("data source \"%s\" does not exist"); name = NameListToString(objname); break; case OBJECT_RLSPOLICY: msg = gettext_noop("row level security policy \"%s\" for relation \"%s\" does not exist"); name = pstrdup(strVal(llast(objname))); args = NameListToString(list_truncate(list_copy(objname), list_length(objname) - 1)); break; case OBJECT_PUBLICATION: msg = gettext_noop("publication \"%s\" does not exist, skipping"); name = NameListToString(objname); break; default: pfree_ext(message->data); pfree_ext(message); ereport( ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("unexpected object type (%d)", (int)objtype))); break; } if (missing_ok) { if (args == NULL) { appendStringInfo(message, msg, name); } else { appendStringInfo(message, msg, name, args); } appendStringInfo(message, ", skipping"); ereport(NOTICE, (errmsg("%s", message->data))); pfree_ext(message->data); pfree_ext(message); pfree_ext(name); } else { pfree_ext(message->data); pfree_ext(message); if (args == NULL) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg(msg, name))); else ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg(msg, name, args))); } }