| /*------------------------------------------------------------------------- |
| * |
| * dependency.c |
| * Routines to support inter-object dependencies. |
| * |
| * |
| * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * IDENTIFICATION |
| * src/backend/catalog/dependency.c |
| * |
| *------------------------------------------------------------------------- |
| */ |
| #include "postgres.h" |
| |
| #include "access/genam.h" |
| #include "access/htup_details.h" |
| #include "access/table.h" |
| #include "access/xact.h" |
| #include "catalog/catalog.h" |
| #include "catalog/dependency.h" |
| #include "catalog/gp_storage_server.h" |
| #include "catalog/gp_storage_user_mapping.h" |
| #include "catalog/heap.h" |
| #include "catalog/index.h" |
| #include "catalog/namespace.h" |
| #include "catalog/objectaccess.h" |
| #include "catalog/pg_am.h" |
| #include "catalog/pg_amop.h" |
| #include "catalog/pg_amproc.h" |
| #include "catalog/pg_attrdef.h" |
| #include "catalog/pg_authid.h" |
| #include "catalog/pg_auth_members.h" |
| #include "catalog/pg_cast.h" |
| #include "catalog/pg_collation.h" |
| #include "catalog/pg_constraint.h" |
| #include "catalog/pg_conversion.h" |
| #include "catalog/pg_database.h" |
| #include "catalog/pg_default_acl.h" |
| #include "catalog/pg_depend.h" |
| #include "catalog/pg_directory_table.h" |
| #include "catalog/pg_event_trigger.h" |
| #include "catalog/pg_extension.h" |
| #include "catalog/pg_foreign_data_wrapper.h" |
| #include "catalog/pg_foreign_server.h" |
| #include "catalog/pg_init_privs.h" |
| #include "catalog/pg_language.h" |
| #include "catalog/pg_largeobject.h" |
| #include "catalog/pg_namespace.h" |
| #include "catalog/pg_opclass.h" |
| #include "catalog/pg_operator.h" |
| #include "catalog/pg_opfamily.h" |
| #include "catalog/pg_parameter_acl.h" |
| #include "catalog/pg_policy.h" |
| #include "catalog/pg_proc.h" |
| #include "catalog/pg_publication.h" |
| #include "catalog/pg_publication_namespace.h" |
| #include "catalog/pg_publication_rel.h" |
| #include "catalog/pg_rewrite.h" |
| #include "catalog/pg_statistic_ext.h" |
| #include "catalog/pg_subscription.h" |
| #include "catalog/pg_tablespace.h" |
| #include "catalog/pg_tag.h" |
| #include "catalog/pg_tag_description.h" |
| #include "catalog/pg_task.h" |
| #include "catalog/pg_transform.h" |
| #include "catalog/pg_trigger.h" |
| #include "catalog/pg_ts_config.h" |
| #include "catalog/pg_ts_dict.h" |
| #include "catalog/pg_ts_parser.h" |
| #include "catalog/pg_ts_template.h" |
| #include "catalog/pg_type.h" |
| #include "catalog/pg_type_encoding.h" |
| #include "catalog/pg_user_mapping.h" |
| #include "catalog/storage_directory_table.h" |
| #include "commands/comment.h" |
| #include "commands/dbcommands.h" |
| #include "commands/defrem.h" |
| #include "commands/event_trigger.h" |
| #include "commands/extension.h" |
| #include "commands/policy.h" |
| #include "commands/publicationcmds.h" |
| #include "commands/schemacmds.h" |
| #include "commands/seclabel.h" |
| #include "commands/sequence.h" |
| #include "commands/tag.h" |
| #include "commands/taskcmds.h" |
| #include "commands/trigger.h" |
| #include "commands/typecmds.h" |
| #include "foreign/foreign.h" |
| #include "funcapi.h" |
| #include "miscadmin.h" |
| #include "nodes/nodeFuncs.h" |
| #include "parser/parsetree.h" |
| #include "rewrite/rewriteRemove.h" |
| #include "storage/lmgr.h" |
| #include "utils/acl.h" |
| #include "utils/fmgroids.h" |
| #include "utils/guc.h" |
| #include "utils/lsyscache.h" |
| #include "utils/syscache.h" |
| |
| #include "catalog/pg_compression.h" |
| #include "catalog/pg_extprotocol.h" |
| #include "commands/tablespace.h" |
| #include "cdb/cdbvars.h" |
| #include "commands/extprotocolcmds.h" |
| #include "catalog/pg_profile.h" |
| #include "catalog/pg_password_history.h" |
| #include "commands/tablecmds.h" |
| #include "catalog/gp_matview_aux.h" |
| |
| |
| /* |
| * Deletion processing requires additional state for each ObjectAddress that |
| * it's planning to delete. For simplicity and code-sharing we make the |
| * ObjectAddresses code support arrays with or without this extra state. |
| */ |
| typedef struct |
| { |
| int flags; /* bitmask, see bit definitions below */ |
| ObjectAddress dependee; /* object whose deletion forced this one */ |
| } ObjectAddressExtra; |
| |
| /* ObjectAddressExtra flag bits */ |
| #define DEPFLAG_ORIGINAL 0x0001 /* an original deletion target */ |
| #define DEPFLAG_NORMAL 0x0002 /* reached via normal dependency */ |
| #define DEPFLAG_AUTO 0x0004 /* reached via auto dependency */ |
| #define DEPFLAG_INTERNAL 0x0008 /* reached via internal dependency */ |
| #define DEPFLAG_PARTITION 0x0010 /* reached via partition dependency */ |
| #define DEPFLAG_EXTENSION 0x0020 /* reached via extension dependency */ |
| #define DEPFLAG_REVERSE 0x0040 /* reverse internal/extension link */ |
| #define DEPFLAG_IS_PART 0x0080 /* has a partition dependency */ |
| #define DEPFLAG_SUBOBJECT 0x0100 /* subobject of another deletable object */ |
| |
| |
| /* expansible list of ObjectAddresses */ |
| struct ObjectAddresses |
| { |
| ObjectAddress *refs; /* => palloc'd array */ |
| ObjectAddressExtra *extras; /* => palloc'd array, or NULL if not used */ |
| int numrefs; /* current number of references */ |
| int maxrefs; /* current size of palloc'd array(s) */ |
| }; |
| |
| /* typedef ObjectAddresses appears in dependency.h */ |
| |
| /* threaded list of ObjectAddresses, for recursion detection */ |
| typedef struct ObjectAddressStack |
| { |
| const ObjectAddress *object; /* object being visited */ |
| int flags; /* its current flag bits */ |
| struct ObjectAddressStack *next; /* next outer stack level */ |
| } ObjectAddressStack; |
| |
| /* temporary storage in findDependentObjects */ |
| typedef struct |
| { |
| ObjectAddress obj; /* object to be deleted --- MUST BE FIRST */ |
| int subflags; /* flags to pass down when recursing to obj */ |
| } ObjectAddressAndFlags; |
| |
| /* for find_expr_references_walker */ |
| typedef struct |
| { |
| ObjectAddresses *addrs; /* addresses being accumulated */ |
| List *rtables; /* list of rangetables to resolve Vars */ |
| } find_expr_references_context; |
| |
| /* |
| * This constant table maps ObjectClasses to the corresponding catalog OIDs. |
| * See also getObjectClass(). |
| */ |
| static const Oid object_classes[] = { |
| RelationRelationId, /* OCLASS_CLASS */ |
| ProcedureRelationId, /* OCLASS_PROC */ |
| TypeRelationId, /* OCLASS_TYPE */ |
| CastRelationId, /* OCLASS_CAST */ |
| CollationRelationId, /* OCLASS_COLLATION */ |
| ConstraintRelationId, /* OCLASS_CONSTRAINT */ |
| ConversionRelationId, /* OCLASS_CONVERSION */ |
| AttrDefaultRelationId, /* OCLASS_DEFAULT */ |
| LanguageRelationId, /* OCLASS_LANGUAGE */ |
| LargeObjectRelationId, /* OCLASS_LARGEOBJECT */ |
| OperatorRelationId, /* OCLASS_OPERATOR */ |
| OperatorClassRelationId, /* OCLASS_OPCLASS */ |
| OperatorFamilyRelationId, /* OCLASS_OPFAMILY */ |
| AccessMethodRelationId, /* OCLASS_AM */ |
| AccessMethodOperatorRelationId, /* OCLASS_AMOP */ |
| AccessMethodProcedureRelationId, /* OCLASS_AMPROC */ |
| RewriteRelationId, /* OCLASS_REWRITE */ |
| TriggerRelationId, /* OCLASS_TRIGGER */ |
| NamespaceRelationId, /* OCLASS_SCHEMA */ |
| StatisticExtRelationId, /* OCLASS_STATISTIC_EXT */ |
| TSParserRelationId, /* OCLASS_TSPARSER */ |
| TSDictionaryRelationId, /* OCLASS_TSDICT */ |
| TSTemplateRelationId, /* OCLASS_TSTEMPLATE */ |
| TSConfigRelationId, /* OCLASS_TSCONFIG */ |
| AuthIdRelationId, /* OCLASS_ROLE */ |
| AuthMemRelationId, /* OCLASS_ROLE_MEMBERSHIP */ |
| DatabaseRelationId, /* OCLASS_DATABASE */ |
| TableSpaceRelationId, /* OCLASS_TBLSPACE */ |
| ForeignDataWrapperRelationId, /* OCLASS_FDW */ |
| ForeignServerRelationId, /* OCLASS_FOREIGN_SERVER */ |
| UserMappingRelationId, /* OCLASS_USER_MAPPING */ |
| DefaultAclRelationId, /* OCLASS_DEFACL */ |
| ExtensionRelationId, /* OCLASS_EXTENSION */ |
| EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */ |
| ParameterAclRelationId, /* OCLASS_PARAMETER_ACL */ |
| PolicyRelationId, /* OCLASS_POLICY */ |
| PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */ |
| PublicationRelationId, /* OCLASS_PUBLICATION */ |
| PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */ |
| SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */ |
| TransformRelationId, /* OCLASS_TRANSFORM */ |
| |
| /* GPDB additions */ |
| ProfileRelationId, /* OCLASS_PROFILE */ |
| PasswordHistoryRelationId, /* OCLASS_PASSWORDHISTORY */ |
| DirectoryTableRelationId, /* OCLASS_DIRECTORY_TABLE */ |
| StorageServerRelationId, /* OCLASS_STORAGE_SERVER */ |
| StorageUserMappingRelationId, /* OCLASS_STORAGE_USER_MAPPING */ |
| TagRelationId, /* OCLASS_TAG */ |
| TagDescriptionRelationId, /* OCLASS_TAG_DESCRIPTION */ |
| ExtprotocolRelationId, /* OCLASS_EXTPROTOCOL */ |
| GpMatviewAuxId, /* OCLASS_MATVIEW_AUX */ |
| TaskRelationId, /* OCLASS_TASK */ |
| }; |
| |
| /* |
| * Make sure object_classes is kept up to date with the ObjectClass enum. |
| */ |
| StaticAssertDecl(lengthof(object_classes) == LAST_OCLASS + 1, |
| "object_classes[] must cover all ObjectClasses"); |
| |
| |
| static void findDependentObjects(const ObjectAddress *object, |
| int objflags, |
| int flags, |
| ObjectAddressStack *stack, |
| ObjectAddresses *targetObjects, |
| const ObjectAddresses *pendingObjects, |
| Relation *depRel); |
| static void reportDependentObjects(const ObjectAddresses *targetObjects, |
| DropBehavior behavior, |
| int flags, |
| const ObjectAddress *origObject); |
| static void deleteOneObject(const ObjectAddress *object, |
| Relation *depRel, int32 flags); |
| static void doDeletion(const ObjectAddress *object, int flags); |
| static bool find_expr_references_walker(Node *node, |
| find_expr_references_context *context); |
| static void process_function_rte_ref(RangeTblEntry *rte, AttrNumber attnum, |
| find_expr_references_context *context); |
| static void eliminate_duplicate_dependencies(ObjectAddresses *addrs); |
| static int object_address_comparator(const void *a, const void *b); |
| static void add_object_address(ObjectClass oclass, Oid objectId, int32 subId, |
| ObjectAddresses *addrs); |
| static void add_exact_object_address_extra(const ObjectAddress *object, |
| const ObjectAddressExtra *extra, |
| ObjectAddresses *addrs); |
| static bool object_address_present_add_flags(const ObjectAddress *object, |
| int flags, |
| ObjectAddresses *addrs); |
| static bool stack_address_present_add_flags(const ObjectAddress *object, |
| int flags, |
| ObjectAddressStack *stack); |
| static void DeleteInitPrivs(const ObjectAddress *object); |
| |
| |
| /* |
| * Go through the objects given running the final actions on them, and execute |
| * the actual deletion. |
| */ |
| static void |
| deleteObjectsInList(ObjectAddresses *targetObjects, Relation *depRel, |
| int flags) |
| { |
| int i; |
| |
| /* |
| * Keep track of objects for event triggers, if necessary. |
| */ |
| if (trackDroppedObjectsNeeded() && !(flags & PERFORM_DELETION_INTERNAL)) |
| { |
| for (i = 0; i < targetObjects->numrefs; i++) |
| { |
| const ObjectAddress *thisobj = &targetObjects->refs[i]; |
| const ObjectAddressExtra *extra = &targetObjects->extras[i]; |
| bool original = false; |
| bool normal = false; |
| |
| if (extra->flags & DEPFLAG_ORIGINAL) |
| original = true; |
| if (extra->flags & DEPFLAG_NORMAL) |
| normal = true; |
| if (extra->flags & DEPFLAG_REVERSE) |
| normal = true; |
| |
| if (EventTriggerSupportsObjectClass(getObjectClass(thisobj))) |
| { |
| EventTriggerSQLDropAddObject(thisobj, original, normal); |
| } |
| } |
| } |
| |
| /* |
| * Delete all the objects in the proper order, except that if told to, we |
| * should skip the original object(s). |
| */ |
| for (i = 0; i < targetObjects->numrefs; i++) |
| { |
| ObjectAddress *thisobj = targetObjects->refs + i; |
| ObjectAddressExtra *thisextra = targetObjects->extras + i; |
| |
| if ((flags & PERFORM_DELETION_SKIP_ORIGINAL) && |
| (thisextra->flags & DEPFLAG_ORIGINAL)) |
| continue; |
| |
| deleteOneObject(thisobj, depRel, flags); |
| } |
| } |
| |
| /* |
| * performDeletion: attempt to drop the specified object. If CASCADE |
| * behavior is specified, also drop any dependent objects (recursively). |
| * If RESTRICT behavior is specified, error out if there are any dependent |
| * objects, except for those that should be implicitly dropped anyway |
| * according to the dependency type. |
| * |
| * This is the outer control routine for all forms of DROP that drop objects |
| * that can participate in dependencies. Note that performMultipleDeletions |
| * is a variant on the same theme; if you change anything here you'll likely |
| * need to fix that too. |
| * |
| * Bits in the flags argument can include: |
| * |
| * PERFORM_DELETION_INTERNAL: indicates that the drop operation is not the |
| * direct result of a user-initiated action. For example, when a temporary |
| * schema is cleaned out so that a new backend can use it, or when a column |
| * default is dropped as an intermediate step while adding a new one, that's |
| * an internal operation. On the other hand, when we drop something because |
| * the user issued a DROP statement against it, that's not internal. Currently |
| * this suppresses calling event triggers and making some permissions checks. |
| * |
| * PERFORM_DELETION_CONCURRENTLY: perform the drop concurrently. This does |
| * not currently work for anything except dropping indexes; don't set it for |
| * other object types or you may get strange results. |
| * |
| * PERFORM_DELETION_QUIETLY: reduce message level from NOTICE to DEBUG2. |
| * |
| * PERFORM_DELETION_SKIP_ORIGINAL: do not delete the specified object(s), |
| * but only what depends on it/them. |
| * |
| * PERFORM_DELETION_SKIP_EXTENSIONS: do not delete extensions, even when |
| * deleting objects that are part of an extension. This should generally |
| * be used only when dropping temporary objects. |
| * |
| * PERFORM_DELETION_CONCURRENT_LOCK: perform the drop normally but with a lock |
| * as if it were concurrent. This is used by REINDEX CONCURRENTLY. |
| * |
| */ |
| void |
| performDeletion(const ObjectAddress *object, |
| DropBehavior behavior, int flags) |
| { |
| Relation depRel; |
| ObjectAddresses *targetObjects; |
| |
| /* |
| * We save some cycles by opening pg_depend just once and passing the |
| * Relation pointer down to all the recursive deletion steps. |
| */ |
| depRel = table_open(DependRelationId, RowExclusiveLock); |
| |
| /* |
| * Acquire deletion lock on the target object. (Ideally the caller has |
| * done this already, but many places are sloppy about it.) |
| */ |
| AcquireDeletionLock(object, 0); |
| |
| /* |
| * Construct a list of objects to delete (ie, the given object plus |
| * everything directly or indirectly dependent on it). |
| */ |
| targetObjects = new_object_addresses(); |
| |
| findDependentObjects(object, |
| DEPFLAG_ORIGINAL, |
| flags, |
| NULL, /* empty stack */ |
| targetObjects, |
| NULL, /* no pendingObjects */ |
| &depRel); |
| |
| /* |
| * Check if deletion is allowed, and report about cascaded deletes. |
| */ |
| reportDependentObjects(targetObjects, |
| behavior, |
| flags, |
| object); |
| |
| /* do the deed */ |
| deleteObjectsInList(targetObjects, &depRel, flags); |
| |
| /* And clean up */ |
| free_object_addresses(targetObjects); |
| |
| table_close(depRel, RowExclusiveLock); |
| } |
| |
| /* |
| * performMultipleDeletions: Similar to performDeletion, but act on multiple |
| * objects at once. |
| * |
| * The main difference from issuing multiple performDeletion calls is that the |
| * list of objects that would be implicitly dropped, for each object to be |
| * dropped, is the union of the implicit-object list for all objects. This |
| * makes each check be more relaxed. |
| */ |
| void |
| performMultipleDeletions(const ObjectAddresses *objects, |
| DropBehavior behavior, int flags) |
| { |
| Relation depRel; |
| ObjectAddresses *targetObjects; |
| int i; |
| |
| /* No work if no objects... */ |
| if (objects->numrefs <= 0) |
| return; |
| |
| /* |
| * We save some cycles by opening pg_depend just once and passing the |
| * Relation pointer down to all the recursive deletion steps. |
| */ |
| depRel = table_open(DependRelationId, RowExclusiveLock); |
| |
| /* |
| * Construct a list of objects to delete (ie, the given objects plus |
| * everything directly or indirectly dependent on them). Note that |
| * because we pass the whole objects list as pendingObjects context, we |
| * won't get a failure from trying to delete an object that is internally |
| * dependent on another one in the list; we'll just skip that object and |
| * delete it when we reach its owner. |
| */ |
| targetObjects = new_object_addresses(); |
| |
| for (i = 0; i < objects->numrefs; i++) |
| { |
| const ObjectAddress *thisobj = objects->refs + i; |
| |
| /* |
| * Acquire deletion lock on each target object. (Ideally the caller |
| * has done this already, but many places are sloppy about it.) |
| */ |
| AcquireDeletionLock(thisobj, flags); |
| |
| findDependentObjects(thisobj, |
| DEPFLAG_ORIGINAL, |
| flags, |
| NULL, /* empty stack */ |
| targetObjects, |
| objects, |
| &depRel); |
| } |
| |
| /* |
| * Check if deletion is allowed, and report about cascaded deletes. |
| * |
| * If there's exactly one object being deleted, report it the same way as |
| * in performDeletion(), else we have to be vaguer. |
| */ |
| reportDependentObjects(targetObjects, |
| behavior, |
| flags, |
| (objects->numrefs == 1 ? objects->refs : NULL)); |
| |
| /* do the deed */ |
| deleteObjectsInList(targetObjects, &depRel, flags); |
| |
| /* And clean up */ |
| free_object_addresses(targetObjects); |
| |
| table_close(depRel, RowExclusiveLock); |
| } |
| |
| /* |
| * findDependentObjects - find all objects that depend on 'object' |
| * |
| * For every object that depends on the starting object, acquire a deletion |
| * lock on the object, add it to targetObjects (if not already there), |
| * and recursively find objects that depend on it. An object's dependencies |
| * will be placed into targetObjects before the object itself; this means |
| * that the finished list's order represents a safe deletion order. |
| * |
| * The caller must already have a deletion lock on 'object' itself, |
| * but must not have added it to targetObjects. (Note: there are corner |
| * cases where we won't add the object either, and will also release the |
| * caller-taken lock. This is a bit ugly, but the API is set up this way |
| * to allow easy rechecking of an object's liveness after we lock it. See |
| * notes within the function.) |
| * |
| * When dropping a whole object (subId = 0), we find dependencies for |
| * its sub-objects too. |
| * |
| * object: the object to add to targetObjects and find dependencies on |
| * objflags: flags to be ORed into the object's targetObjects entry |
| * flags: PERFORM_DELETION_xxx flags for the deletion operation as a whole |
| * stack: list of objects being visited in current recursion; topmost item |
| * is the object that we recursed from (NULL for external callers) |
| * targetObjects: list of objects that are scheduled to be deleted |
| * pendingObjects: list of other objects slated for destruction, but |
| * not necessarily in targetObjects yet (can be NULL if none) |
| * *depRel: already opened pg_depend relation |
| * |
| * Note: objflags describes the reason for visiting this particular object |
| * at this time, and is not passed down when recursing. The flags argument |
| * is passed down, since it describes what we're doing overall. |
| */ |
| static void |
| findDependentObjects(const ObjectAddress *object, |
| int objflags, |
| int flags, |
| ObjectAddressStack *stack, |
| ObjectAddresses *targetObjects, |
| const ObjectAddresses *pendingObjects, |
| Relation *depRel) |
| { |
| ScanKeyData key[3]; |
| int nkeys; |
| SysScanDesc scan; |
| HeapTuple tup; |
| ObjectAddress otherObject; |
| ObjectAddress owningObject; |
| ObjectAddress partitionObject; |
| ObjectAddressAndFlags *dependentObjects; |
| int numDependentObjects; |
| int maxDependentObjects; |
| ObjectAddressStack mystack; |
| ObjectAddressExtra extra; |
| |
| /* |
| * If the target object is already being visited in an outer recursion |
| * level, just report the current objflags back to that level and exit. |
| * This is needed to avoid infinite recursion in the face of circular |
| * dependencies. |
| * |
| * The stack check alone would result in dependency loops being broken at |
| * an arbitrary point, ie, the first member object of the loop to be |
| * visited is the last one to be deleted. This is obviously unworkable. |
| * However, the check for internal dependency below guarantees that we |
| * will not break a loop at an internal dependency: if we enter the loop |
| * at an "owned" object we will switch and start at the "owning" object |
| * instead. We could probably hack something up to avoid breaking at an |
| * auto dependency, too, if we had to. However there are no known cases |
| * where that would be necessary. |
| */ |
| if (stack_address_present_add_flags(object, objflags, stack)) |
| return; |
| |
| /* |
| * since this function recurses, it could be driven to stack overflow, |
| * because of the deep dependency tree, not only due to dependency loops. |
| */ |
| check_stack_depth(); |
| |
| /* |
| * It's also possible that the target object has already been completely |
| * processed and put into targetObjects. If so, again we just add the |
| * specified objflags to its entry and return. |
| * |
| * (Note: in these early-exit cases we could release the caller-taken |
| * lock, since the object is presumably now locked multiple times; but it |
| * seems not worth the cycles.) |
| */ |
| if (object_address_present_add_flags(object, objflags, targetObjects)) |
| return; |
| |
| /* |
| * If the target object is pinned, we can just error out immediately; it |
| * won't have any objects recorded as depending on it. |
| */ |
| if (IsPinnedObject(object->classId, object->objectId)) |
| ereport(ERROR, |
| (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), |
| errmsg("cannot drop %s because it is required by the database system", |
| getObjectDescription(object, false)))); |
| |
| /* |
| * The target object might be internally dependent on some other object |
| * (its "owner"), and/or be a member of an extension (also considered its |
| * owner). If so, and if we aren't recursing from the owning object, we |
| * have to transform this deletion request into a deletion request of the |
| * owning object. (We'll eventually recurse back to this object, but the |
| * owning object has to be visited first so it will be deleted after.) The |
| * way to find out about this is to scan the pg_depend entries that show |
| * what this object depends on. |
| */ |
| |
| /* |
| * Step 1: find and remove pg_depend records that link from this object to |
| * others. We have to do this anyway, and doing it first ensures that we |
| * avoid infinite recursion in the case of cycles. Also, some dependency |
| * types require extra processing here. |
| * |
| * When dropping a whole object (subId = 0), remove all pg_depend records |
| * for its sub-objects too. |
| */ |
| ScanKeyInit(&key[0], |
| Anum_pg_depend_classid, |
| BTEqualStrategyNumber, F_OIDEQ, |
| ObjectIdGetDatum(object->classId)); |
| ScanKeyInit(&key[1], |
| Anum_pg_depend_objid, |
| BTEqualStrategyNumber, F_OIDEQ, |
| ObjectIdGetDatum(object->objectId)); |
| if (object->objectSubId != 0) |
| { |
| /* Consider only dependencies of this sub-object */ |
| ScanKeyInit(&key[2], |
| Anum_pg_depend_objsubid, |
| BTEqualStrategyNumber, F_INT4EQ, |
| Int32GetDatum(object->objectSubId)); |
| nkeys = 3; |
| } |
| else |
| { |
| /* Consider dependencies of this object and any sub-objects it has */ |
| nkeys = 2; |
| } |
| |
| scan = systable_beginscan(*depRel, DependDependerIndexId, true, |
| NULL, nkeys, key); |
| |
| /* initialize variables that loop may fill */ |
| memset(&owningObject, 0, sizeof(owningObject)); |
| memset(&partitionObject, 0, sizeof(partitionObject)); |
| |
| while (HeapTupleIsValid(tup = systable_getnext(scan))) |
| { |
| Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup); |
| |
| otherObject.classId = foundDep->refclassid; |
| otherObject.objectId = foundDep->refobjid; |
| otherObject.objectSubId = foundDep->refobjsubid; |
| |
| /* |
| * When scanning dependencies of a whole object, we may find rows |
| * linking sub-objects of the object to the object itself. (Normally, |
| * such a dependency is implicit, but we must make explicit ones in |
| * some cases involving partitioning.) We must ignore such rows to |
| * avoid infinite recursion. |
| */ |
| if (otherObject.classId == object->classId && |
| otherObject.objectId == object->objectId && |
| object->objectSubId == 0) |
| continue; |
| |
| switch (foundDep->deptype) |
| { |
| case DEPENDENCY_NORMAL: |
| case DEPENDENCY_AUTO: |
| case DEPENDENCY_AUTO_EXTENSION: |
| /* no problem */ |
| break; |
| |
| case DEPENDENCY_EXTENSION: |
| |
| /* |
| * If told to, ignore EXTENSION dependencies altogether. This |
| * flag is normally used to prevent dropping extensions during |
| * temporary-object cleanup, even if a temp object was created |
| * during an extension script. |
| */ |
| if (flags & PERFORM_DELETION_SKIP_EXTENSIONS) |
| break; |
| |
| /* |
| * If the other object is the extension currently being |
| * created/altered, ignore this dependency and continue with |
| * the deletion. This allows dropping of an extension's |
| * objects within the extension's scripts, as well as corner |
| * cases such as dropping a transient object created within |
| * such a script. |
| */ |
| if (creating_extension && |
| otherObject.classId == ExtensionRelationId && |
| otherObject.objectId == CurrentExtensionObject) |
| break; |
| |
| /* Otherwise, treat this like an internal dependency */ |
| /* FALL THRU */ |
| |
| case DEPENDENCY_INTERNAL: |
| |
| /* |
| * This object is part of the internal implementation of |
| * another object, or is part of the extension that is the |
| * other object. We have three cases: |
| * |
| * 1. At the outermost recursion level, we must disallow the |
| * DROP. However, if the owning object is listed in |
| * pendingObjects, just release the caller's lock and return; |
| * we'll eventually complete the DROP when we reach that entry |
| * in the pending list. |
| * |
| * Note: the above statement is true only if this pg_depend |
| * entry still exists by then; in principle, therefore, we |
| * could miss deleting an item the user told us to delete. |
| * However, no inconsistency can result: since we're at outer |
| * level, there is no object depending on this one. |
| */ |
| if (stack == NULL) |
| { |
| if (pendingObjects && |
| object_address_present(&otherObject, pendingObjects)) |
| { |
| systable_endscan(scan); |
| /* need to release caller's lock; see notes below */ |
| ReleaseDeletionLock(object); |
| return; |
| } |
| |
| /* |
| * We postpone actually issuing the error message until |
| * after this loop, so that we can make the behavior |
| * independent of the ordering of pg_depend entries, at |
| * least if there's not more than one INTERNAL and one |
| * EXTENSION dependency. (If there's more, we'll complain |
| * about a random one of them.) Prefer to complain about |
| * EXTENSION, since that's generally a more important |
| * dependency. |
| */ |
| if (!OidIsValid(owningObject.classId) || |
| foundDep->deptype == DEPENDENCY_EXTENSION) |
| owningObject = otherObject; |
| break; |
| } |
| |
| /* |
| * 2. When recursing from the other end of this dependency, |
| * it's okay to continue with the deletion. This holds when |
| * recursing from a whole object that includes the nominal |
| * other end as a component, too. Since there can be more |
| * than one "owning" object, we have to allow matches that are |
| * more than one level down in the stack. |
| */ |
| if (stack_address_present_add_flags(&otherObject, 0, stack)) |
| break; |
| |
| /* |
| * 3. Not all the owning objects have been visited, so |
| * transform this deletion request into a delete of this |
| * owning object. |
| * |
| * First, release caller's lock on this object and get |
| * deletion lock on the owning object. (We must release |
| * caller's lock to avoid deadlock against a concurrent |
| * deletion of the owning object.) |
| */ |
| ReleaseDeletionLock(object); |
| AcquireDeletionLock(&otherObject, 0); |
| |
| /* |
| * The owning object might have been deleted while we waited |
| * to lock it; if so, neither it nor the current object are |
| * interesting anymore. We test this by checking the |
| * pg_depend entry (see notes below). |
| */ |
| if (!systable_recheck_tuple(scan, tup)) |
| { |
| systable_endscan(scan); |
| ReleaseDeletionLock(&otherObject); |
| return; |
| } |
| |
| /* |
| * One way or the other, we're done with the scan; might as |
| * well close it down before recursing, to reduce peak |
| * resource consumption. |
| */ |
| systable_endscan(scan); |
| |
| /* |
| * Okay, recurse to the owning object instead of proceeding. |
| * |
| * We do not need to stack the current object; we want the |
| * traversal order to be as if the original reference had |
| * linked to the owning object instead of this one. |
| * |
| * The dependency type is a "reverse" dependency: we need to |
| * delete the owning object if this one is to be deleted, but |
| * this linkage is never a reason for an automatic deletion. |
| */ |
| findDependentObjects(&otherObject, |
| DEPFLAG_REVERSE, |
| flags, |
| stack, |
| targetObjects, |
| pendingObjects, |
| depRel); |
| |
| /* |
| * The current target object should have been added to |
| * targetObjects while processing the owning object; but it |
| * probably got only the flag bits associated with the |
| * dependency we're looking at. We need to add the objflags |
| * that were passed to this recursion level, too, else we may |
| * get a bogus failure in reportDependentObjects (if, for |
| * example, we were called due to a partition dependency). |
| * |
| * If somehow the current object didn't get scheduled for |
| * deletion, bleat. (That would imply that somebody deleted |
| * this dependency record before the recursion got to it.) |
| * Another idea would be to reacquire lock on the current |
| * object and resume trying to delete it, but it seems not |
| * worth dealing with the race conditions inherent in that. |
| */ |
| if (!object_address_present_add_flags(object, objflags, |
| targetObjects)) |
| elog(ERROR, "deletion of owning object %s failed to delete %s", |
| getObjectDescription(&otherObject, false), |
| getObjectDescription(object, false)); |
| |
| /* And we're done here. */ |
| return; |
| |
| case DEPENDENCY_PARTITION_PRI: |
| |
| /* |
| * Remember that this object has a partition-type dependency. |
| * After the dependency scan, we'll complain if we didn't find |
| * a reason to delete one of its partition dependencies. |
| */ |
| objflags |= DEPFLAG_IS_PART; |
| |
| /* |
| * Also remember the primary partition owner, for error |
| * messages. If there are multiple primary owners (which |
| * there should not be), we'll report a random one of them. |
| */ |
| partitionObject = otherObject; |
| break; |
| |
| case DEPENDENCY_PARTITION_SEC: |
| |
| /* |
| * Only use secondary partition owners in error messages if we |
| * find no primary owner (which probably shouldn't happen). |
| */ |
| if (!(objflags & DEPFLAG_IS_PART)) |
| partitionObject = otherObject; |
| |
| /* |
| * Remember that this object has a partition-type dependency. |
| * After the dependency scan, we'll complain if we didn't find |
| * a reason to delete one of its partition dependencies. |
| */ |
| objflags |= DEPFLAG_IS_PART; |
| break; |
| |
| default: |
| elog(ERROR, "unrecognized dependency type '%c' for %s", |
| foundDep->deptype, getObjectDescription(object, false)); |
| break; |
| } |
| } |
| |
| systable_endscan(scan); |
| |
| /* |
| * If we found an INTERNAL or EXTENSION dependency when we're at outer |
| * level, complain about it now. If we also found a PARTITION dependency, |
| * we prefer to report the PARTITION dependency. This is arbitrary but |
| * seems to be more useful in practice. |
| */ |
| if (OidIsValid(owningObject.classId)) |
| { |
| char *otherObjDesc; |
| |
| if (OidIsValid(partitionObject.classId)) |
| otherObjDesc = getObjectDescription(&partitionObject, false); |
| else |
| otherObjDesc = getObjectDescription(&owningObject, false); |
| |
| ereport(ERROR, |
| (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), |
| errmsg("cannot drop %s because %s requires it", |
| getObjectDescription(object, false), otherObjDesc), |
| errhint("You can drop %s instead.", otherObjDesc))); |
| } |
| |
| /* |
| * Next, identify all objects that directly depend on the current object. |
| * To ensure predictable deletion order, we collect them up in |
| * dependentObjects and sort the list before actually recursing. (The |
| * deletion order would be valid in any case, but doing this ensures |
| * consistent output from DROP CASCADE commands, which is helpful for |
| * regression testing.) |
| */ |
| maxDependentObjects = 128; /* arbitrary initial allocation */ |
| dependentObjects = (ObjectAddressAndFlags *) |
| palloc(maxDependentObjects * sizeof(ObjectAddressAndFlags)); |
| numDependentObjects = 0; |
| |
| ScanKeyInit(&key[0], |
| Anum_pg_depend_refclassid, |
| BTEqualStrategyNumber, F_OIDEQ, |
| ObjectIdGetDatum(object->classId)); |
| ScanKeyInit(&key[1], |
| Anum_pg_depend_refobjid, |
| BTEqualStrategyNumber, F_OIDEQ, |
| ObjectIdGetDatum(object->objectId)); |
| if (object->objectSubId != 0) |
| { |
| ScanKeyInit(&key[2], |
| Anum_pg_depend_refobjsubid, |
| BTEqualStrategyNumber, F_INT4EQ, |
| Int32GetDatum(object->objectSubId)); |
| nkeys = 3; |
| } |
| else |
| nkeys = 2; |
| |
| scan = systable_beginscan(*depRel, DependReferenceIndexId, true, |
| NULL, nkeys, key); |
| |
| while (HeapTupleIsValid(tup = systable_getnext(scan))) |
| { |
| Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup); |
| int subflags; |
| |
| otherObject.classId = foundDep->classid; |
| otherObject.objectId = foundDep->objid; |
| otherObject.objectSubId = foundDep->objsubid; |
| |
| /* |
| * If what we found is a sub-object of the current object, just ignore |
| * it. (Normally, such a dependency is implicit, but we must make |
| * explicit ones in some cases involving partitioning.) |
| */ |
| if (otherObject.classId == object->classId && |
| otherObject.objectId == object->objectId && |
| object->objectSubId == 0) |
| continue; |
| |
| /* |
| * Must lock the dependent object before recursing to it. |
| */ |
| AcquireDeletionLock(&otherObject, 0); |
| |
| /* |
| * The dependent object might have been deleted while we waited to |
| * lock it; if so, we don't need to do anything more with it. We can |
| * test this cheaply and independently of the object's type by seeing |
| * if the pg_depend tuple we are looking at is still live. (If the |
| * object got deleted, the tuple would have been deleted too.) |
| */ |
| if (!systable_recheck_tuple(scan, tup)) |
| { |
| /* release the now-useless lock */ |
| ReleaseDeletionLock(&otherObject); |
| /* and continue scanning for dependencies */ |
| continue; |
| } |
| |
| /* |
| * We do need to delete it, so identify objflags to be passed down, |
| * which depend on the dependency type. |
| */ |
| switch (foundDep->deptype) |
| { |
| case DEPENDENCY_NORMAL: |
| subflags = DEPFLAG_NORMAL; |
| break; |
| case DEPENDENCY_AUTO: |
| case DEPENDENCY_AUTO_EXTENSION: |
| subflags = DEPFLAG_AUTO; |
| break; |
| case DEPENDENCY_INTERNAL: |
| subflags = DEPFLAG_INTERNAL; |
| break; |
| case DEPENDENCY_PARTITION_PRI: |
| case DEPENDENCY_PARTITION_SEC: |
| subflags = DEPFLAG_PARTITION; |
| break; |
| case DEPENDENCY_EXTENSION: |
| subflags = DEPFLAG_EXTENSION; |
| break; |
| default: |
| elog(ERROR, "unrecognized dependency type '%c' for %s", |
| foundDep->deptype, getObjectDescription(object, false)); |
| subflags = 0; /* keep compiler quiet */ |
| break; |
| } |
| |
| /* And add it to the pending-objects list */ |
| if (numDependentObjects >= maxDependentObjects) |
| { |
| /* enlarge array if needed */ |
| maxDependentObjects *= 2; |
| dependentObjects = (ObjectAddressAndFlags *) |
| repalloc(dependentObjects, |
| maxDependentObjects * sizeof(ObjectAddressAndFlags)); |
| } |
| |
| dependentObjects[numDependentObjects].obj = otherObject; |
| dependentObjects[numDependentObjects].subflags = subflags; |
| numDependentObjects++; |
| } |
| |
| systable_endscan(scan); |
| |
| /* |
| * Now we can sort the dependent objects into a stable visitation order. |
| * It's safe to use object_address_comparator here since the obj field is |
| * first within ObjectAddressAndFlags. |
| */ |
| if (numDependentObjects > 1) |
| qsort(dependentObjects, numDependentObjects, |
| sizeof(ObjectAddressAndFlags), |
| object_address_comparator); |
| |
| /* |
| * Now recurse to the dependent objects. We must visit them first since |
| * they have to be deleted before the current object. |
| */ |
| mystack.object = object; /* set up a new stack level */ |
| mystack.flags = objflags; |
| mystack.next = stack; |
| |
| for (int i = 0; i < numDependentObjects; i++) |
| { |
| ObjectAddressAndFlags *depObj = dependentObjects + i; |
| |
| findDependentObjects(&depObj->obj, |
| depObj->subflags, |
| flags, |
| &mystack, |
| targetObjects, |
| pendingObjects, |
| depRel); |
| } |
| |
| pfree(dependentObjects); |
| |
| /* |
| * Finally, we can add the target object to targetObjects. Be careful to |
| * include any flags that were passed back down to us from inner recursion |
| * levels. Record the "dependee" as being either the most important |
| * partition owner if there is one, else the object we recursed from, if |
| * any. (The logic in reportDependentObjects() is such that it can only |
| * need one of those objects.) |
| */ |
| extra.flags = mystack.flags; |
| if (extra.flags & DEPFLAG_IS_PART) |
| extra.dependee = partitionObject; |
| else if (stack) |
| extra.dependee = *stack->object; |
| else |
| memset(&extra.dependee, 0, sizeof(extra.dependee)); |
| add_exact_object_address_extra(object, &extra, targetObjects); |
| } |
| |
| /* |
| * reportDependentObjects - report about dependencies, and fail if RESTRICT |
| * |
| * Tell the user about dependent objects that we are going to delete |
| * (or would need to delete, but are prevented by RESTRICT mode); |
| * then error out if there are any and it's not CASCADE mode. |
| * |
| * targetObjects: list of objects that are scheduled to be deleted |
| * behavior: RESTRICT or CASCADE |
| * flags: other flags for the deletion operation |
| * origObject: base object of deletion, or NULL if not available |
| * (the latter case occurs in DROP OWNED) |
| */ |
| static void |
| reportDependentObjects(const ObjectAddresses *targetObjects, |
| DropBehavior behavior, |
| int flags, |
| const ObjectAddress *origObject) |
| { |
| int msglevel = (flags & PERFORM_DELETION_QUIETLY) ? DEBUG2 : NOTICE; |
| bool ok = true; |
| StringInfoData clientdetail; |
| StringInfoData logdetail; |
| int numReportedClient = 0; |
| int numNotReportedClient = 0; |
| int i; |
| |
| /* |
| * If we need to delete any partition-dependent objects, make sure that |
| * we're deleting at least one of their partition dependencies, too. That |
| * can be detected by checking that we reached them by a PARTITION |
| * dependency at some point. |
| * |
| * We just report the first such object, as in most cases the only way to |
| * trigger this complaint is to explicitly try to delete one partition of |
| * a partitioned object. |
| */ |
| for (i = 0; i < targetObjects->numrefs; i++) |
| { |
| const ObjectAddressExtra *extra = &targetObjects->extras[i]; |
| |
| if ((extra->flags & DEPFLAG_IS_PART) && |
| !(extra->flags & DEPFLAG_PARTITION)) |
| { |
| const ObjectAddress *object = &targetObjects->refs[i]; |
| char *otherObjDesc = getObjectDescription(&extra->dependee, |
| false); |
| |
| ereport(ERROR, |
| (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), |
| errmsg("cannot drop %s because %s requires it", |
| getObjectDescription(object, false), otherObjDesc), |
| errhint("You can drop %s instead.", otherObjDesc))); |
| } |
| } |
| |
| /* |
| * If no error is to be thrown, and the msglevel is too low to be shown to |
| * either client or server log, there's no need to do any of the rest of |
| * the work. |
| */ |
| if (behavior == DROP_CASCADE && |
| !message_level_is_interesting(msglevel)) |
| return; |
| |
| /* |
| * We limit the number of dependencies reported to the client to |
| * MAX_REPORTED_DEPS, since client software may not deal well with |
| * enormous error strings. The server log always gets a full report. |
| */ |
| #define MAX_REPORTED_DEPS 100 |
| |
| initStringInfo(&clientdetail); |
| initStringInfo(&logdetail); |
| |
| /* |
| * We process the list back to front (ie, in dependency order not deletion |
| * order), since this makes for a more understandable display. |
| */ |
| for (i = targetObjects->numrefs - 1; i >= 0; i--) |
| { |
| const ObjectAddress *obj = &targetObjects->refs[i]; |
| const ObjectAddressExtra *extra = &targetObjects->extras[i]; |
| char *objDesc; |
| |
| /* Ignore the original deletion target(s) */ |
| if (extra->flags & DEPFLAG_ORIGINAL) |
| continue; |
| |
| /* Also ignore sub-objects; we'll report the whole object elsewhere */ |
| if (extra->flags & DEPFLAG_SUBOBJECT) |
| continue; |
| |
| objDesc = getObjectDescription(obj, false); |
| |
| /* An object being dropped concurrently doesn't need to be reported */ |
| if (objDesc == NULL) |
| continue; |
| |
| /* |
| * If, at any stage of the recursive search, we reached the object via |
| * an AUTO, INTERNAL, PARTITION, or EXTENSION dependency, then it's |
| * okay to delete it even in RESTRICT mode. |
| */ |
| if (extra->flags & (DEPFLAG_AUTO | |
| DEPFLAG_INTERNAL | |
| DEPFLAG_PARTITION | |
| DEPFLAG_EXTENSION)) |
| { |
| /* |
| * auto-cascades are reported at DEBUG2, not msglevel. We don't |
| * try to combine them with the regular message because the |
| * results are too confusing when client_min_messages and |
| * log_min_messages are different. |
| */ |
| ereport(DEBUG2, |
| (errmsg_internal("drop auto-cascades to %s", |
| objDesc))); |
| } |
| else if (behavior == DROP_RESTRICT) |
| { |
| char *otherDesc = getObjectDescription(&extra->dependee, |
| false); |
| |
| if (otherDesc) |
| { |
| if (msglevel == NOTICE && Gp_role == GP_ROLE_EXECUTE) |
| { |
| ereport(DEBUG1, |
| (errmsg("%s depends on %s", |
| objDesc, otherDesc))); |
| } |
| else |
| { |
| if (numReportedClient < MAX_REPORTED_DEPS) |
| { |
| /* separate entries with a newline */ |
| if (clientdetail.len != 0) |
| appendStringInfoChar(&clientdetail, '\n'); |
| appendStringInfo(&clientdetail, _("%s depends on %s"), |
| objDesc, otherDesc); |
| numReportedClient++; |
| } |
| else |
| numNotReportedClient++; |
| /* separate entries with a newline */ |
| if (logdetail.len != 0) |
| appendStringInfoChar(&logdetail, '\n'); |
| appendStringInfo(&logdetail, _("%s depends on %s"), |
| objDesc, otherDesc); |
| pfree(otherDesc); |
| } |
| } |
| else |
| numNotReportedClient++; |
| ok = false; |
| } |
| else |
| { |
| if (Gp_role == GP_ROLE_EXECUTE) |
| { |
| ereport(DEBUG1, |
| (errmsg("drop cascades to %s", |
| objDesc))); |
| } |
| else |
| { |
| if (numReportedClient < MAX_REPORTED_DEPS) |
| { |
| /* separate entries with a newline */ |
| if (clientdetail.len != 0) |
| appendStringInfoChar(&clientdetail, '\n'); |
| appendStringInfo(&clientdetail, _("drop cascades to %s"), |
| objDesc); |
| numReportedClient++; |
| } |
| else |
| numNotReportedClient++; |
| /* separate entries with a newline */ |
| if (logdetail.len != 0) |
| appendStringInfoChar(&logdetail, '\n'); |
| appendStringInfo(&logdetail, _("drop cascades to %s"), |
| objDesc); |
| } |
| } |
| |
| pfree(objDesc); |
| } |
| |
| if (numNotReportedClient > 0) |
| appendStringInfo(&clientdetail, ngettext("\nand %d other object " |
| "(see server log for list)", |
| "\nand %d other objects " |
| "(see server log for list)", |
| numNotReportedClient), |
| numNotReportedClient); |
| |
| if (!ok) |
| { |
| if (origObject) |
| ereport(ERROR, |
| (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), |
| errmsg("cannot drop %s because other objects depend on it", |
| getObjectDescription(origObject, false)), |
| errdetail_internal("%s", clientdetail.data), |
| errdetail_log("%s", logdetail.data), |
| errhint("Use DROP ... CASCADE to drop the dependent objects too."))); |
| else |
| ereport(ERROR, |
| (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), |
| errmsg("cannot drop desired object(s) because other objects depend on them"), |
| errdetail_internal("%s", clientdetail.data), |
| errdetail_log("%s", logdetail.data), |
| errhint("Use DROP ... CASCADE to drop the dependent objects too."))); |
| } |
| else if (numReportedClient > 1) |
| { |
| ereport(msglevel, |
| (errmsg_plural("drop cascades to %d other object", |
| "drop cascades to %d other objects", |
| numReportedClient + numNotReportedClient, |
| numReportedClient + numNotReportedClient), |
| errdetail_internal("%s", clientdetail.data), |
| errdetail_log("%s", logdetail.data))); |
| } |
| else if (numReportedClient == 1) |
| { |
| /* we just use the single item as-is */ |
| ereport(msglevel, |
| (errmsg_internal("%s", clientdetail.data))); |
| } |
| |
| pfree(clientdetail.data); |
| pfree(logdetail.data); |
| } |
| |
| /* |
| * Drop an object by OID. Works for most catalogs, if no special processing |
| * is needed. |
| */ |
| static void |
| DropObjectById(const ObjectAddress *object) |
| { |
| int cacheId; |
| Relation rel; |
| HeapTuple tup; |
| |
| /* |
| * GPDB_14_MERGE_FIXME: specific for GPDB's pg_stat_last_* tables. |
| * TODO: check more objects as api changed. |
| */ |
| if ((Gp_role == GP_ROLE_DISPATCH) && (OCLASS_TRANSFORM == getObjectClass(object))) |
| MetaTrackDropObject(TransformRelationId, object->objectId); |
| |
| cacheId = get_object_catcache_oid(object->classId); |
| |
| rel = table_open(object->classId, RowExclusiveLock); |
| |
| /* |
| * Use the system cache for the oid column, if one exists. |
| */ |
| if (cacheId >= 0) |
| { |
| tup = SearchSysCache1(cacheId, ObjectIdGetDatum(object->objectId)); |
| if (!HeapTupleIsValid(tup)) |
| elog(ERROR, "cache lookup failed for %s %u", |
| get_object_class_descr(object->classId), object->objectId); |
| |
| CatalogTupleDelete(rel, &tup->t_self); |
| |
| ReleaseSysCache(tup); |
| } |
| else |
| { |
| ScanKeyData skey[1]; |
| SysScanDesc scan; |
| |
| ScanKeyInit(&skey[0], |
| get_object_attnum_oid(object->classId), |
| BTEqualStrategyNumber, F_OIDEQ, |
| ObjectIdGetDatum(object->objectId)); |
| |
| scan = systable_beginscan(rel, get_object_oid_index(object->classId), true, |
| NULL, 1, skey); |
| |
| /* we expect exactly one match */ |
| tup = systable_getnext(scan); |
| if (!HeapTupleIsValid(tup)) |
| elog(ERROR, "could not find tuple for %s %u", |
| get_object_class_descr(object->classId), object->objectId); |
| |
| CatalogTupleDelete(rel, &tup->t_self); |
| |
| systable_endscan(scan); |
| } |
| |
| table_close(rel, RowExclusiveLock); |
| } |
| |
| /* |
| * deleteOneObject: delete a single object for performDeletion. |
| * |
| * *depRel is the already-open pg_depend relation. |
| */ |
| static void |
| deleteOneObject(const ObjectAddress *object, Relation *depRel, int flags) |
| { |
| ScanKeyData key[3]; |
| int nkeys; |
| SysScanDesc scan; |
| HeapTuple tup; |
| |
| /* DROP hook of the objects being removed */ |
| InvokeObjectDropHookArg(object->classId, object->objectId, |
| object->objectSubId, flags); |
| |
| /* |
| * Close depRel if we are doing a drop concurrently. The object deletion |
| * subroutine will commit the current transaction, so we can't keep the |
| * relation open across doDeletion(). |
| */ |
| if (flags & PERFORM_DELETION_CONCURRENTLY) |
| table_close(*depRel, RowExclusiveLock); |
| |
| /* |
| * Delete the object itself, in an object-type-dependent way. |
| * |
| * We used to do this after removing the outgoing dependency links, but it |
| * seems just as reasonable to do it beforehand. In the concurrent case |
| * we *must* do it in this order, because we can't make any transactional |
| * updates before calling doDeletion() --- they'd get committed right |
| * away, which is not cool if the deletion then fails. |
| */ |
| doDeletion(object, flags); |
| |
| /* |
| * Reopen depRel if we closed it above |
| */ |
| if (flags & PERFORM_DELETION_CONCURRENTLY) |
| *depRel = table_open(DependRelationId, RowExclusiveLock); |
| |
| /* |
| * Now remove any pg_depend records that link from this object to others. |
| * (Any records linking to this object should be gone already.) |
| * |
| * When dropping a whole object (subId = 0), remove all pg_depend records |
| * for its sub-objects too. |
| */ |
| ScanKeyInit(&key[0], |
| Anum_pg_depend_classid, |
| BTEqualStrategyNumber, F_OIDEQ, |
| ObjectIdGetDatum(object->classId)); |
| ScanKeyInit(&key[1], |
| Anum_pg_depend_objid, |
| BTEqualStrategyNumber, F_OIDEQ, |
| ObjectIdGetDatum(object->objectId)); |
| if (object->objectSubId != 0) |
| { |
| ScanKeyInit(&key[2], |
| Anum_pg_depend_objsubid, |
| BTEqualStrategyNumber, F_INT4EQ, |
| Int32GetDatum(object->objectSubId)); |
| nkeys = 3; |
| } |
| else |
| nkeys = 2; |
| |
| scan = systable_beginscan(*depRel, DependDependerIndexId, true, |
| NULL, nkeys, key); |
| |
| while (HeapTupleIsValid(tup = systable_getnext(scan))) |
| { |
| CatalogTupleDelete(*depRel, &tup->t_self); |
| } |
| |
| systable_endscan(scan); |
| |
| /* |
| * Delete shared dependency references related to this object. Again, if |
| * subId = 0, remove records for sub-objects too. |
| */ |
| deleteSharedDependencyRecordsFor(object->classId, object->objectId, |
| object->objectSubId); |
| |
| |
| /* |
| * Delete any comments, security labels, or initial privileges associated |
| * with this object. (This is a convenient place to do these things, |
| * rather than having every object type know to do it.) As above, all |
| * these functions must remove records for sub-objects too if the subid is |
| * zero. |
| */ |
| DeleteComments(object->objectId, object->classId, object->objectSubId); |
| DeleteSecurityLabel(object); |
| DeleteInitPrivs(object); |
| |
| /* |
| * CommandCounterIncrement here to ensure that preceding changes are all |
| * visible to the next deletion step. |
| */ |
| CommandCounterIncrement(); |
| |
| /* |
| * And we're done! |
| */ |
| } |
| |
| /* |
| * doDeletion: actually delete a single object |
| */ |
| static void |
| doDeletion(const ObjectAddress *object, int flags) |
| { |
| switch (getObjectClass(object)) |
| { |
| case OCLASS_CLASS: |
| { |
| char relKind = get_rel_relkind(object->objectId); |
| |
| if (relKind == RELKIND_INDEX || |
| relKind == RELKIND_PARTITIONED_INDEX) |
| { |
| bool concurrent = ((flags & PERFORM_DELETION_CONCURRENTLY) != 0); |
| bool concurrent_lock_mode = ((flags & PERFORM_DELETION_CONCURRENT_LOCK) != 0); |
| |
| Assert(object->objectSubId == 0); |
| index_drop(object->objectId, concurrent, concurrent_lock_mode); |
| |
| /* |
| * Delete tag description. |
| */ |
| DeleteTagDescriptions(MyDatabaseId, |
| object->classId, |
| object->objectId); |
| } |
| else |
| { |
| bool drop_with_content = (flags & PERFORM_DELETION_WITH_CONTENT) != 0; |
| |
| if (object->objectSubId != 0) |
| RemoveAttributeById(object->objectId, |
| object->objectSubId); |
| else |
| { |
| heap_drop_with_catalog(object->objectId); |
| |
| /* |
| * Remove driectory table file conteng |
| */ |
| if (drop_with_content) |
| { |
| DirectoryTableDropStorage(object->objectId); |
| } |
| |
| /* |
| * Delete tag description. |
| */ |
| DeleteTagDescriptions(MyDatabaseId, |
| object->classId, |
| object->objectId); |
| } |
| |
| } |
| |
| /* |
| * for a sequence, in addition to dropping the heap, also |
| * delete pg_sequence tuple |
| */ |
| if (relKind == RELKIND_SEQUENCE) |
| DeleteSequenceTuple(object->objectId); |
| break; |
| } |
| |
| case OCLASS_PROC: |
| RemoveFunctionById(object->objectId); |
| break; |
| |
| case OCLASS_TYPE: |
| RemoveTypeById(object->objectId); |
| break; |
| |
| case OCLASS_CONSTRAINT: |
| RemoveConstraintById(object->objectId); |
| break; |
| |
| case OCLASS_DEFAULT: |
| RemoveAttrDefaultById(object->objectId); |
| break; |
| |
| case OCLASS_LARGEOBJECT: |
| LargeObjectDrop(object->objectId); |
| break; |
| |
| case OCLASS_OPERATOR: |
| RemoveOperatorById(object->objectId); |
| break; |
| |
| case OCLASS_REWRITE: |
| RemoveRewriteRuleById(object->objectId); |
| break; |
| |
| case OCLASS_TRIGGER: |
| RemoveTriggerById(object->objectId); |
| break; |
| |
| case OCLASS_STATISTIC_EXT: |
| RemoveStatisticsById(object->objectId); |
| break; |
| |
| case OCLASS_TSCONFIG: |
| RemoveTSConfigurationById(object->objectId); |
| break; |
| |
| case OCLASS_EXTENSION: |
| RemoveExtensionById(object->objectId); |
| break; |
| |
| case OCLASS_POLICY: |
| RemovePolicyById(object->objectId); |
| break; |
| |
| case OCLASS_PUBLICATION_NAMESPACE: |
| RemovePublicationSchemaById(object->objectId); |
| break; |
| |
| case OCLASS_PUBLICATION_REL: |
| RemovePublicationRelById(object->objectId); |
| break; |
| |
| case OCLASS_PUBLICATION: |
| RemovePublicationById(object->objectId); |
| break; |
| |
| case OCLASS_SCHEMA: |
| RemoveSchemaById(object->objectId); |
| /* |
| * Delete tag description. |
| */ |
| DeleteTagDescriptions(MyDatabaseId, |
| object->classId, |
| object->objectId); |
| break; |
| case OCLASS_TASK: |
| RemoveTaskById(object->objectId); |
| break; |
| |
| case OCLASS_MATVIEW_AUX: |
| RemoveMatviewAuxEntry(object->objectId); |
| break; |
| |
| case OCLASS_CAST: |
| case OCLASS_COLLATION: |
| case OCLASS_CONVERSION: |
| case OCLASS_LANGUAGE: |
| case OCLASS_OPCLASS: |
| case OCLASS_OPFAMILY: |
| case OCLASS_AM: |
| case OCLASS_AMOP: |
| case OCLASS_AMPROC: |
| case OCLASS_TSPARSER: |
| case OCLASS_TSDICT: |
| case OCLASS_TSTEMPLATE: |
| case OCLASS_FDW: |
| case OCLASS_FOREIGN_SERVER: |
| case OCLASS_USER_MAPPING: |
| case OCLASS_DEFACL: |
| case OCLASS_EVENT_TRIGGER: |
| case OCLASS_EXTPROTOCOL: |
| case OCLASS_TRANSFORM: |
| case OCLASS_ROLE_MEMBERSHIP: |
| DropObjectById(object); |
| break; |
| |
| /* |
| * These global object types are not supported here. |
| */ |
| case OCLASS_ROLE: |
| case OCLASS_DATABASE: |
| case OCLASS_TBLSPACE: |
| case OCLASS_SUBSCRIPTION: |
| case OCLASS_PROFILE: |
| case OCLASS_PASSWORDHISTORY: |
| case OCLASS_STORAGE_SERVER: |
| case OCLASS_STORAGE_USER_MAPPING: |
| case OCLASS_TAG: |
| case OCLASS_TAG_DESCRIPTION: |
| case OCLASS_PARAMETER_ACL: |
| elog(ERROR, "global objects cannot be deleted by doDeletion"); |
| break; |
| |
| default: |
| { |
| struct CustomObjectClass *coc; |
| |
| coc = find_custom_object_class_by_classid(object->classId, false); |
| if (coc->do_delete) |
| coc->do_delete(coc, object, flags); |
| /* |
| * There's intentionally no default: case here; we want the |
| * compiler to warn if a new OCLASS hasn't been handled above. |
| */ |
| break; |
| } |
| } |
| } |
| |
| /* |
| * AcquireDeletionLock - acquire a suitable lock for deleting an object |
| * |
| * Accepts the same flags as performDeletion (though currently only |
| * PERFORM_DELETION_CONCURRENTLY does anything). |
| * |
| * We use LockRelation for relations, and otherwise LockSharedObject or |
| * LockDatabaseObject as appropriate for the object type. |
| */ |
| void |
| AcquireDeletionLock(const ObjectAddress *object, int flags) |
| { |
| if (object->classId == RelationRelationId) |
| { |
| /* |
| * In DROP INDEX CONCURRENTLY, take only ShareUpdateExclusiveLock on |
| * the index for the moment. index_drop() will promote the lock once |
| * it's safe to do so. In all other cases we need full exclusive |
| * lock. |
| */ |
| if (flags & PERFORM_DELETION_CONCURRENTLY) |
| LockRelationOid(object->objectId, ShareUpdateExclusiveLock); |
| else |
| LockRelationOid(object->objectId, AccessExclusiveLock); |
| } |
| else if (object->classId == AuthMemRelationId) |
| LockSharedObject(object->classId, object->objectId, 0, |
| AccessExclusiveLock); |
| else |
| { |
| /* assume we should lock the whole object not a sub-object */ |
| LockDatabaseObject(object->classId, object->objectId, 0, |
| AccessExclusiveLock); |
| } |
| } |
| |
| /* |
| * ReleaseDeletionLock - release an object deletion lock |
| * |
| * Companion to AcquireDeletionLock. |
| */ |
| void |
| ReleaseDeletionLock(const ObjectAddress *object) |
| { |
| if (object->classId == RelationRelationId) |
| UnlockRelationOid(object->objectId, AccessExclusiveLock); |
| else |
| /* assume we should lock the whole object not a sub-object */ |
| UnlockDatabaseObject(object->classId, object->objectId, 0, |
| AccessExclusiveLock); |
| } |
| |
| /* |
| * recordDependencyOnExpr - find expression dependencies |
| * |
| * This is used to find the dependencies of rules, constraint expressions, |
| * etc. |
| * |
| * Given an expression or query in node-tree form, find all the objects |
| * it refers to (tables, columns, operators, functions, etc). Record |
| * a dependency of the specified type from the given depender object |
| * to each object mentioned in the expression. |
| * |
| * rtable is the rangetable to be used to interpret Vars with varlevelsup=0. |
| * It can be NIL if no such variables are expected. |
| */ |
| void |
| recordDependencyOnExpr(const ObjectAddress *depender, |
| Node *expr, List *rtable, |
| DependencyType behavior) |
| { |
| find_expr_references_context context; |
| |
| context.addrs = new_object_addresses(); |
| |
| /* Set up interpretation for Vars at varlevelsup = 0 */ |
| context.rtables = list_make1(rtable); |
| |
| /* Scan the expression tree for referenceable objects */ |
| find_expr_references_walker(expr, &context); |
| |
| /* Remove any duplicates */ |
| eliminate_duplicate_dependencies(context.addrs); |
| |
| /* And record 'em */ |
| recordMultipleDependencies(depender, |
| context.addrs->refs, context.addrs->numrefs, |
| behavior); |
| |
| free_object_addresses(context.addrs); |
| } |
| |
| /* |
| * recordDependencyOnSingleRelExpr - find expression dependencies |
| * |
| * As above, but only one relation is expected to be referenced (with |
| * varno = 1 and varlevelsup = 0). Pass the relation OID instead of a |
| * range table. An additional frammish is that dependencies on that |
| * relation's component columns will be marked with 'self_behavior', |
| * whereas 'behavior' is used for everything else; also, if 'reverse_self' |
| * is true, those dependencies are reversed so that the columns are made |
| * to depend on the table not vice versa. |
| * |
| * NOTE: the caller should ensure that a whole-table dependency on the |
| * specified relation is created separately, if one is needed. In particular, |
| * a whole-row Var "relation.*" will not cause this routine to emit any |
| * dependency item. This is appropriate behavior for subexpressions of an |
| * ordinary query, so other cases need to cope as necessary. |
| */ |
| void |
| recordDependencyOnSingleRelExpr(const ObjectAddress *depender, |
| Node *expr, Oid relId, |
| DependencyType behavior, |
| DependencyType self_behavior, |
| bool reverse_self) |
| { |
| find_expr_references_context context; |
| RangeTblEntry rte = {0}; |
| |
| context.addrs = new_object_addresses(); |
| |
| /* We gin up a rather bogus rangetable list to handle Vars */ |
| rte.type = T_RangeTblEntry; |
| rte.rtekind = RTE_RELATION; |
| rte.relid = relId; |
| rte.relkind = RELKIND_RELATION; /* no need for exactness here */ |
| rte.rellockmode = AccessShareLock; |
| |
| context.rtables = list_make1(list_make1(&rte)); |
| |
| /* Scan the expression tree for referenceable objects */ |
| find_expr_references_walker(expr, &context); |
| |
| /* Remove any duplicates */ |
| eliminate_duplicate_dependencies(context.addrs); |
| |
| /* Separate self-dependencies if necessary */ |
| if ((behavior != self_behavior || reverse_self) && |
| context.addrs->numrefs > 0) |
| { |
| ObjectAddresses *self_addrs; |
| ObjectAddress *outobj; |
| int oldref, |
| outrefs; |
| |
| self_addrs = new_object_addresses(); |
| |
| outobj = context.addrs->refs; |
| outrefs = 0; |
| for (oldref = 0; oldref < context.addrs->numrefs; oldref++) |
| { |
| ObjectAddress *thisobj = context.addrs->refs + oldref; |
| |
| if (thisobj->classId == RelationRelationId && |
| thisobj->objectId == relId) |
| { |
| /* Move this ref into self_addrs */ |
| add_exact_object_address(thisobj, self_addrs); |
| } |
| else |
| { |
| /* Keep it in context.addrs */ |
| *outobj = *thisobj; |
| outobj++; |
| outrefs++; |
| } |
| } |
| context.addrs->numrefs = outrefs; |
| |
| /* Record the self-dependencies with the appropriate direction */ |
| if (!reverse_self) |
| recordMultipleDependencies(depender, |
| self_addrs->refs, self_addrs->numrefs, |
| self_behavior); |
| else |
| { |
| /* Can't use recordMultipleDependencies, so do it the hard way */ |
| int selfref; |
| |
| for (selfref = 0; selfref < self_addrs->numrefs; selfref++) |
| { |
| ObjectAddress *thisobj = self_addrs->refs + selfref; |
| |
| recordDependencyOn(thisobj, depender, self_behavior); |
| } |
| } |
| |
| free_object_addresses(self_addrs); |
| } |
| |
| /* Record the external dependencies */ |
| recordMultipleDependencies(depender, |
| context.addrs->refs, context.addrs->numrefs, |
| behavior); |
| |
| free_object_addresses(context.addrs); |
| } |
| |
| /* |
| * Recursively search an expression tree for object references. |
| * |
| * Note: in many cases we do not need to create dependencies on the datatypes |
| * involved in an expression, because we'll have an indirect dependency via |
| * some other object. For instance Var nodes depend on a column which depends |
| * on the datatype, and OpExpr nodes depend on the operator which depends on |
| * the datatype. However we do need a type dependency if there is no such |
| * indirect dependency, as for example in Const and CoerceToDomain nodes. |
| * |
| * Similarly, we don't need to create dependencies on collations except where |
| * the collation is being freshly introduced to the expression. |
| */ |
| static bool |
| find_expr_references_walker(Node *node, |
| find_expr_references_context *context) |
| { |
| if (node == NULL) |
| return false; |
| if (IsA(node, Var)) |
| { |
| Var *var = (Var *) node; |
| List *rtable; |
| RangeTblEntry *rte; |
| |
| /* Find matching rtable entry, or complain if not found */ |
| if (var->varlevelsup >= list_length(context->rtables)) |
| elog(ERROR, "invalid varlevelsup %d", var->varlevelsup); |
| rtable = (List *) list_nth(context->rtables, var->varlevelsup); |
| if (var->varno <= 0 || var->varno > list_length(rtable)) |
| elog(ERROR, "invalid varno %d", var->varno); |
| rte = rt_fetch(var->varno, rtable); |
| |
| /* |
| * A whole-row Var references no specific columns, so adds no new |
| * dependency. (We assume that there is a whole-table dependency |
| * arising from each underlying rangetable entry. While we could |
| * record such a dependency when finding a whole-row Var that |
| * references a relation directly, it's quite unclear how to extend |
| * that to whole-row Vars for JOINs, so it seems better to leave the |
| * responsibility with the range table. Note that this poses some |
| * risks for identifying dependencies of stand-alone expressions: |
| * whole-table references may need to be created separately.) |
| */ |
| if (var->varattno == InvalidAttrNumber) |
| return false; |
| if (rte->rtekind == RTE_RELATION) |
| { |
| /* If it's a plain relation, reference this column */ |
| add_object_address(OCLASS_CLASS, rte->relid, var->varattno, |
| context->addrs); |
| } |
| else if (rte->rtekind == RTE_FUNCTION) |
| { |
| /* Might need to add a dependency on a composite type's column */ |
| /* (done out of line, because it's a bit bulky) */ |
| process_function_rte_ref(rte, var->varattno, context); |
| } |
| |
| /* |
| * Vars referencing other RTE types require no additional work. In |
| * particular, a join alias Var can be ignored, because it must |
| * reference a merged USING column. The relevant join input columns |
| * will also be referenced in the join qual, and any type coercion |
| * functions involved in the alias expression will be dealt with when |
| * we scan the RTE itself. |
| */ |
| return false; |
| } |
| else if (IsA(node, Const)) |
| { |
| Const *con = (Const *) node; |
| Oid objoid; |
| |
| /* A constant must depend on the constant's datatype */ |
| add_object_address(OCLASS_TYPE, con->consttype, 0, |
| context->addrs); |
| |
| /* |
| * We must also depend on the constant's collation: it could be |
| * different from the datatype's, if a CollateExpr was const-folded to |
| * a simple constant. However we can save work in the most common |
| * case where the collation is "default", since we know that's pinned. |
| */ |
| if (OidIsValid(con->constcollid) && |
| con->constcollid != DEFAULT_COLLATION_OID) |
| add_object_address(OCLASS_COLLATION, con->constcollid, 0, |
| context->addrs); |
| |
| /* |
| * If it's a regclass or similar literal referring to an existing |
| * object, add a reference to that object. (Currently, only the |
| * regclass and regconfig cases have any likely use, but we may as |
| * well handle all the OID-alias datatypes consistently.) |
| */ |
| if (!con->constisnull) |
| { |
| switch (con->consttype) |
| { |
| case REGPROCOID: |
| case REGPROCEDUREOID: |
| objoid = DatumGetObjectId(con->constvalue); |
| if (SearchSysCacheExists1(PROCOID, |
| ObjectIdGetDatum(objoid))) |
| add_object_address(OCLASS_PROC, objoid, 0, |
| context->addrs); |
| break; |
| case REGOPEROID: |
| case REGOPERATOROID: |
| objoid = DatumGetObjectId(con->constvalue); |
| if (SearchSysCacheExists1(OPEROID, |
| ObjectIdGetDatum(objoid))) |
| add_object_address(OCLASS_OPERATOR, objoid, 0, |
| context->addrs); |
| break; |
| case REGCLASSOID: |
| objoid = DatumGetObjectId(con->constvalue); |
| if (SearchSysCacheExists1(RELOID, |
| ObjectIdGetDatum(objoid))) |
| add_object_address(OCLASS_CLASS, objoid, 0, |
| context->addrs); |
| break; |
| case REGTYPEOID: |
| objoid = DatumGetObjectId(con->constvalue); |
| if (SearchSysCacheExists1(TYPEOID, |
| ObjectIdGetDatum(objoid))) |
| add_object_address(OCLASS_TYPE, objoid, 0, |
| context->addrs); |
| break; |
| case REGCOLLATIONOID: |
| objoid = DatumGetObjectId(con->constvalue); |
| if (SearchSysCacheExists1(COLLOID, |
| ObjectIdGetDatum(objoid))) |
| add_object_address(OCLASS_COLLATION, objoid, 0, |
| context->addrs); |
| break; |
| case REGCONFIGOID: |
| objoid = DatumGetObjectId(con->constvalue); |
| if (SearchSysCacheExists1(TSCONFIGOID, |
| ObjectIdGetDatum(objoid))) |
| add_object_address(OCLASS_TSCONFIG, objoid, 0, |
| context->addrs); |
| break; |
| case REGDICTIONARYOID: |
| objoid = DatumGetObjectId(con->constvalue); |
| if (SearchSysCacheExists1(TSDICTOID, |
| ObjectIdGetDatum(objoid))) |
| add_object_address(OCLASS_TSDICT, objoid, 0, |
| context->addrs); |
| break; |
| |
| case REGNAMESPACEOID: |
| objoid = DatumGetObjectId(con->constvalue); |
| if (SearchSysCacheExists1(NAMESPACEOID, |
| ObjectIdGetDatum(objoid))) |
| add_object_address(OCLASS_SCHEMA, objoid, 0, |
| context->addrs); |
| break; |
| |
| /* |
| * Dependencies for regrole should be shared among all |
| * databases, so explicitly inhibit to have dependencies. |
| */ |
| case REGROLEOID: |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("constant of the type %s cannot be used here", |
| "regrole"))); |
| break; |
| } |
| } |
| return false; |
| } |
| else if (IsA(node, Param)) |
| { |
| Param *param = (Param *) node; |
| |
| /* A parameter must depend on the parameter's datatype */ |
| add_object_address(OCLASS_TYPE, param->paramtype, 0, |
| context->addrs); |
| /* and its collation, just as for Consts */ |
| if (OidIsValid(param->paramcollid) && |
| param->paramcollid != DEFAULT_COLLATION_OID) |
| add_object_address(OCLASS_COLLATION, param->paramcollid, 0, |
| context->addrs); |
| } |
| else if (IsA(node, FuncExpr)) |
| { |
| FuncExpr *funcexpr = (FuncExpr *) node; |
| |
| add_object_address(OCLASS_PROC, funcexpr->funcid, 0, |
| context->addrs); |
| /* fall through to examine arguments */ |
| } |
| else if (IsA(node, OpExpr)) |
| { |
| OpExpr *opexpr = (OpExpr *) node; |
| |
| add_object_address(OCLASS_OPERATOR, opexpr->opno, 0, |
| context->addrs); |
| /* fall through to examine arguments */ |
| } |
| else if (IsA(node, DistinctExpr)) |
| { |
| DistinctExpr *distinctexpr = (DistinctExpr *) node; |
| |
| add_object_address(OCLASS_OPERATOR, distinctexpr->opno, 0, |
| context->addrs); |
| /* fall through to examine arguments */ |
| } |
| else if (IsA(node, NullIfExpr)) |
| { |
| NullIfExpr *nullifexpr = (NullIfExpr *) node; |
| |
| add_object_address(OCLASS_OPERATOR, nullifexpr->opno, 0, |
| context->addrs); |
| /* fall through to examine arguments */ |
| } |
| else if (IsA(node, ScalarArrayOpExpr)) |
| { |
| ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node; |
| |
| add_object_address(OCLASS_OPERATOR, opexpr->opno, 0, |
| context->addrs); |
| /* fall through to examine arguments */ |
| } |
| else if (IsA(node, Aggref)) |
| { |
| Aggref *aggref = (Aggref *) node; |
| |
| add_object_address(OCLASS_PROC, aggref->aggfnoid, 0, |
| context->addrs); |
| /* fall through to examine arguments */ |
| } |
| else if (IsA(node, WindowFunc)) |
| { |
| WindowFunc *wfunc = (WindowFunc *) node; |
| |
| add_object_address(OCLASS_PROC, wfunc->winfnoid, 0, |
| context->addrs); |
| /* fall through to examine arguments */ |
| } |
| else if (IsA(node, SubscriptingRef)) |
| { |
| SubscriptingRef *sbsref = (SubscriptingRef *) node; |
| |
| /* |
| * The refexpr should provide adequate dependency on refcontainertype, |
| * and that type in turn depends on refelemtype. However, a custom |
| * subscripting handler might set refrestype to something different |
| * from either of those, in which case we'd better record it. |
| */ |
| if (sbsref->refrestype != sbsref->refcontainertype && |
| sbsref->refrestype != sbsref->refelemtype) |
| add_object_address(OCLASS_TYPE, sbsref->refrestype, 0, |
| context->addrs); |
| /* fall through to examine arguments */ |
| } |
| else if (IsA(node, SubPlan)) |
| { |
| /* Extra work needed here if we ever need this case */ |
| elog(ERROR, "already-planned subqueries not supported"); |
| } |
| else if (IsA(node, FieldSelect)) |
| { |
| FieldSelect *fselect = (FieldSelect *) node; |
| Oid argtype = getBaseType(exprType((Node *) fselect->arg)); |
| Oid reltype = get_typ_typrelid(argtype); |
| |
| /* |
| * We need a dependency on the specific column named in FieldSelect, |
| * assuming we can identify the pg_class OID for it. (Probably we |
| * always can at the moment, but in future it might be possible for |
| * argtype to be RECORDOID.) If we can make a column dependency then |
| * we shouldn't need a dependency on the column's type; but if we |
| * can't, make a dependency on the type, as it might not appear |
| * anywhere else in the expression. |
| */ |
| if (OidIsValid(reltype)) |
| add_object_address(OCLASS_CLASS, reltype, fselect->fieldnum, |
| context->addrs); |
| else |
| add_object_address(OCLASS_TYPE, fselect->resulttype, 0, |
| context->addrs); |
| /* the collation might not be referenced anywhere else, either */ |
| if (OidIsValid(fselect->resultcollid) && |
| fselect->resultcollid != DEFAULT_COLLATION_OID) |
| add_object_address(OCLASS_COLLATION, fselect->resultcollid, 0, |
| context->addrs); |
| } |
| else if (IsA(node, FieldStore)) |
| { |
| FieldStore *fstore = (FieldStore *) node; |
| Oid reltype = get_typ_typrelid(fstore->resulttype); |
| |
| /* similar considerations to FieldSelect, but multiple column(s) */ |
| if (OidIsValid(reltype)) |
| { |
| ListCell *l; |
| |
| foreach(l, fstore->fieldnums) |
| add_object_address(OCLASS_CLASS, reltype, lfirst_int(l), |
| context->addrs); |
| } |
| else |
| add_object_address(OCLASS_TYPE, fstore->resulttype, 0, |
| context->addrs); |
| } |
| else if (IsA(node, RelabelType)) |
| { |
| RelabelType *relab = (RelabelType *) node; |
| |
| /* since there is no function dependency, need to depend on type */ |
| add_object_address(OCLASS_TYPE, relab->resulttype, 0, |
| context->addrs); |
| /* the collation might not be referenced anywhere else, either */ |
| if (OidIsValid(relab->resultcollid) && |
| relab->resultcollid != DEFAULT_COLLATION_OID) |
| add_object_address(OCLASS_COLLATION, relab->resultcollid, 0, |
| context->addrs); |
| } |
| else if (IsA(node, CoerceViaIO)) |
| { |
| CoerceViaIO *iocoerce = (CoerceViaIO *) node; |
| |
| /* since there is no exposed function, need to depend on type */ |
| add_object_address(OCLASS_TYPE, iocoerce->resulttype, 0, |
| context->addrs); |
| /* the collation might not be referenced anywhere else, either */ |
| if (OidIsValid(iocoerce->resultcollid) && |
| iocoerce->resultcollid != DEFAULT_COLLATION_OID) |
| add_object_address(OCLASS_COLLATION, iocoerce->resultcollid, 0, |
| context->addrs); |
| } |
| else if (IsA(node, ArrayCoerceExpr)) |
| { |
| ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node; |
| |
| /* as above, depend on type */ |
| add_object_address(OCLASS_TYPE, acoerce->resulttype, 0, |
| context->addrs); |
| /* the collation might not be referenced anywhere else, either */ |
| if (OidIsValid(acoerce->resultcollid) && |
| acoerce->resultcollid != DEFAULT_COLLATION_OID) |
| add_object_address(OCLASS_COLLATION, acoerce->resultcollid, 0, |
| context->addrs); |
| /* fall through to examine arguments */ |
| } |
| else if (IsA(node, ConvertRowtypeExpr)) |
| { |
| ConvertRowtypeExpr *cvt = (ConvertRowtypeExpr *) node; |
| |
| /* since there is no function dependency, need to depend on type */ |
| add_object_address(OCLASS_TYPE, cvt->resulttype, 0, |
| context->addrs); |
| } |
| else if (IsA(node, CollateExpr)) |
| { |
| CollateExpr *coll = (CollateExpr *) node; |
| |
| add_object_address(OCLASS_COLLATION, coll->collOid, 0, |
| context->addrs); |
| } |
| else if (IsA(node, RowExpr)) |
| { |
| RowExpr *rowexpr = (RowExpr *) node; |
| |
| add_object_address(OCLASS_TYPE, rowexpr->row_typeid, 0, |
| context->addrs); |
| } |
| else if (IsA(node, RowCompareExpr)) |
| { |
| RowCompareExpr *rcexpr = (RowCompareExpr *) node; |
| ListCell *l; |
| |
| foreach(l, rcexpr->opnos) |
| { |
| add_object_address(OCLASS_OPERATOR, lfirst_oid(l), 0, |
| context->addrs); |
| } |
| foreach(l, rcexpr->opfamilies) |
| { |
| add_object_address(OCLASS_OPFAMILY, lfirst_oid(l), 0, |
| context->addrs); |
| } |
| /* fall through to examine arguments */ |
| } |
| else if (IsA(node, CoerceToDomain)) |
| { |
| CoerceToDomain *cd = (CoerceToDomain *) node; |
| |
| add_object_address(OCLASS_TYPE, cd->resulttype, 0, |
| context->addrs); |
| } |
| else if (IsA(node, NextValueExpr)) |
| { |
| NextValueExpr *nve = (NextValueExpr *) node; |
| |
| add_object_address(OCLASS_CLASS, nve->seqid, 0, |
| context->addrs); |
| } |
| else if (IsA(node, OnConflictExpr)) |
| { |
| OnConflictExpr *onconflict = (OnConflictExpr *) node; |
| |
| if (OidIsValid(onconflict->constraint)) |
| add_object_address(OCLASS_CONSTRAINT, onconflict->constraint, 0, |
| context->addrs); |
| /* fall through to examine arguments */ |
| } |
| else if (IsA(node, SortGroupClause)) |
| { |
| SortGroupClause *sgc = (SortGroupClause *) node; |
| |
| add_object_address(OCLASS_OPERATOR, sgc->eqop, 0, |
| context->addrs); |
| if (OidIsValid(sgc->sortop)) |
| add_object_address(OCLASS_OPERATOR, sgc->sortop, 0, |
| context->addrs); |
| return false; |
| } |
| else if (IsA(node, WindowClause)) |
| { |
| WindowClause *wc = (WindowClause *) node; |
| |
| if (OidIsValid(wc->startInRangeFunc)) |
| add_object_address(OCLASS_PROC, wc->startInRangeFunc, 0, |
| context->addrs); |
| if (OidIsValid(wc->endInRangeFunc)) |
| add_object_address(OCLASS_PROC, wc->endInRangeFunc, 0, |
| context->addrs); |
| if (OidIsValid(wc->inRangeColl) && |
| wc->inRangeColl != DEFAULT_COLLATION_OID) |
| add_object_address(OCLASS_COLLATION, wc->inRangeColl, 0, |
| context->addrs); |
| /* fall through to examine substructure */ |
| } |
| else if (IsA(node, CTECycleClause)) |
| { |
| CTECycleClause *cc = (CTECycleClause *) node; |
| |
| if (OidIsValid(cc->cycle_mark_type)) |
| add_object_address(OCLASS_TYPE, cc->cycle_mark_type, 0, |
| context->addrs); |
| if (OidIsValid(cc->cycle_mark_collation)) |
| add_object_address(OCLASS_COLLATION, cc->cycle_mark_collation, 0, |
| context->addrs); |
| if (OidIsValid(cc->cycle_mark_neop)) |
| add_object_address(OCLASS_OPERATOR, cc->cycle_mark_neop, 0, |
| context->addrs); |
| /* fall through to examine substructure */ |
| } |
| else if (IsA(node, Query)) |
| { |
| /* Recurse into RTE subquery or not-yet-planned sublink subquery */ |
| Query *query = (Query *) node; |
| ListCell *lc; |
| bool result; |
| |
| /* |
| * Add whole-relation refs for each plain relation mentioned in the |
| * subquery's rtable, and ensure we add refs for any type-coercion |
| * functions used in join alias lists. |
| * |
| * Note: query_tree_walker takes care of recursing into RTE_FUNCTION |
| * RTEs, subqueries, etc, so no need to do that here. But we must |
| * tell it not to visit join alias lists, or we'll add refs for join |
| * input columns whether or not they are actually used in our query. |
| * |
| * Note: we don't need to worry about collations mentioned in |
| * RTE_VALUES or RTE_CTE RTEs, because those must just duplicate |
| * collations referenced in other parts of the Query. We do have to |
| * worry about collations mentioned in RTE_FUNCTION, but we take care |
| * of those when we recurse to the RangeTblFunction node(s). |
| */ |
| foreach(lc, query->rtable) |
| { |
| RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); |
| |
| switch (rte->rtekind) |
| { |
| case RTE_RELATION: |
| add_object_address(OCLASS_CLASS, rte->relid, 0, |
| context->addrs); |
| break; |
| case RTE_JOIN: |
| |
| /* |
| * Examine joinaliasvars entries only for merged JOIN |
| * USING columns. Only those entries could contain |
| * type-coercion functions. Also, their join input |
| * columns must be referenced in the join quals, so this |
| * won't accidentally add refs to otherwise-unused join |
| * input columns. (We want to ref the type coercion |
| * functions even if the merged column isn't explicitly |
| * used anywhere, to protect possible expansion of the |
| * join RTE as a whole-row var, and because it seems like |
| * a bad idea to allow dropping a function that's present |
| * in our query tree, whether or not it could get called.) |
| */ |
| context->rtables = lcons(query->rtable, context->rtables); |
| for (int i = 0; i < rte->joinmergedcols; i++) |
| { |
| Node *aliasvar = list_nth(rte->joinaliasvars, i); |
| |
| if (!IsA(aliasvar, Var)) |
| find_expr_references_walker(aliasvar, context); |
| } |
| context->rtables = list_delete_first(context->rtables); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /* |
| * If the query is an INSERT or UPDATE, we should create a dependency |
| * on each target column, to prevent the specific target column from |
| * being dropped. Although we will visit the TargetEntry nodes again |
| * during query_tree_walker, we won't have enough context to do this |
| * conveniently, so do it here. |
| */ |
| if (query->commandType == CMD_INSERT || |
| query->commandType == CMD_UPDATE) |
| { |
| RangeTblEntry *rte; |
| |
| if (query->resultRelation <= 0 || |
| query->resultRelation > list_length(query->rtable)) |
| elog(ERROR, "invalid resultRelation %d", |
| query->resultRelation); |
| rte = rt_fetch(query->resultRelation, query->rtable); |
| if (rte->rtekind == RTE_RELATION) |
| { |
| foreach(lc, query->targetList) |
| { |
| TargetEntry *tle = (TargetEntry *) lfirst(lc); |
| |
| if (tle->resjunk) |
| continue; /* ignore junk tlist items */ |
| add_object_address(OCLASS_CLASS, rte->relid, tle->resno, |
| context->addrs); |
| } |
| } |
| } |
| |
| /* |
| * Add dependencies on constraints listed in query's constraintDeps |
| */ |
| foreach(lc, query->constraintDeps) |
| { |
| add_object_address(OCLASS_CONSTRAINT, lfirst_oid(lc), 0, |
| context->addrs); |
| } |
| |
| /* Examine substructure of query */ |
| context->rtables = lcons(query->rtable, context->rtables); |
| result = query_tree_walker(query, |
| find_expr_references_walker, |
| (void *) context, |
| QTW_IGNORE_JOINALIASES | |
| QTW_EXAMINE_SORTGROUP); |
| context->rtables = list_delete_first(context->rtables); |
| return result; |
| } |
| else if (IsA(node, SetOperationStmt)) |
| { |
| SetOperationStmt *setop = (SetOperationStmt *) node; |
| |
| /* we need to look at the groupClauses for operator references */ |
| find_expr_references_walker((Node *) setop->groupClauses, context); |
| /* fall through to examine child nodes */ |
| } |
| else if (IsA(node, RangeTblFunction)) |
| { |
| RangeTblFunction *rtfunc = (RangeTblFunction *) node; |
| ListCell *ct; |
| |
| /* |
| * Add refs for any datatypes and collations used in a column |
| * definition list for a RECORD function. (For other cases, it should |
| * be enough to depend on the function itself.) |
| */ |
| foreach(ct, rtfunc->funccoltypes) |
| { |
| add_object_address(OCLASS_TYPE, lfirst_oid(ct), 0, |
| context->addrs); |
| } |
| foreach(ct, rtfunc->funccolcollations) |
| { |
| Oid collid = lfirst_oid(ct); |
| |
| if (OidIsValid(collid) && collid != DEFAULT_COLLATION_OID) |
| add_object_address(OCLASS_COLLATION, collid, 0, |
| context->addrs); |
| } |
| } |
| else if (IsA(node, TableFunc)) |
| { |
| TableFunc *tf = (TableFunc *) node; |
| ListCell *ct; |
| |
| /* |
| * Add refs for the datatypes and collations used in the TableFunc. |
| */ |
| foreach(ct, tf->coltypes) |
| { |
| add_object_address(OCLASS_TYPE, lfirst_oid(ct), 0, |
| context->addrs); |
| } |
| foreach(ct, tf->colcollations) |
| { |
| Oid collid = lfirst_oid(ct); |
| |
| if (OidIsValid(collid) && collid != DEFAULT_COLLATION_OID) |
| add_object_address(OCLASS_COLLATION, collid, 0, |
| context->addrs); |
| } |
| } |
| else if (IsA(node, TableSampleClause)) |
| { |
| TableSampleClause *tsc = (TableSampleClause *) node; |
| |
| add_object_address(OCLASS_PROC, tsc->tsmhandler, 0, |
| context->addrs); |
| /* fall through to examine arguments */ |
| } |
| |
| return expression_tree_walker(node, find_expr_references_walker, |
| (void *) context); |
| } |
| |
| /* |
| * find_expr_references_walker subroutine: handle a Var reference |
| * to an RTE_FUNCTION RTE |
| */ |
| static void |
| process_function_rte_ref(RangeTblEntry *rte, AttrNumber attnum, |
| find_expr_references_context *context) |
| { |
| int atts_done = 0; |
| ListCell *lc; |
| |
| /* |
| * Identify which RangeTblFunction produces this attnum, and see if it |
| * returns a composite type. If so, we'd better make a dependency on the |
| * referenced column of the composite type (or actually, of its associated |
| * relation). |
| */ |
| foreach(lc, rte->functions) |
| { |
| RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc); |
| |
| if (attnum > atts_done && |
| attnum <= atts_done + rtfunc->funccolcount) |
| { |
| TupleDesc tupdesc; |
| |
| /* If it has a coldeflist, it certainly returns RECORD */ |
| if (rtfunc->funccolnames != NIL) |
| tupdesc = NULL; /* no need to work hard */ |
| else |
| tupdesc = get_expr_result_tupdesc(rtfunc->funcexpr, true); |
| if (tupdesc && tupdesc->tdtypeid != RECORDOID) |
| { |
| /* |
| * Named composite type, so individual columns could get |
| * dropped. Make a dependency on this specific column. |
| */ |
| Oid reltype = get_typ_typrelid(tupdesc->tdtypeid); |
| |
| Assert(attnum - atts_done <= tupdesc->natts); |
| if (OidIsValid(reltype)) /* can this fail? */ |
| add_object_address(OCLASS_CLASS, reltype, |
| attnum - atts_done, |
| context->addrs); |
| return; |
| } |
| /* Nothing to do; function's result type is handled elsewhere */ |
| return; |
| } |
| atts_done += rtfunc->funccolcount; |
| } |
| |
| /* If we get here, must be looking for the ordinality column */ |
| if (rte->funcordinality && attnum == atts_done + 1) |
| return; |
| |
| /* this probably can't happen ... */ |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_COLUMN), |
| errmsg("column %d of relation \"%s\" does not exist", |
| attnum, rte->eref->aliasname))); |
| } |
| |
| /* |
| * Given an array of dependency references, eliminate any duplicates. |
| */ |
| static void |
| eliminate_duplicate_dependencies(ObjectAddresses *addrs) |
| { |
| ObjectAddress *priorobj; |
| int oldref, |
| newrefs; |
| |
| /* |
| * We can't sort if the array has "extra" data, because there's no way to |
| * keep it in sync. Fortunately that combination of features is not |
| * needed. |
| */ |
| Assert(!addrs->extras); |
| |
| if (addrs->numrefs <= 1) |
| return; /* nothing to do */ |
| |
| /* Sort the refs so that duplicates are adjacent */ |
| qsort(addrs->refs, addrs->numrefs, sizeof(ObjectAddress), |
| object_address_comparator); |
| |
| /* Remove dups */ |
| priorobj = addrs->refs; |
| newrefs = 1; |
| for (oldref = 1; oldref < addrs->numrefs; oldref++) |
| { |
| ObjectAddress *thisobj = addrs->refs + oldref; |
| |
| if (priorobj->classId == thisobj->classId && |
| priorobj->objectId == thisobj->objectId) |
| { |
| if (priorobj->objectSubId == thisobj->objectSubId) |
| continue; /* identical, so drop thisobj */ |
| |
| /* |
| * If we have a whole-object reference and a reference to a part |
| * of the same object, we don't need the whole-object reference |
| * (for example, we don't need to reference both table foo and |
| * column foo.bar). The whole-object reference will always appear |
| * first in the sorted list. |
| */ |
| if (priorobj->objectSubId == 0) |
| { |
| /* replace whole ref with partial */ |
| priorobj->objectSubId = thisobj->objectSubId; |
| continue; |
| } |
| } |
| /* Not identical, so add thisobj to output set */ |
| priorobj++; |
| *priorobj = *thisobj; |
| newrefs++; |
| } |
| |
| addrs->numrefs = newrefs; |
| } |
| |
| /* |
| * qsort comparator for ObjectAddress items |
| */ |
| static int |
| object_address_comparator(const void *a, const void *b) |
| { |
| const ObjectAddress *obja = (const ObjectAddress *) a; |
| const ObjectAddress *objb = (const ObjectAddress *) b; |
| |
| /* |
| * Primary sort key is OID descending. Most of the time, this will result |
| * in putting newer objects before older ones, which is likely to be the |
| * right order to delete in. |
| */ |
| if (obja->objectId > objb->objectId) |
| return -1; |
| if (obja->objectId < objb->objectId) |
| return 1; |
| |
| /* |
| * Next sort on catalog ID, in case identical OIDs appear in different |
| * catalogs. Sort direction is pretty arbitrary here. |
| */ |
| if (obja->classId < objb->classId) |
| return -1; |
| if (obja->classId > objb->classId) |
| return 1; |
| |
| /* |
| * Last, sort on object subId. |
| * |
| * We sort the subId as an unsigned int so that 0 (the whole object) will |
| * come first. This is essential for eliminate_duplicate_dependencies, |
| * and is also the best order for findDependentObjects. |
| */ |
| if ((unsigned int) obja->objectSubId < (unsigned int) objb->objectSubId) |
| return -1; |
| if ((unsigned int) obja->objectSubId > (unsigned int) objb->objectSubId) |
| return 1; |
| return 0; |
| } |
| |
| /* |
| * Routines for handling an expansible array of ObjectAddress items. |
| * |
| * new_object_addresses: create a new ObjectAddresses array. |
| */ |
| ObjectAddresses * |
| new_object_addresses(void) |
| { |
| ObjectAddresses *addrs; |
| |
| addrs = palloc(sizeof(ObjectAddresses)); |
| |
| addrs->numrefs = 0; |
| addrs->maxrefs = 32; |
| addrs->refs = (ObjectAddress *) |
| palloc(addrs->maxrefs * sizeof(ObjectAddress)); |
| addrs->extras = NULL; /* until/unless needed */ |
| |
| return addrs; |
| } |
| |
| /* |
| * Add an entry to an ObjectAddresses array. |
| * |
| * It is convenient to specify the class by ObjectClass rather than directly |
| * by catalog OID. |
| */ |
| static void |
| add_object_address(ObjectClass oclass, Oid objectId, int32 subId, |
| ObjectAddresses *addrs) |
| { |
| ObjectAddress *item; |
| |
| /* enlarge array if needed */ |
| if (addrs->numrefs >= addrs->maxrefs) |
| { |
| addrs->maxrefs *= 2; |
| addrs->refs = (ObjectAddress *) |
| repalloc(addrs->refs, addrs->maxrefs * sizeof(ObjectAddress)); |
| Assert(!addrs->extras); |
| } |
| /* record this item */ |
| item = addrs->refs + addrs->numrefs; |
| item->classId = object_classes[oclass]; |
| item->objectId = objectId; |
| item->objectSubId = subId; |
| addrs->numrefs++; |
| } |
| |
| /* |
| * Add an entry to an ObjectAddresses array. |
| * |
| * As above, but specify entry exactly. |
| */ |
| void |
| add_exact_object_address(const ObjectAddress *object, |
| ObjectAddresses *addrs) |
| { |
| ObjectAddress *item; |
| |
| /* enlarge array if needed */ |
| if (addrs->numrefs >= addrs->maxrefs) |
| { |
| addrs->maxrefs *= 2; |
| addrs->refs = (ObjectAddress *) |
| repalloc(addrs->refs, addrs->maxrefs * sizeof(ObjectAddress)); |
| Assert(!addrs->extras); |
| } |
| /* record this item */ |
| item = addrs->refs + addrs->numrefs; |
| *item = *object; |
| addrs->numrefs++; |
| } |
| |
| /* |
| * Add an entry to an ObjectAddresses array. |
| * |
| * As above, but specify entry exactly and provide some "extra" data too. |
| */ |
| static void |
| add_exact_object_address_extra(const ObjectAddress *object, |
| const ObjectAddressExtra *extra, |
| ObjectAddresses *addrs) |
| { |
| ObjectAddress *item; |
| ObjectAddressExtra *itemextra; |
| |
| /* allocate extra space if first time */ |
| if (!addrs->extras) |
| addrs->extras = (ObjectAddressExtra *) |
| palloc(addrs->maxrefs * sizeof(ObjectAddressExtra)); |
| |
| /* enlarge array if needed */ |
| if (addrs->numrefs >= addrs->maxrefs) |
| { |
| addrs->maxrefs *= 2; |
| addrs->refs = (ObjectAddress *) |
| repalloc(addrs->refs, addrs->maxrefs * sizeof(ObjectAddress)); |
| addrs->extras = (ObjectAddressExtra *) |
| repalloc(addrs->extras, addrs->maxrefs * sizeof(ObjectAddressExtra)); |
| } |
| /* record this item */ |
| item = addrs->refs + addrs->numrefs; |
| *item = *object; |
| itemextra = addrs->extras + addrs->numrefs; |
| *itemextra = *extra; |
| addrs->numrefs++; |
| } |
| |
| /* |
| * Test whether an object is present in an ObjectAddresses array. |
| * |
| * We return "true" if object is a subobject of something in the array, too. |
| */ |
| bool |
| object_address_present(const ObjectAddress *object, |
| const ObjectAddresses *addrs) |
| { |
| int i; |
| |
| for (i = addrs->numrefs - 1; i >= 0; i--) |
| { |
| const ObjectAddress *thisobj = addrs->refs + i; |
| |
| if (object->classId == thisobj->classId && |
| object->objectId == thisobj->objectId) |
| { |
| if (object->objectSubId == thisobj->objectSubId || |
| thisobj->objectSubId == 0) |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /* |
| * As above, except that if the object is present then also OR the given |
| * flags into its associated extra data (which must exist). |
| */ |
| static bool |
| object_address_present_add_flags(const ObjectAddress *object, |
| int flags, |
| ObjectAddresses *addrs) |
| { |
| bool result = false; |
| int i; |
| |
| for (i = addrs->numrefs - 1; i >= 0; i--) |
| { |
| ObjectAddress *thisobj = addrs->refs + i; |
| |
| if (object->classId == thisobj->classId && |
| object->objectId == thisobj->objectId) |
| { |
| if (object->objectSubId == thisobj->objectSubId) |
| { |
| ObjectAddressExtra *thisextra = addrs->extras + i; |
| |
| thisextra->flags |= flags; |
| result = true; |
| } |
| else if (thisobj->objectSubId == 0) |
| { |
| /* |
| * We get here if we find a need to delete a column after |
| * having already decided to drop its whole table. Obviously |
| * we no longer need to drop the subobject, so report that we |
| * found the subobject in the array. But don't plaster its |
| * flags on the whole object. |
| */ |
| result = true; |
| } |
| else if (object->objectSubId == 0) |
| { |
| /* |
| * We get here if we find a need to delete a whole table after |
| * having already decided to drop one of its columns. We |
| * can't report that the whole object is in the array, but we |
| * should mark the subobject with the whole object's flags. |
| * |
| * It might seem attractive to physically delete the column's |
| * array entry, or at least mark it as no longer needing |
| * separate deletion. But that could lead to, e.g., dropping |
| * the column's datatype before we drop the table, which does |
| * not seem like a good idea. This is a very rare situation |
| * in practice, so we just take the hit of doing a separate |
| * DROP COLUMN action even though we know we're gonna delete |
| * the table later. |
| * |
| * What we can do, though, is mark this as a subobject so that |
| * we don't report it separately, which is confusing because |
| * it's unpredictable whether it happens or not. But do so |
| * only if flags != 0 (flags == 0 is a read-only probe). |
| * |
| * Because there could be other subobjects of this object in |
| * the array, this case means we always have to loop through |
| * the whole array; we cannot exit early on a match. |
| */ |
| ObjectAddressExtra *thisextra = addrs->extras + i; |
| |
| if (flags) |
| thisextra->flags |= (flags | DEPFLAG_SUBOBJECT); |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| /* |
| * Similar to above, except we search an ObjectAddressStack. |
| */ |
| static bool |
| stack_address_present_add_flags(const ObjectAddress *object, |
| int flags, |
| ObjectAddressStack *stack) |
| { |
| bool result = false; |
| ObjectAddressStack *stackptr; |
| |
| for (stackptr = stack; stackptr; stackptr = stackptr->next) |
| { |
| const ObjectAddress *thisobj = stackptr->object; |
| |
| if (object->classId == thisobj->classId && |
| object->objectId == thisobj->objectId) |
| { |
| if (object->objectSubId == thisobj->objectSubId) |
| { |
| stackptr->flags |= flags; |
| result = true; |
| } |
| else if (thisobj->objectSubId == 0) |
| { |
| /* |
| * We're visiting a column with whole table already on stack. |
| * As in object_address_present_add_flags(), we can skip |
| * further processing of the subobject, but we don't want to |
| * propagate flags for the subobject to the whole object. |
| */ |
| result = true; |
| } |
| else if (object->objectSubId == 0) |
| { |
| /* |
| * We're visiting a table with column already on stack. As in |
| * object_address_present_add_flags(), we should propagate |
| * flags for the whole object to each of its subobjects. |
| */ |
| if (flags) |
| stackptr->flags |= (flags | DEPFLAG_SUBOBJECT); |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| /* |
| * Record multiple dependencies from an ObjectAddresses array, after first |
| * removing any duplicates. |
| */ |
| void |
| record_object_address_dependencies(const ObjectAddress *depender, |
| ObjectAddresses *referenced, |
| DependencyType behavior) |
| { |
| eliminate_duplicate_dependencies(referenced); |
| recordMultipleDependencies(depender, |
| referenced->refs, referenced->numrefs, |
| behavior); |
| } |
| |
| /* |
| * Sort the items in an ObjectAddresses array. |
| * |
| * The major sort key is OID-descending, so that newer objects will be listed |
| * first in most cases. This is primarily useful for ensuring stable outputs |
| * from regression tests; it's not recommended if the order of the objects is |
| * determined by user input, such as the order of targets in a DROP command. |
| */ |
| void |
| sort_object_addresses(ObjectAddresses *addrs) |
| { |
| if (addrs->numrefs > 1) |
| qsort(addrs->refs, addrs->numrefs, |
| sizeof(ObjectAddress), |
| object_address_comparator); |
| } |
| |
| /* |
| * Clean up when done with an ObjectAddresses array. |
| */ |
| void |
| free_object_addresses(ObjectAddresses *addrs) |
| { |
| pfree(addrs->refs); |
| if (addrs->extras) |
| pfree(addrs->extras); |
| pfree(addrs); |
| } |
| |
| /* |
| * Determine the class of a given object identified by objectAddress. |
| * |
| * This function is essentially the reverse mapping for the object_classes[] |
| * table. We implement it as a function because the OIDs aren't consecutive. |
| */ |
| ObjectClass |
| getObjectClass(const ObjectAddress *object) |
| { |
| /* only pg_class entries can have nonzero objectSubId */ |
| if (object->classId != RelationRelationId && |
| object->objectSubId != 0) |
| elog(ERROR, "invalid non-zero objectSubId for object class %u", |
| object->classId); |
| |
| switch (object->classId) |
| { |
| case RelationRelationId: |
| /* caller must check objectSubId */ |
| return OCLASS_CLASS; |
| |
| case ProcedureRelationId: |
| return OCLASS_PROC; |
| |
| case TypeRelationId: |
| return OCLASS_TYPE; |
| |
| case CastRelationId: |
| return OCLASS_CAST; |
| |
| case CollationRelationId: |
| return OCLASS_COLLATION; |
| |
| case ConstraintRelationId: |
| return OCLASS_CONSTRAINT; |
| |
| case ConversionRelationId: |
| return OCLASS_CONVERSION; |
| |
| case AttrDefaultRelationId: |
| return OCLASS_DEFAULT; |
| |
| case LanguageRelationId: |
| return OCLASS_LANGUAGE; |
| |
| case LargeObjectRelationId: |
| return OCLASS_LARGEOBJECT; |
| |
| case OperatorRelationId: |
| return OCLASS_OPERATOR; |
| |
| case OperatorClassRelationId: |
| return OCLASS_OPCLASS; |
| |
| case OperatorFamilyRelationId: |
| return OCLASS_OPFAMILY; |
| |
| case AccessMethodRelationId: |
| return OCLASS_AM; |
| |
| case AccessMethodOperatorRelationId: |
| return OCLASS_AMOP; |
| |
| case AccessMethodProcedureRelationId: |
| return OCLASS_AMPROC; |
| |
| case RewriteRelationId: |
| return OCLASS_REWRITE; |
| |
| case TriggerRelationId: |
| return OCLASS_TRIGGER; |
| |
| case NamespaceRelationId: |
| return OCLASS_SCHEMA; |
| |
| case StatisticExtRelationId: |
| return OCLASS_STATISTIC_EXT; |
| |
| case TSParserRelationId: |
| return OCLASS_TSPARSER; |
| |
| case TSDictionaryRelationId: |
| return OCLASS_TSDICT; |
| |
| case TSTemplateRelationId: |
| return OCLASS_TSTEMPLATE; |
| |
| case TSConfigRelationId: |
| return OCLASS_TSCONFIG; |
| |
| case AuthIdRelationId: |
| return OCLASS_ROLE; |
| |
| case AuthMemRelationId: |
| return OCLASS_ROLE_MEMBERSHIP; |
| |
| case DatabaseRelationId: |
| return OCLASS_DATABASE; |
| |
| case TableSpaceRelationId: |
| return OCLASS_TBLSPACE; |
| |
| case ForeignDataWrapperRelationId: |
| return OCLASS_FDW; |
| |
| case ForeignServerRelationId: |
| return OCLASS_FOREIGN_SERVER; |
| |
| case UserMappingRelationId: |
| return OCLASS_USER_MAPPING; |
| |
| case DefaultAclRelationId: |
| return OCLASS_DEFACL; |
| |
| case ExtensionRelationId: |
| return OCLASS_EXTENSION; |
| |
| case EventTriggerRelationId: |
| return OCLASS_EVENT_TRIGGER; |
| |
| case ExtprotocolRelationId: |
| Assert(object->objectSubId == 0); |
| return OCLASS_EXTPROTOCOL; |
| |
| case ProfileRelationId: |
| return OCLASS_PROFILE; |
| |
| case PasswordHistoryRelationId: |
| return OCLASS_PASSWORDHISTORY; |
| |
| case ParameterAclRelationId: |
| return OCLASS_PARAMETER_ACL; |
| |
| case PolicyRelationId: |
| return OCLASS_POLICY; |
| |
| case PublicationNamespaceRelationId: |
| return OCLASS_PUBLICATION_NAMESPACE; |
| |
| case PublicationRelationId: |
| return OCLASS_PUBLICATION; |
| |
| case PublicationRelRelationId: |
| return OCLASS_PUBLICATION_REL; |
| |
| case SubscriptionRelationId: |
| return OCLASS_SUBSCRIPTION; |
| |
| case TransformRelationId: |
| return OCLASS_TRANSFORM; |
| |
| case TaskRelationId: |
| return OCLASS_TASK; |
| |
| case GpMatviewAuxId: |
| return OCLASS_MATVIEW_AUX; |
| |
| case DirectoryTableRelationId: |
| return OCLASS_DIRTABLE; |
| |
| case StorageServerRelationId: |
| return OCLASS_STORAGE_SERVER; |
| |
| case StorageUserMappingRelationId: |
| return OCLASS_STORAGE_USER_MAPPING; |
| |
| case TagRelationId: |
| return OCLASS_TAG; |
| |
| case TagDescriptionRelationId: |
| return OCLASS_TAG_DESCRIPTION; |
| |
| default: |
| { |
| struct CustomObjectClass *coc; |
| coc = find_custom_object_class_by_classid(object->classId, true); |
| if (coc) |
| return coc->oclass; |
| break; |
| } |
| } |
| |
| /* shouldn't get here */ |
| elog(ERROR, "unrecognized object class: %u", object->classId); |
| return OCLASS_CLASS; /* keep compiler quiet */ |
| } |
| |
| /* check if there are dependencies on the objects provides, error out if exists*/ |
| void |
| checkDependencies(const ObjectAddresses *objects, |
| const char *msg, |
| const char *hint) |
| { |
| Relation depRel; |
| ObjectAddresses *targetObjects; |
| StringInfoData clientdetail; |
| bool ok = true; |
| int i; |
| int numReportedClient = 0; |
| |
| /* |
| * We save some cycles by opening pg_depend just once and passing the |
| * Relation pointer down to all the recursive deletion steps. |
| */ |
| depRel = heap_open(DependRelationId, RowExclusiveLock); |
| |
| targetObjects = new_object_addresses(); |
| |
| for (i = 0; i < objects->numrefs; i++) |
| { |
| const ObjectAddress *thisobj = objects->refs + i; |
| |
| /* |
| * Acquire deletion lock on each target object. (Ideally the caller |
| * has done this already, but many places are sloppy about it.) |
| */ |
| AcquireDeletionLock(thisobj, 0); |
| |
| findDependentObjects(thisobj, |
| DEPFLAG_ORIGINAL, |
| 0, |
| NULL, /* empty stack */ |
| targetObjects, |
| objects, |
| &depRel); |
| } |
| |
| /* |
| * We limit the number of dependencies reported to the client to |
| * MAX_REPORTED_DEPS, since client software may not deal well with |
| * enormous error strings. The server log always gets a full report. |
| */ |
| #define MAX_REPORTED_DEPS 100 |
| |
| initStringInfo(&clientdetail); |
| |
| for (i = targetObjects->numrefs - 1; i >= 0; i--) |
| { |
| const ObjectAddress *obj = &targetObjects->refs[i]; |
| const ObjectAddressExtra *extra = &targetObjects->extras[i]; |
| char *otherDesc; |
| char *objDesc; |
| |
| if (extra->flags & (DEPFLAG_ORIGINAL | |
| DEPFLAG_AUTO | |
| DEPFLAG_INTERNAL | |
| DEPFLAG_EXTENSION)) |
| continue; |
| |
| objDesc = getObjectDescription(obj, false); |
| otherDesc = getObjectDescription(&extra->dependee, false); |
| |
| if (numReportedClient < MAX_REPORTED_DEPS) |
| { |
| /* separate entries with a newline */ |
| if (clientdetail.len != 0) |
| appendStringInfoChar(&clientdetail, '\n'); |
| appendStringInfo(&clientdetail, _("%s depends on %s"), |
| objDesc, otherDesc); |
| numReportedClient++; |
| } |
| pfree(objDesc); |
| pfree(otherDesc); |
| ok = false; |
| } |
| |
| if (!ok) |
| ereport(ERROR, |
| (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), |
| errmsg("%s", msg), |
| errdetail("%s", clientdetail.data), |
| errhint("%s", hint))); |
| |
| pfree(clientdetail.data); |
| |
| /* And clean up */ |
| free_object_addresses(targetObjects); |
| |
| heap_close(depRel, RowExclusiveLock); |
| } |
| |
| /* |
| * delete initial ACL for extension objects |
| */ |
| static void |
| DeleteInitPrivs(const ObjectAddress *object) |
| { |
| Relation relation; |
| ScanKeyData key[3]; |
| int nkeys; |
| SysScanDesc scan; |
| HeapTuple oldtuple; |
| |
| relation = table_open(InitPrivsRelationId, RowExclusiveLock); |
| |
| ScanKeyInit(&key[0], |
| Anum_pg_init_privs_objoid, |
| BTEqualStrategyNumber, F_OIDEQ, |
| ObjectIdGetDatum(object->objectId)); |
| ScanKeyInit(&key[1], |
| Anum_pg_init_privs_classoid, |
| BTEqualStrategyNumber, F_OIDEQ, |
| ObjectIdGetDatum(object->classId)); |
| if (object->objectSubId != 0) |
| { |
| ScanKeyInit(&key[2], |
| Anum_pg_init_privs_objsubid, |
| BTEqualStrategyNumber, F_INT4EQ, |
| Int32GetDatum(object->objectSubId)); |
| nkeys = 3; |
| } |
| else |
| nkeys = 2; |
| |
| scan = systable_beginscan(relation, InitPrivsObjIndexId, true, |
| NULL, nkeys, key); |
| |
| while (HeapTupleIsValid(oldtuple = systable_getnext(scan))) |
| CatalogTupleDelete(relation, &oldtuple->t_self); |
| |
| systable_endscan(scan); |
| |
| table_close(relation, RowExclusiveLock); |
| } |
| |
| /* Custom object class */ |
| #define FIRST_COSTOM_OBJECT_CLASS 512 |
| static List *custom_object_class_list = NIL; |
| |
| /* |
| * don't support unregister object class in a session. |
| * Return: the oclass allocated by the kernel |
| */ |
| int |
| register_custom_object_class(struct CustomObjectClass *coc) |
| { |
| MemoryContext saved_ctx; |
| /* check class_id and required callbacks */ |
| Assert(OidIsValid(coc->class_id)); |
| Assert(coc->do_delete); |
| |
| if (find_custom_object_class_by_classid(coc->class_id, true)) |
| elog(ERROR, "class_id %u exists already", coc->class_id); |
| |
| coc->oclass = FIRST_COSTOM_OBJECT_CLASS + list_length(custom_object_class_list); |
| saved_ctx = MemoryContextSwitchTo(TopMemoryContext); |
| custom_object_class_list = lappend(custom_object_class_list, coc); |
| MemoryContextSwitchTo(saved_ctx); |
| return coc->oclass; |
| } |
| |
| struct CustomObjectClass * |
| find_custom_object_class(int oclass) |
| { |
| int index = oclass - FIRST_COSTOM_OBJECT_CLASS; |
| |
| if (oclass < FIRST_COSTOM_OBJECT_CLASS) |
| elog(ERROR, "unexpected oclass, found builtin one %u", oclass); |
| if (index >= list_length(custom_object_class_list)) |
| elog(ERROR, "unexpected oclass beyond custom object class %u", oclass); |
| |
| return (struct CustomObjectClass *) list_nth(custom_object_class_list, index); |
| } |
| |
| struct CustomObjectClass * |
| find_custom_object_class_by_classid(Oid class_id, bool missing_ok) |
| { |
| ListCell *lc; |
| |
| #ifdef USE_ASSERT_CHECKING |
| int n = lengthof(object_classes); |
| Assert(n < FIRST_COSTOM_OBJECT_CLASS); |
| |
| for (int i = 0; i < n; i++) { |
| if (object_classes[i] == class_id) |
| elog(ERROR, "find builtin object class by class id %u", class_id); |
| } |
| #endif |
| |
| foreach(lc, custom_object_class_list) { |
| struct CustomObjectClass *coc = (struct CustomObjectClass *)lfirst(lc); |
| if (coc->class_id == class_id) |
| return coc; |
| } |
| if (!missing_ok) |
| ereport(ERROR, (errmsg("found no custom object class for class_id=%u", class_id))); |
| return NULL; |
| } |