aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/backend/access/index/genam.c43
-rw-r--r--src/backend/catalog/dependency.c1149
-rw-r--r--src/include/access/genam.h3
-rw-r--r--src/include/catalog/dependency.h7
-rw-r--r--src/test/regress/expected/alter_table.out14
-rw-r--r--src/test/regress/expected/create_view.out95
-rw-r--r--src/test/regress/expected/domain.out2
-rw-r--r--src/test/regress/expected/foreign_key.out16
-rw-r--r--src/test/regress/expected/inherit.out55
-rw-r--r--src/test/regress/expected/namespace.out4
-rw-r--r--src/test/regress/expected/plancache.out3
-rw-r--r--src/test/regress/expected/privileges.out1
-rw-r--r--src/test/regress/expected/subselect.out1
-rw-r--r--src/test/regress/expected/truncate.out4
-rw-r--r--src/test/regress/output/tablespace.source6
15 files changed, 747 insertions, 656 deletions
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index e8958abca2b..301062951b6 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/index/genam.c,v 1.68 2008/04/13 19:18:14 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/access/index/genam.c,v 1.69 2008/06/08 22:41:04 tgl Exp $
*
* NOTES
* many of the old access method routines have been turned into
@@ -252,6 +252,47 @@ systable_getnext(SysScanDesc sysscan)
}
/*
+ * systable_recheck_tuple --- recheck visibility of most-recently-fetched tuple
+ *
+ * This is useful to test whether an object was deleted while we waited to
+ * acquire lock on it.
+ *
+ * Note: we don't actually *need* the tuple to be passed in, but it's a
+ * good crosscheck that the caller is interested in the right tuple.
+ */
+bool
+systable_recheck_tuple(SysScanDesc sysscan, HeapTuple tup)
+{
+ bool result;
+
+ if (sysscan->irel)
+ {
+ IndexScanDesc scan = sysscan->iscan;
+
+ Assert(tup == &scan->xs_ctup);
+ Assert(BufferIsValid(scan->xs_cbuf));
+ /* must hold a buffer lock to call HeapTupleSatisfiesVisibility */
+ LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE);
+ result = HeapTupleSatisfiesVisibility(tup, scan->xs_snapshot,
+ scan->xs_cbuf);
+ LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK);
+ }
+ else
+ {
+ HeapScanDesc scan = sysscan->scan;
+
+ Assert(tup == &scan->rs_ctup);
+ Assert(BufferIsValid(scan->rs_cbuf));
+ /* must hold a buffer lock to call HeapTupleSatisfiesVisibility */
+ LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
+ result = HeapTupleSatisfiesVisibility(tup, scan->rs_snapshot,
+ scan->rs_cbuf);
+ LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+ }
+ return result;
+}
+
+/*
* systable_endscan --- close scan, release resources
*
* Note that it's still up to the caller to close the heap relation.
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 24423d2837e..12b654504d7 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.73 2008/06/05 15:04:39 alvherre Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.74 2008/06/08 22:41:04 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -59,6 +59,7 @@
#include "optimizer/clauses.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteRemove.h"
+#include "storage/lmgr.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
@@ -66,16 +67,43 @@
#include "utils/tqual.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 */
+
+
/* 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 */
+ 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;
+
/* for find_expr_references_walker */
typedef struct
{
@@ -114,33 +142,32 @@ static const Oid object_classes[MAX_OCLASS] = {
};
-static void performDeletionWithList(const ObjectAddress *object,
- ObjectAddresses *oktodelete,
- DropBehavior behavior,
- ObjectAddresses *alreadyDeleted);
-static void findAutoDeletableObjects(const ObjectAddress *object,
- ObjectAddresses *oktodelete,
- Relation depRel, bool addself);
-static bool recursiveDeletion(const ObjectAddress *object,
- DropBehavior behavior,
- int msglevel,
- const ObjectAddress *callingObject,
- ObjectAddresses *oktodelete,
- Relation depRel,
- ObjectAddresses *alreadyDeleted);
-static bool deleteDependentObjects(const ObjectAddress *object,
- const char *objDescription,
+static void findDependentObjects(const ObjectAddress *object,
+ int flags,
+ ObjectAddressStack *stack,
+ ObjectAddresses *targetObjects,
+ const ObjectAddresses *pendingObjects,
+ Relation depRel);
+static void reportDependentObjects(const ObjectAddresses *targetObjects,
DropBehavior behavior,
int msglevel,
- ObjectAddresses *oktodelete,
- Relation depRel);
+ const ObjectAddress *origObject);
+static void deleteOneObject(const ObjectAddress *object, Relation depRel);
static void doDeletion(const ObjectAddress *object);
+static void AcquireDeletionLock(const ObjectAddress *object);
+static void ReleaseDeletionLock(const ObjectAddress *object);
static bool find_expr_references_walker(Node *node,
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 void getRelationDescription(StringInfo buffer, Oid relid);
static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
@@ -153,21 +180,17 @@ static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
* according to the dependency type.
*
* This is the outer control routine for all forms of DROP that drop objects
- * that can participate in dependencies.
+ * that can participate in dependencies. Note that the next two routines
+ * are variants on the same theme; if you change anything here you'll likely
+ * need to fix them too.
*/
void
performDeletion(const ObjectAddress *object,
DropBehavior behavior)
{
- char *objDescription;
Relation depRel;
- ObjectAddresses *oktodelete;
-
- /*
- * Get object description for possible use in failure message. Must do
- * this before deleting it ...
- */
- objDescription = getObjectDescription(object);
+ ObjectAddresses *targetObjects;
+ int i;
/*
* We save some cycles by opening pg_depend just once and passing the
@@ -176,83 +199,50 @@ performDeletion(const ObjectAddress *object,
depRel = heap_open(DependRelationId, RowExclusiveLock);
/*
- * Construct a list of objects that are reachable by AUTO or INTERNAL
- * dependencies from the target object. These should be deleted silently,
- * even if the actual deletion pass first reaches one of them via a
- * non-auto dependency.
+ * Acquire deletion lock on the target object. (Ideally the caller has
+ * done this already, but many places are sloppy about it.)
*/
- oktodelete = new_object_addresses();
-
- findAutoDeletableObjects(object, oktodelete, depRel, true);
-
- if (!recursiveDeletion(object, behavior, NOTICE,
- NULL, oktodelete, depRel, NULL))
- ereport(ERROR,
- (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
- errmsg("cannot drop %s because other objects depend on it",
- objDescription),
- errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
-
- free_object_addresses(oktodelete);
-
- heap_close(depRel, RowExclusiveLock);
-
- pfree(objDescription);
-}
-
-
-/*
- * performDeletionWithList: As above, but the oktodelete list may have already
- * filled with some objects. Also, the deleted objects are saved in the
- * alreadyDeleted list.
- *
- * XXX performDeletion could be refactored to be a thin wrapper around this
- * function.
- */
-static void
-performDeletionWithList(const ObjectAddress *object,
- ObjectAddresses *oktodelete,
- DropBehavior behavior,
- ObjectAddresses *alreadyDeleted)
-{
- char *objDescription;
- Relation depRel;
+ AcquireDeletionLock(object);
/*
- * Get object description for possible use in failure message. Must do
- * this before deleting it ...
+ * Construct a list of objects to delete (ie, the given object plus
+ * everything directly or indirectly dependent on it).
*/
- objDescription = getObjectDescription(object);
+ targetObjects = new_object_addresses();
+
+ findDependentObjects(object,
+ DEPFLAG_ORIGINAL,
+ NULL, /* empty stack */
+ targetObjects,
+ NULL, /* no pendingObjects */
+ depRel);
/*
- * We save some cycles by opening pg_depend just once and passing the
- * Relation pointer down to all the recursive deletion steps.
+ * Check if deletion is allowed, and report about cascaded deletes.
*/
- depRel = heap_open(DependRelationId, RowExclusiveLock);
+ reportDependentObjects(targetObjects,
+ behavior,
+ NOTICE,
+ object);
/*
- * Construct a list of objects that are reachable by AUTO or INTERNAL
- * dependencies from the target object. These should be deleted silently,
- * even if the actual deletion pass first reaches one of them via a
- * non-auto dependency.
+ * Delete all the objects in the proper order.
*/
- findAutoDeletableObjects(object, oktodelete, depRel, true);
+ for (i = 0; i < targetObjects->numrefs; i++)
+ {
+ ObjectAddress *thisobj = targetObjects->refs + i;
- if (!recursiveDeletion(object, behavior, NOTICE,
- NULL, oktodelete, depRel, alreadyDeleted))
- ereport(ERROR,
- (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
- errmsg("cannot drop %s because other objects depend on it",
- objDescription),
- errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
+ deleteOneObject(thisobj, depRel);
+ }
- heap_close(depRel, RowExclusiveLock);
+ /* And clean up */
+ free_object_addresses(targetObjects);
- pfree(objDescription);
+ heap_close(depRel, RowExclusiveLock);
}
/*
- * performMultipleDeletion: Similar to performDeletion, but act on multiple
+ * performMultipleDeletions: Similar to performDeletion, but act on multiple
* objects at once.
*
* The main difference from issuing multiple performDeletion calls is that the
@@ -264,67 +254,66 @@ void
performMultipleDeletions(const ObjectAddresses *objects,
DropBehavior behavior)
{
- ObjectAddresses *implicit;
- ObjectAddresses *alreadyDeleted;
Relation depRel;
+ ObjectAddresses *targetObjects;
int i;
- implicit = new_object_addresses();
- alreadyDeleted = new_object_addresses();
-
+ /*
+ * 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);
/*
- * Get the list of all objects that would be deleted after deleting the
- * whole "objects" list. We do this by creating a list of all implicit
- * (INTERNAL and AUTO) dependencies for each object we collected above.
- * Note that we must exclude the objects themselves from this list!
+ * 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++)
{
- ObjectAddress obj = objects->refs[i];
+ const ObjectAddress *thisobj = objects->refs + i;
/*
- * If it's in the implicit list, we don't need to delete it explicitly
- * nor follow the dependencies, because that was already done in a
- * previous iteration.
+ * Acquire deletion lock on each target object. (Ideally the caller
+ * has done this already, but many places are sloppy about it.)
*/
- if (object_address_present(&obj, implicit))
- continue;
-
- /*
- * Add the objects dependent on this one to the global list of
- * implicit objects.
- */
- findAutoDeletableObjects(&obj, implicit, depRel, false);
+ AcquireDeletionLock(thisobj);
+
+ findDependentObjects(thisobj,
+ DEPFLAG_ORIGINAL,
+ NULL, /* empty stack */
+ targetObjects,
+ objects,
+ depRel);
}
- /* Do the deletion. */
- for (i = 0; i < objects->numrefs; i++)
- {
- ObjectAddress obj = objects->refs[i];
-
- /*
- * Skip this object if it was already deleted in a previous iteration.
- */
- if (object_address_present(&obj, alreadyDeleted))
- continue;
+ /*
+ * Check if deletion is allowed, and report about cascaded deletes.
+ */
+ reportDependentObjects(targetObjects,
+ behavior,
+ NOTICE,
+ NULL);
- /*
- * Skip this object if it's also present in the list of implicit
- * objects --- it will be deleted later.
- */
- if (object_address_present(&obj, implicit))
- continue;
+ /*
+ * Delete all the objects in the proper order.
+ */
+ for (i = 0; i < targetObjects->numrefs; i++)
+ {
+ ObjectAddress *thisobj = targetObjects->refs + i;
- /* delete it */
- performDeletionWithList(&obj, implicit, behavior, alreadyDeleted);
+ deleteOneObject(thisobj, depRel);
}
- heap_close(depRel, RowExclusiveLock);
+ /* And clean up */
+ free_object_addresses(targetObjects);
- free_object_addresses(implicit);
- free_object_addresses(alreadyDeleted);
+ heap_close(depRel, RowExclusiveLock);
}
/*
@@ -340,14 +329,9 @@ void
deleteWhatDependsOn(const ObjectAddress *object,
bool showNotices)
{
- char *objDescription;
Relation depRel;
- ObjectAddresses *oktodelete;
-
- /*
- * Get object description for possible use in failure messages
- */
- objDescription = getObjectDescription(object);
+ ObjectAddresses *targetObjects;
+ int i;
/*
* We save some cycles by opening pg_depend just once and passing the
@@ -356,213 +340,155 @@ deleteWhatDependsOn(const ObjectAddress *object,
depRel = heap_open(DependRelationId, RowExclusiveLock);
/*
- * Construct a list of objects that are reachable by AUTO or INTERNAL
- * dependencies from the target object. These should be deleted silently,
- * even if the actual deletion pass first reaches one of them via a
- * non-auto dependency.
+ * Acquire deletion lock on the target object. (Ideally the caller has
+ * done this already, but many places are sloppy about it.)
*/
- oktodelete = new_object_addresses();
-
- findAutoDeletableObjects(object, oktodelete, depRel, true);
-
- /*
- * Now invoke only step 2 of recursiveDeletion: just recurse to the stuff
- * dependent on the given object.
- */
- if (!deleteDependentObjects(object, objDescription,
- DROP_CASCADE,
- showNotices ? NOTICE : DEBUG2,
- oktodelete, depRel))
- ereport(ERROR,
- (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
- errmsg("failed to drop all objects depending on %s",
- objDescription)));
+ AcquireDeletionLock(object);
/*
- * We do not need CommandCounterIncrement here, since if step 2 did
- * anything then each recursive call will have ended with one.
+ * Construct a list of objects to delete (ie, the given object plus
+ * everything directly or indirectly dependent on it).
*/
+ targetObjects = new_object_addresses();
- free_object_addresses(oktodelete);
-
- heap_close(depRel, RowExclusiveLock);
-
- pfree(objDescription);
-}
-
-
-/*
- * findAutoDeletableObjects: find all objects that are reachable by AUTO or
- * INTERNAL dependency paths from the given object. Add them all to the
- * oktodelete list. If addself is true, the originally given object will also
- * be added to the list.
- *
- * depRel is the already-open pg_depend relation.
- */
-static void
-findAutoDeletableObjects(const ObjectAddress *object,
- ObjectAddresses *oktodelete,
- Relation depRel, bool addself)
-{
- ScanKeyData key[3];
- int nkeys;
- SysScanDesc scan;
- HeapTuple tup;
- ObjectAddress otherObject;
+ findDependentObjects(object,
+ DEPFLAG_ORIGINAL,
+ NULL, /* empty stack */
+ targetObjects,
+ NULL, /* no pendingObjects */
+ depRel);
/*
- * If this object is already in oktodelete, then we already visited it;
- * don't do so again (this prevents infinite recursion if there's a loop
- * in pg_depend). Otherwise, add it.
+ * Check if deletion is allowed, and report about cascaded deletes.
*/
- if (object_address_present(object, oktodelete))
- return;
- if (addself)
- add_exact_object_address(object, oktodelete);
+ reportDependentObjects(targetObjects,
+ DROP_CASCADE,
+ showNotices ? NOTICE : DEBUG2,
+ object);
/*
- * Scan pg_depend records that link to this object, showing the things
- * that depend on it. For each one that is AUTO or INTERNAL, visit the
- * referencing object.
- *
- * When dropping a whole object (subId = 0), find pg_depend records for
- * its sub-objects too.
+ * Delete all the objects in the proper order, except we skip the original
+ * object.
*/
- 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)
+ for (i = 0; i < targetObjects->numrefs; i++)
{
- ScanKeyInit(&key[2],
- Anum_pg_depend_refobjsubid,
- BTEqualStrategyNumber, F_INT4EQ,
- Int32GetDatum(object->objectSubId));
- nkeys = 3;
- }
- else
- nkeys = 2;
-
- scan = systable_beginscan(depRel, DependReferenceIndexId, true,
- SnapshotNow, nkeys, key);
+ ObjectAddress *thisobj = targetObjects->refs + i;
+ ObjectAddressExtra *thisextra = targetObjects->extras + i;
- while (HeapTupleIsValid(tup = systable_getnext(scan)))
- {
- Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup);
-
- switch (foundDep->deptype)
- {
- case DEPENDENCY_NORMAL:
- /* ignore */
- break;
- case DEPENDENCY_AUTO:
- case DEPENDENCY_INTERNAL:
- /* recurse */
- otherObject.classId = foundDep->classid;
- otherObject.objectId = foundDep->objid;
- otherObject.objectSubId = foundDep->objsubid;
- findAutoDeletableObjects(&otherObject, oktodelete, depRel, true);
- break;
- case DEPENDENCY_PIN:
+ if (thisextra->flags & DEPFLAG_ORIGINAL)
+ continue;
- /*
- * For a PIN dependency we just ereport immediately; there
- * won't be any others to examine, and we aren't ever going to
- * let the user delete it.
- */
- ereport(ERROR,
- (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
- errmsg("cannot drop %s because it is required by the database system",
- getObjectDescription(object))));
- break;
- default:
- elog(ERROR, "unrecognized dependency type '%c' for %s",
- foundDep->deptype, getObjectDescription(object));
- break;
- }
+ deleteOneObject(thisobj, depRel);
}
- systable_endscan(scan);
-}
+ /* And clean up */
+ free_object_addresses(targetObjects);
+ heap_close(depRel, RowExclusiveLock);
+}
/*
- * recursiveDeletion: delete a single object for performDeletion, plus
- * (recursively) anything that depends on it.
- *
- * Returns TRUE if successful, FALSE if not.
+ * findDependentObjects - find all objects that depend on 'object'
*
- * callingObject is NULL at the outer level, else identifies the object that
- * we recursed from (the reference object that someone else needs to delete).
+ * 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.
*
- * oktodelete is a list of objects verified deletable (ie, reachable by one
- * or more AUTO or INTERNAL dependencies from the original target).
- *
- * depRel is the already-open pg_depend relation.
+ * 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.
*
- * In RESTRICT mode, we perform all the deletions anyway, but ereport a message
- * and return FALSE if we find a restriction violation. performDeletion
- * will then abort the transaction to nullify the deletions. We have to
- * do it this way to (a) report all the direct and indirect dependencies
- * while (b) not going into infinite recursion if there's a cycle.
- *
- * This is even more complex than one could wish, because it is possible for
- * the same pair of objects to be related by both NORMAL and AUTO/INTERNAL
- * dependencies. Also, we might have a situation where we've been asked to
- * delete object A, and objects B and C both have AUTO dependencies on A,
- * but B also has a NORMAL dependency on C. (Since any of these paths might
- * be indirect, we can't prevent these scenarios, but must cope instead.)
- * If we visit C before B then we would mistakenly decide that the B->C link
- * should prevent the restricted drop from occurring. To handle this, we make
- * a pre-scan to find all the objects that are auto-deletable from A. If we
- * visit C first, but B is present in the oktodelete list, then we make no
- * complaint but recurse to delete B anyway. (Note that in general we must
- * delete B before deleting C; the drop routine for B may try to access C.)
- *
- * Note: in the case where the path to B is traversed first, we will not
- * see the NORMAL dependency when we reach C, because of the pg_depend
- * removals done in step 1. The oktodelete list is necessary just
- * to make the behavior independent of the order in which pg_depend
- * entries are visited.
+ * object: the object to add to targetObjects and find dependencies on
+ * flags: flags to be ORed into the object's targetObjects entry
+ * 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
*/
-static bool
-recursiveDeletion(const ObjectAddress *object,
- DropBehavior behavior,
- int msglevel,
- const ObjectAddress *callingObject,
- ObjectAddresses *oktodelete,
- Relation depRel,
- ObjectAddresses *alreadyDeleted)
+static void
+findDependentObjects(const ObjectAddress *object,
+ int flags,
+ ObjectAddressStack *stack,
+ ObjectAddresses *targetObjects,
+ const ObjectAddresses *pendingObjects,
+ Relation depRel)
{
- bool ok = true;
- char *objDescription;
ScanKeyData key[3];
int nkeys;
SysScanDesc scan;
HeapTuple tup;
ObjectAddress otherObject;
- ObjectAddress owningObject;
- bool amOwned = false;
+ ObjectAddressStack mystack;
+ ObjectAddressExtra extra;
+ ObjectAddressStack *stackptr;
/*
- * Get object description for possible use in messages. Must do this
- * before deleting it ...
+ * If the target object is already being visited in an outer recursion
+ * level, just report the current flags 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.
*/
- objDescription = getObjectDescription(object);
+ for (stackptr = stack; stackptr; stackptr = stackptr->next)
+ {
+ if (object->classId == stackptr->object->classId &&
+ object->objectId == stackptr->object->objectId)
+ {
+ if (object->objectSubId == stackptr->object->objectSubId)
+ {
+ stackptr->flags |= flags;
+ return;
+ }
+ /*
+ * Could visit column with whole table already on stack; this is
+ * the same case noted in object_address_present_add_flags().
+ * (It's not clear this can really happen, but we might as well
+ * check.)
+ */
+ if (stackptr->object->objectSubId == 0)
+ return;
+ }
+ }
/*
- * 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.
+ * 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 flags to its entry and return.
*
- * When dropping a whole object (subId = 0), remove all pg_depend records
- * for its sub-objects too.
+ * (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, flags, targetObjects))
+ return;
+
+ /*
+ * The target object might be internally dependent on some other object
+ * (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.
*/
ScanKeyInit(&key[0],
Anum_pg_depend_classid,
@@ -608,16 +534,28 @@ recursiveDeletion(const ObjectAddress *object,
*
* 1. At the outermost recursion level, disallow the DROP. (We
* just ereport here, rather than proceeding, since no other
- * dependencies are likely to be interesting.)
+ * dependencies are likely to be interesting.) However, if
+ * the other 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.
*/
- if (callingObject == NULL)
+ if (stack == NULL)
{
- char *otherObjDesc = getObjectDescription(&otherObject);
+ char *otherObjDesc;
+ if (object_address_present(&otherObject, pendingObjects))
+ {
+ systable_endscan(scan);
+ /* need to release caller's lock; see notes below */
+ ReleaseDeletionLock(object);
+ return;
+ }
+ otherObjDesc = getObjectDescription(&otherObject);
ereport(ERROR,
(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
errmsg("cannot drop %s because %s requires it",
- objDescription, otherObjDesc),
+ getObjectDescription(object),
+ otherObjDesc),
errhint("You can drop %s instead.",
otherObjDesc)));
}
@@ -628,27 +566,52 @@ recursiveDeletion(const ObjectAddress *object,
* recursing from a whole object that includes the nominal
* other end as a component, too.
*/
- if (callingObject->classId == otherObject.classId &&
- callingObject->objectId == otherObject.objectId &&
- (callingObject->objectSubId == otherObject.objectSubId ||
- callingObject->objectSubId == 0))
+ if (stack->object->classId == otherObject.classId &&
+ stack->object->objectId == otherObject.objectId &&
+ (stack->object->objectSubId == otherObject.objectSubId ||
+ stack->object->objectSubId == 0))
break;
/*
* 3. When recursing from anyplace else, transform this
- * deletion request into a delete of the other object. (This
- * will be an error condition iff RESTRICT mode.) In this case
- * we finish deleting my dependencies except for the INTERNAL
- * link, which will be needed to cause the owning object to
- * recurse back to me.
+ * deletion request into a delete of the other object.
+ *
+ * First, release caller's lock on this object and get
+ * deletion lock on the other object. (We must release
+ * caller's lock to avoid deadlock against a concurrent
+ * deletion of the other object.)
*/
- if (amOwned) /* shouldn't happen */
- elog(ERROR, "multiple INTERNAL dependencies for %s",
- objDescription);
- owningObject = otherObject;
- amOwned = true;
- /* "continue" bypasses the simple_heap_delete call below */
- continue;
+ ReleaseDeletionLock(object);
+ AcquireDeletionLock(&otherObject);
+
+ /*
+ * The other 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;
+ }
+
+ /*
+ * Okay, recurse to the other object instead of proceeding.
+ * We treat this exactly as if the original reference had
+ * linked to that object instead of this one; hence, pass
+ * through the same flags and stack.
+ */
+ findDependentObjects(&otherObject,
+ flags,
+ stack,
+ targetObjects,
+ pendingObjects,
+ depRel);
+ /* And we're done here. */
+ systable_endscan(scan);
+ return;
case DEPENDENCY_PIN:
/*
@@ -656,159 +619,24 @@ recursiveDeletion(const ObjectAddress *object,
* the depender fields...
*/
elog(ERROR, "incorrect use of PIN dependency with %s",
- objDescription);
+ getObjectDescription(object));
break;
default:
elog(ERROR, "unrecognized dependency type '%c' for %s",
- foundDep->deptype, objDescription);
+ foundDep->deptype, getObjectDescription(object));
break;
}
-
- /* delete the pg_depend tuple */
- simple_heap_delete(depRel, &tup->t_self);
}
systable_endscan(scan);
/*
- * CommandCounterIncrement here to ensure that preceding changes are all
- * visible; in particular, that the above deletions of pg_depend entries
- * are visible. That prevents infinite recursion in case of a dependency
- * loop (which is perfectly legal).
- */
- CommandCounterIncrement();
-
- /*
- * If we found we are owned by another object, ask it to delete itself
- * instead of proceeding. Complain if RESTRICT mode, unless the other
- * object is in oktodelete.
- */
- if (amOwned)
- {
- if (object_address_present(&owningObject, oktodelete))
- ereport(DEBUG2,
- (errmsg("drop auto-cascades to %s",
- getObjectDescription(&owningObject))));
- else if (behavior == DROP_RESTRICT)
- {
- ereport(msglevel,
- (errmsg("%s depends on %s",
- getObjectDescription(&owningObject),
- objDescription)));
- ok = false;
- }
- else
- ereport(msglevel,
- (errmsg("drop cascades to %s",
- getObjectDescription(&owningObject))));
-
- if (!recursiveDeletion(&owningObject, behavior, msglevel,
- object, oktodelete, depRel, alreadyDeleted))
- ok = false;
-
- pfree(objDescription);
-
- return ok;
- }
-
- /*
- * Step 2: scan pg_depend records that link to this object, showing the
- * things that depend on it. Recursively delete those things. Note it's
- * important to delete the dependent objects before the referenced one,
- * since the deletion routines might do things like try to update the
- * pg_class record when deleting a check constraint.
- */
- if (!deleteDependentObjects(object, objDescription,
- behavior, msglevel,
- oktodelete, depRel))
- ok = false;
-
- /*
- * We do not need CommandCounterIncrement here, since if step 2 did
- * anything then each recursive call will have ended with one.
- */
-
- /*
- * Step 3: delete the object itself, and save it to the list of deleted
- * objects if appropiate.
- */
- doDeletion(object);
- if (alreadyDeleted != NULL)
- {
- if (!object_address_present(object, alreadyDeleted))
- add_exact_object_address(object, alreadyDeleted);
- }
-
- /*
- * Delete any comments associated with this object. (This is a convenient
- * place to do it instead of having every object type know to do it.)
- */
- DeleteComments(object->objectId, object->classId, object->objectSubId);
-
- /*
- * Delete shared dependency references related to this object. Sub-objects
- * (columns) don't have dependencies on global objects, so skip them.
- */
- if (object->objectSubId == 0)
- deleteSharedDependencyRecordsFor(object->classId, object->objectId);
-
- /*
- * CommandCounterIncrement here to ensure that preceding changes are all
- * visible.
- */
- CommandCounterIncrement();
-
- /*
- * And we're done!
+ * Now recurse to any dependent objects. We must visit them first
+ * since they have to be deleted before the current object.
*/
- pfree(objDescription);
-
- return ok;
-}
-
-
-/*
- * deleteDependentObjects - find and delete objects that depend on 'object'
- *
- * Scan pg_depend records that link to the given object, showing
- * the things that depend on it. Recursively delete those things. (We
- * don't delete the pg_depend records here, as the recursive call will
- * do that.) Note it's important to delete the dependent objects
- * before the referenced one, since the deletion routines might do
- * things like try to update the pg_class record when deleting a check
- * constraint.
- *
- * When dropping a whole object (subId = 0), find pg_depend records for
- * its sub-objects too.
- *
- * object: the object to find dependencies on
- * objDescription: description of object (only used for error messages)
- * behavior: desired drop behavior
- * oktodelete: stuff that's AUTO-deletable
- * depRel: already opened pg_depend relation
- *
- * Returns TRUE if all is well, false if any problem found.
- *
- * NOTE: because we are using SnapshotNow, if a recursive call deletes
- * any pg_depend tuples that our scan hasn't yet visited, we will not
- * see them as good when we do visit them. This is essential for
- * correct behavior if there are multiple dependency paths between two
- * objects --- else we might try to delete an already-deleted object.
- */
-static bool
-deleteDependentObjects(const ObjectAddress *object,
- const char *objDescription,
- DropBehavior behavior,
- int msglevel,
- ObjectAddresses *oktodelete,
- Relation depRel)
-{
- bool ok = true;
- ScanKeyData key[3];
- int nkeys;
- SysScanDesc scan;
- HeapTuple tup;
- ObjectAddress otherObject;
+ mystack.object = object; /* set up a new stack level */
+ mystack.flags = flags;
+ mystack.next = stack;
ScanKeyInit(&key[0],
Anum_pg_depend_refclassid,
@@ -835,57 +663,43 @@ deleteDependentObjects(const ObjectAddress *object,
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;
+ /*
+ * Must lock the dependent object before recursing to it.
+ */
+ AcquireDeletionLock(&otherObject);
+
+ /*
+ * 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;
+ }
+
+ /* Recurse, passing flags indicating the dependency type */
switch (foundDep->deptype)
{
case DEPENDENCY_NORMAL:
-
- /*
- * Perhaps there was another dependency path that would have
- * allowed silent deletion of the otherObject, had we only
- * taken that path first. In that case, act like this link is
- * AUTO, too.
- */
- if (object_address_present(&otherObject, oktodelete))
- ereport(DEBUG2,
- (errmsg("drop auto-cascades to %s",
- getObjectDescription(&otherObject))));
- else if (behavior == DROP_RESTRICT)
- {
- ereport(msglevel,
- (errmsg("%s depends on %s",
- getObjectDescription(&otherObject),
- objDescription)));
- ok = false;
- }
- else
- ereport(msglevel,
- (errmsg("drop cascades to %s",
- getObjectDescription(&otherObject))));
-
- if (!recursiveDeletion(&otherObject, behavior, msglevel,
- object, oktodelete, depRel, NULL))
- ok = false;
+ subflags = DEPFLAG_NORMAL;
break;
case DEPENDENCY_AUTO:
+ subflags = DEPFLAG_AUTO;
+ break;
case DEPENDENCY_INTERNAL:
-
- /*
- * We propagate the DROP without complaint even in the
- * RESTRICT case. (However, normal dependencies on the
- * component object could still cause failure.)
- */
- ereport(DEBUG2,
- (errmsg("drop auto-cascades to %s",
- getObjectDescription(&otherObject))));
-
- if (!recursiveDeletion(&otherObject, behavior, msglevel,
- object, oktodelete, depRel, NULL))
- ok = false;
+ subflags = DEPFLAG_INTERNAL;
break;
case DEPENDENCY_PIN:
@@ -896,20 +710,190 @@ deleteDependentObjects(const ObjectAddress *object,
ereport(ERROR,
(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
errmsg("cannot drop %s because it is required by the database system",
- objDescription)));
+ getObjectDescription(object))));
+ subflags = 0; /* keep compiler quiet */
break;
default:
elog(ERROR, "unrecognized dependency type '%c' for %s",
- foundDep->deptype, objDescription);
+ foundDep->deptype, getObjectDescription(object));
+ subflags = 0; /* keep compiler quiet */
break;
}
+
+ findDependentObjects(&otherObject,
+ subflags,
+ &mystack,
+ targetObjects,
+ pendingObjects,
+ depRel);
}
systable_endscan(scan);
- return ok;
+ /*
+ * 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.
+ */
+ extra.flags = mystack.flags;
+ 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
+ * msglevel: elog level for non-debug notice messages
+ * 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 msglevel,
+ const ObjectAddress *origObject)
+{
+ bool ok = true;
+ int i;
+
+ /*
+ * 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];
+
+ /* Ignore the original deletion target(s) */
+ if (extra->flags & DEPFLAG_ORIGINAL)
+ continue;
+
+ /*
+ * If, at any stage of the recursive search, we reached the object
+ * via an AUTO or INTERNAL dependency, then it's okay to delete it
+ * even in RESTRICT mode.
+ */
+ if (extra->flags & (DEPFLAG_AUTO | DEPFLAG_INTERNAL))
+ ereport(DEBUG2,
+ (errmsg("drop auto-cascades to %s",
+ getObjectDescription(obj))));
+ else if (behavior == DROP_RESTRICT)
+ {
+ ereport(msglevel,
+ (errmsg("%s depends on %s",
+ getObjectDescription(obj),
+ getObjectDescription(&extra->dependee))));
+ ok = false;
+ }
+ else
+ ereport(msglevel,
+ (errmsg("drop cascades to %s",
+ getObjectDescription(obj))));
+ }
+
+ if (!ok)
+ {
+ if (origObject)
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot drop %s because other objects depend on it",
+ getObjectDescription(origObject)),
+ 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"),
+ errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
+ }
+}
+
+/*
+ * deleteOneObject: delete a single object for performDeletion.
+ *
+ * depRel is the already-open pg_depend relation.
+ */
+static void
+deleteOneObject(const ObjectAddress *object, Relation depRel)
+{
+ ScanKeyData key[3];
+ int nkeys;
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ /*
+ * First 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,
+ SnapshotNow, nkeys, key);
+
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ simple_heap_delete(depRel, &tup->t_self);
+ }
+
+ systable_endscan(scan);
+
+ /*
+ * Now delete the object itself, in an object-type-dependent way.
+ */
+ doDeletion(object);
+
+ /*
+ * Delete any comments associated with this object. (This is a convenient
+ * place to do it instead of having every object type know to do it.)
+ */
+ DeleteComments(object->objectId, object->classId, object->objectSubId);
+
+ /*
+ * Delete shared dependency references related to this object. Sub-objects
+ * (columns) don't have dependencies on global objects, so skip them.
+ */
+ if (object->objectSubId == 0)
+ deleteSharedDependencyRecordsFor(object->classId, object->objectId);
+
+ /*
+ * 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
@@ -1024,6 +1008,38 @@ doDeletion(const ObjectAddress *object)
}
/*
+ * AcquireDeletionLock - acquire a suitable lock for deleting an object
+ *
+ * We use LockRelation for relations, LockDatabaseObject for everything
+ * else. Note that dependency.c is not concerned with deleting any kind of
+ * shared-across-databases object, so we have no need for LockSharedObject.
+ */
+static void
+AcquireDeletionLock(const ObjectAddress *object)
+{
+ if (object->classId == RelationRelationId)
+ LockRelationOid(object->objectId, 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
+ */
+static 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,
@@ -1117,15 +1133,12 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
thisobj->objectId == relId)
{
/* Move this ref into self_addrs */
- add_object_address(OCLASS_CLASS, relId, thisobj->objectSubId,
- self_addrs);
+ add_exact_object_address(thisobj, self_addrs);
}
else
{
/* Keep it in context.addrs */
- outobj->classId = thisobj->classId;
- outobj->objectId = thisobj->objectId;
- outobj->objectSubId = thisobj->objectSubId;
+ *outobj = *thisobj;
outobj++;
outrefs++;
}
@@ -1476,6 +1489,13 @@ eliminate_duplicate_dependencies(ObjectAddresses *addrs)
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 */
@@ -1512,9 +1532,7 @@ eliminate_duplicate_dependencies(ObjectAddresses *addrs)
}
/* Not identical, so add thisobj to output set */
priorobj++;
- priorobj->classId = thisobj->classId;
- priorobj->objectId = thisobj->objectId;
- priorobj->objectSubId = thisobj->objectSubId;
+ *priorobj = *thisobj;
newrefs++;
}
@@ -1566,6 +1584,7 @@ new_object_addresses(void)
addrs->maxrefs = 32;
addrs->refs = (ObjectAddress *)
palloc(addrs->maxrefs * sizeof(ObjectAddress));
+ addrs->extras = NULL; /* until/unless needed */
return addrs;
}
@@ -1588,6 +1607,7 @@ add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
addrs->maxrefs *= 2;
addrs->refs = (ObjectAddress *)
repalloc(addrs->refs, addrs->maxrefs * sizeof(ObjectAddress));
+ Assert(!addrs->extras);
}
/* record this item */
item = addrs->refs + addrs->numrefs;
@@ -1614,6 +1634,7 @@ add_exact_object_address(const ObjectAddress *object,
addrs->maxrefs *= 2;
addrs->refs = (ObjectAddress *)
repalloc(addrs->refs, addrs->maxrefs * sizeof(ObjectAddress));
+ Assert(!addrs->extras);
}
/* record this item */
item = addrs->refs + addrs->numrefs;
@@ -1622,19 +1643,54 @@ add_exact_object_address(const ObjectAddress *object,
}
/*
+ * 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,
- ObjectAddresses *addrs)
+ const ObjectAddresses *addrs)
{
int i;
for (i = addrs->numrefs - 1; i >= 0; i--)
{
- ObjectAddress *thisobj = addrs->refs + i;
+ const ObjectAddress *thisobj = addrs->refs + i;
if (object->classId == thisobj->classId &&
object->objectId == thisobj->objectId)
@@ -1649,6 +1705,47 @@ object_address_present(const ObjectAddress *object,
}
/*
+ * 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)
+{
+ 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;
+ return true;
+ }
+ 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 column. But don't plaster
+ * its flags on the table.
+ */
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
* Record multiple dependencies from an ObjectAddresses array, after first
* removing any duplicates.
*/
@@ -1670,6 +1767,8 @@ void
free_object_addresses(ObjectAddresses *addrs)
{
pfree(addrs->refs);
+ if (addrs->extras)
+ pfree(addrs->extras);
pfree(addrs);
}
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index 21374f17d3d..c59c3b9d52f 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/access/genam.h,v 1.72 2008/05/12 00:00:53 alvherre Exp $
+ * $PostgreSQL: pgsql/src/include/access/genam.h,v 1.73 2008/06/08 22:41:04 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -138,6 +138,7 @@ extern SysScanDesc systable_beginscan(Relation heapRelation,
Snapshot snapshot,
int nkeys, ScanKey key);
extern HeapTuple systable_getnext(SysScanDesc sysscan);
+extern bool systable_recheck_tuple(SysScanDesc sysscan, HeapTuple tup);
extern void systable_endscan(SysScanDesc sysscan);
extern SysScanDesc systable_beginscan_ordered(Relation heapRelation,
Relation indexRelation,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index e192c1ed288..8929be6a342 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/dependency.h,v 1.35 2008/05/16 23:36:05 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/dependency.h,v 1.36 2008/06/08 22:41:04 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -17,7 +17,7 @@
#include "nodes/parsenodes.h" /* for DropBehavior */
-/*----------
+/*
* Precise semantics of a dependency relationship are specified by the
* DependencyType code (which is stored in a "char" field in pg_depend,
* so we assign ASCII-code values to the enumeration members).
@@ -56,7 +56,6 @@
* contain zeroes.
*
* Other dependency flavors may be needed in future.
- *----------
*/
typedef enum DependencyType
@@ -178,7 +177,7 @@ extern void add_exact_object_address(const ObjectAddress *object,
ObjectAddresses *addrs);
extern bool object_address_present(const ObjectAddress *object,
- ObjectAddresses *addrs);
+ const ObjectAddresses *addrs);
extern void record_object_address_dependencies(const ObjectAddress *depender,
ObjectAddresses *referenced,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index b5af16c558e..e3aebc630d8 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -376,7 +376,6 @@ select test2 from atacc2;
drop table atacc2 cascade;
NOTICE: drop cascades to table atacc3
-NOTICE: drop cascades to constraint foo on table atacc3
drop table atacc1;
-- adding only to a parent is disallowed as of 8.4
create table atacc1 (test int);
@@ -1251,7 +1250,6 @@ select * from p1;
drop table p1 cascade;
NOTICE: drop cascades to table c1
-NOTICE: drop cascades to constraint p1_a1_check on table c1
-- test that operations with a dropped column do not try to reference
-- its datatype
create domain mytype as text;
@@ -1470,12 +1468,8 @@ select alter2.plus1(41);
-- clean up
drop schema alter2 cascade;
-NOTICE: drop cascades to type alter2.ctype
-NOTICE: drop cascades to type alter2.posint
-NOTICE: drop cascades to function alter2.plus1(integer)
-NOTICE: drop cascades to view alter2.v1
-NOTICE: drop cascades to rule _RETURN on view alter2.v1
-NOTICE: drop cascades to sequence alter2.t1_f1_seq
-NOTICE: drop cascades to default for table alter2.t1 column f1
NOTICE: drop cascades to table alter2.t1
-NOTICE: drop cascades to constraint t1_f2_check on table alter2.t1
+NOTICE: drop cascades to view alter2.v1
+NOTICE: drop cascades to function alter2.plus1(integer)
+NOTICE: drop cascades to type alter2.posint
+NOTICE: drop cascades to type alter2.ctype
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index 0ef4bffe0b5..3eae7e90ccd 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -237,72 +237,43 @@ And relnamespace IN (SELECT OID FROM pg_namespace WHERE nspname LIKE 'pg_temp%')
(1 row)
DROP SCHEMA temp_view_test CASCADE;
-NOTICE: drop cascades to view temp_view_test.v9
-NOTICE: drop cascades to rule _RETURN on view temp_view_test.v9
-NOTICE: drop cascades to sequence temp_view_test.seq1
-NOTICE: drop cascades to view temp_view_test.v8
-NOTICE: drop cascades to rule _RETURN on view temp_view_test.v8
-NOTICE: drop cascades to view temp_view_test.v7
-NOTICE: drop cascades to rule _RETURN on view temp_view_test.v7
-NOTICE: drop cascades to view temp_view_test.v6
-NOTICE: drop cascades to rule _RETURN on view temp_view_test.v6
-NOTICE: drop cascades to view temp_view_test.v5
-NOTICE: drop cascades to rule _RETURN on view temp_view_test.v5
-NOTICE: drop cascades to view temp_view_test.v4
-NOTICE: drop cascades to rule _RETURN on view temp_view_test.v4
-NOTICE: drop cascades to view temp_view_test.v3
-NOTICE: drop cascades to rule _RETURN on view temp_view_test.v3
-NOTICE: drop cascades to view temp_view_test.v2
-NOTICE: drop cascades to rule _RETURN on view temp_view_test.v2
-NOTICE: drop cascades to view temp_view_test.v1
-NOTICE: drop cascades to rule _RETURN on view temp_view_test.v1
-NOTICE: drop cascades to table temp_view_test.base_table2
-NOTICE: drop cascades to rule _RETURN on view v5_temp
-NOTICE: drop cascades to view v5_temp
NOTICE: drop cascades to table temp_view_test.base_table
-NOTICE: drop cascades to rule _RETURN on view v9_temp
-NOTICE: drop cascades to view v9_temp
-NOTICE: drop cascades to rule _RETURN on view v8_temp
-NOTICE: drop cascades to view v8_temp
-NOTICE: drop cascades to rule _RETURN on view v6_temp
-NOTICE: drop cascades to view v6_temp
-NOTICE: drop cascades to rule _RETURN on view v4_temp
-NOTICE: drop cascades to view v4_temp
-NOTICE: drop cascades to rule _RETURN on view v2_temp
-NOTICE: drop cascades to view v2_temp
-NOTICE: drop cascades to rule _RETURN on view v11_temp
-NOTICE: drop cascades to view v11_temp
-NOTICE: drop cascades to rule _RETURN on view v12_temp
-NOTICE: drop cascades to view v12_temp
-NOTICE: drop cascades to rule _RETURN on view v7_temp
NOTICE: drop cascades to view v7_temp
-NOTICE: drop cascades to rule _RETURN on view v10_temp
NOTICE: drop cascades to view v10_temp
+NOTICE: drop cascades to view v11_temp
+NOTICE: drop cascades to view v12_temp
+NOTICE: drop cascades to view v2_temp
+NOTICE: drop cascades to view v4_temp
+NOTICE: drop cascades to view v6_temp
+NOTICE: drop cascades to view v8_temp
+NOTICE: drop cascades to view v9_temp
+NOTICE: drop cascades to table temp_view_test.base_table2
+NOTICE: drop cascades to view v5_temp
+NOTICE: drop cascades to view temp_view_test.v1
+NOTICE: drop cascades to view temp_view_test.v2
+NOTICE: drop cascades to view temp_view_test.v3
+NOTICE: drop cascades to view temp_view_test.v4
+NOTICE: drop cascades to view temp_view_test.v5
+NOTICE: drop cascades to view temp_view_test.v6
+NOTICE: drop cascades to view temp_view_test.v7
+NOTICE: drop cascades to view temp_view_test.v8
+NOTICE: drop cascades to sequence temp_view_test.seq1
+NOTICE: drop cascades to view temp_view_test.v9
DROP SCHEMA testviewschm2 CASCADE;
-NOTICE: drop cascades to view pubview
-NOTICE: drop cascades to rule _RETURN on view pubview
-NOTICE: drop cascades to table tbl4
-NOTICE: drop cascades to rule _RETURN on view mytempview
-NOTICE: drop cascades to view mytempview
-NOTICE: drop cascades to table tbl3
-NOTICE: drop cascades to table tbl2
-NOTICE: drop cascades to table tbl1
-NOTICE: drop cascades to view nontemp4
-NOTICE: drop cascades to rule _RETURN on view nontemp4
-NOTICE: drop cascades to view nontemp3
-NOTICE: drop cascades to rule _RETURN on view nontemp3
-NOTICE: drop cascades to view nontemp2
-NOTICE: drop cascades to rule _RETURN on view nontemp2
-NOTICE: drop cascades to view nontemp1
-NOTICE: drop cascades to rule _RETURN on view nontemp1
-NOTICE: drop cascades to table t2
NOTICE: drop cascades to table t1
-NOTICE: drop cascades to rule _RETURN on view temporal4
-NOTICE: drop cascades to view temporal4
-NOTICE: drop cascades to rule _RETURN on view temporal3
-NOTICE: drop cascades to view temporal3
-NOTICE: drop cascades to rule _RETURN on view temporal2
-NOTICE: drop cascades to view temporal2
-NOTICE: drop cascades to rule _RETURN on view temporal1
NOTICE: drop cascades to view temporal1
+NOTICE: drop cascades to view temporal2
+NOTICE: drop cascades to view temporal3
+NOTICE: drop cascades to view temporal4
+NOTICE: drop cascades to table t2
+NOTICE: drop cascades to view nontemp1
+NOTICE: drop cascades to view nontemp2
+NOTICE: drop cascades to view nontemp3
+NOTICE: drop cascades to view nontemp4
+NOTICE: drop cascades to table tbl1
+NOTICE: drop cascades to table tbl2
+NOTICE: drop cascades to table tbl3
+NOTICE: drop cascades to table tbl4
+NOTICE: drop cascades to view mytempview
+NOTICE: drop cascades to view pubview
SET search_path to public;
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index a80eae7d590..179c8c63471 100644
--- a/src/test/regress/expected/domain.out
+++ b/src/test/regress/expected/domain.out
@@ -266,8 +266,8 @@ ERROR: domain dnotnulltest does not allow null values
alter domain dnotnulltest drop not null;
update domnotnull set col1 = null;
drop domain dnotnulltest cascade;
-NOTICE: drop cascades to table domnotnull column col2
NOTICE: drop cascades to table domnotnull column col1
+NOTICE: drop cascades to table domnotnull column col2
-- Test ALTER DOMAIN .. DEFAULT ..
create table domdeftest (col1 ddef1);
insert into domdeftest default values;
diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out
index e24ceb51a87..87e6591ea35 100644
--- a/src/test/regress/expected/foreign_key.out
+++ b/src/test/regress/expected/foreign_key.out
@@ -1157,15 +1157,15 @@ FOREIGN KEY (x2,x4,x1) REFERENCES pktable(id1,id3,id2);
ERROR: foreign key constraint "fk_241_132" cannot be implemented
DETAIL: Key columns "x2" and "id1" are of incompatible types: character varying and integer.
DROP TABLE pktable, fktable CASCADE;
-NOTICE: drop cascades to constraint fk_253_213 on table fktable
-NOTICE: drop cascades to constraint fk_213_213 on table fktable
-NOTICE: drop cascades to constraint fk_123_123 on table fktable
-NOTICE: drop cascades to constraint fk_5_1 on table fktable
-NOTICE: drop cascades to constraint fktable_x1_fkey on table fktable
-NOTICE: drop cascades to constraint fk_4_2 on table fktable
-NOTICE: drop cascades to constraint fktable_x2_fkey on table fktable
-NOTICE: drop cascades to constraint fk_1_3 on table fktable
NOTICE: drop cascades to constraint fktable_x3_fkey on table fktable
+NOTICE: drop cascades to constraint fk_1_3 on table fktable
+NOTICE: drop cascades to constraint fktable_x2_fkey on table fktable
+NOTICE: drop cascades to constraint fk_4_2 on table fktable
+NOTICE: drop cascades to constraint fktable_x1_fkey on table fktable
+NOTICE: drop cascades to constraint fk_5_1 on table fktable
+NOTICE: drop cascades to constraint fk_123_123 on table fktable
+NOTICE: drop cascades to constraint fk_213_213 on table fktable
+NOTICE: drop cascades to constraint fk_253_213 on table fktable
-- test a tricky case: we can elide firing the FK check trigger during
-- an UPDATE if the UPDATE did not change the foreign key
-- field. However, we can't do this if our transaction was the one that
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index e3acd03c17d..7c7ac0ef338 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -818,9 +818,9 @@ create table c2(f3 int) inherits(p1,p2);
Table "public.c2"
Column | Type | Modifiers
--------+---------+-----------
- f1 | integer |
- f2 | integer |
- f3 | integer |
+ f1 | integer |
+ f2 | integer |
+ f3 | integer |
Check constraints:
"p2_f2_check" CHECK (f2 > 0)
Inherits: p1,
@@ -834,22 +834,19 @@ NOTICE: merging multiple inherited definitions of column "f3"
Table "public.c3"
Column | Type | Modifiers
--------+---------+-----------
- f1 | integer |
- f2 | integer |
- f3 | integer |
- f4 | integer |
+ f1 | integer |
+ f2 | integer |
+ f3 | integer |
+ f4 | integer |
Check constraints:
"p2_f2_check" CHECK (f2 > 0)
Inherits: c1,
c2
drop table p1 cascade;
+NOTICE: drop cascades to table c1
NOTICE: drop cascades to table c2
NOTICE: drop cascades to table c3
-NOTICE: drop cascades to constraint p2_f2_check on table c3
-NOTICE: drop cascades to constraint p2_f2_check on table c2
-NOTICE: drop cascades to table c1
-NOTICE: drop cascades to constraint p2_f2_check on table c1
drop table p2 cascade;
create table pp1 (f1 int);
create table cc1 (f2 text, f3 int) inherits (pp1);
@@ -858,10 +855,10 @@ alter table pp1 add column a1 int check (a1 > 0);
Table "public.cc1"
Column | Type | Modifiers
--------+---------+-----------
- f1 | integer |
- f2 | text |
- f3 | integer |
- a1 | integer |
+ f1 | integer |
+ f2 | text |
+ f3 | integer |
+ a1 | integer |
Check constraints:
"pp1_a1_check" CHECK (a1 > 0)
Inherits: pp1
@@ -873,11 +870,11 @@ NOTICE: merging multiple inherited definitions of column "a1"
Table "public.cc2"
Column | Type | Modifiers
--------+------------------+-----------
- f1 | integer |
- a1 | integer |
- f2 | text |
- f3 | integer |
- f4 | double precision |
+ f1 | integer |
+ a1 | integer |
+ f2 | text |
+ f3 | integer |
+ f4 | double precision |
Check constraints:
"pp1_a1_check" CHECK (a1 > 0)
Inherits: pp1,
@@ -890,12 +887,12 @@ NOTICE: merging constraint "pp1_a2_check" with inherited definition
Table "public.cc2"
Column | Type | Modifiers
--------+------------------+-----------
- f1 | integer |
- a1 | integer |
- f2 | text |
- f3 | integer |
- f4 | double precision |
- a2 | integer |
+ f1 | integer |
+ a1 | integer |
+ f2 | text |
+ f3 | integer |
+ f4 | double precision |
+ a2 | integer |
Check constraints:
"pp1_a1_check" CHECK (a1 > 0)
"pp1_a2_check" CHECK (a2 > 0)
@@ -903,9 +900,5 @@ Inherits: pp1,
cc1
drop table pp1 cascade;
-NOTICE: drop cascades to table cc2
-NOTICE: drop cascades to constraint pp1_a1_check on table cc2
-NOTICE: drop cascades to constraint pp1_a2_check on table cc2
NOTICE: drop cascades to table cc1
-NOTICE: drop cascades to constraint pp1_a1_check on table cc1
-NOTICE: drop cascades to constraint pp1_a2_check on table cc1
+NOTICE: drop cascades to table cc2
diff --git a/src/test/regress/expected/namespace.out b/src/test/regress/expected/namespace.out
index a9f3e4ab92e..94b3e5e99b0 100644
--- a/src/test/regress/expected/namespace.out
+++ b/src/test/regress/expected/namespace.out
@@ -39,10 +39,8 @@ SELECT * FROM test_schema_1.abc_view;
(3 rows)
DROP SCHEMA test_schema_1 CASCADE;
-NOTICE: drop cascades to view test_schema_1.abc_view
-NOTICE: drop cascades to rule _RETURN on view test_schema_1.abc_view
NOTICE: drop cascades to table test_schema_1.abc
-NOTICE: drop cascades to default for table test_schema_1.abc column a
+NOTICE: drop cascades to view test_schema_1.abc_view
-- verify that the objects were dropped
SELECT COUNT(*) FROM pg_class WHERE relnamespace =
(SELECT oid FROM pg_namespace WHERE nspname = 'test_schema_1');
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index 7ee11921c25..c0681d26e38 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -245,9 +245,6 @@ NOTICE: 3
(1 row)
select cachebug();
-NOTICE: drop cascades to rule _RETURN on view vv
-CONTEXT: SQL statement "drop table if exists temptable cascade"
-PL/pgSQL function "cachebug" line 3 at SQL statement
NOTICE: drop cascades to view vv
CONTEXT: SQL statement "drop table if exists temptable cascade"
PL/pgSQL function "cachebug" line 3 at SQL statement
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index bcb2ea54aed..54d6613f28a 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -589,7 +589,6 @@ DROP VIEW atestv1;
DROP VIEW atestv2;
-- this should cascade to drop atestv4
DROP VIEW atestv3 CASCADE;
-NOTICE: drop cascades to rule _RETURN on view atestv4
NOTICE: drop cascades to view atestv4
-- this should complain "does not exist"
DROP VIEW atestv4;
diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out
index 723dca70079..4bd0b4afbb5 100644
--- a/src/test/regress/expected/subselect.out
+++ b/src/test/regress/expected/subselect.out
@@ -332,7 +332,6 @@ SELECT * FROM orders_view;
(11 rows)
DROP TABLE orderstest cascade;
-NOTICE: drop cascades to rule _RETURN on view orders_view
NOTICE: drop cascades to view orders_view
--
-- Test cases to catch situations where rule rewriter fails to propagate
diff --git a/src/test/regress/expected/truncate.out b/src/test/regress/expected/truncate.out
index 929aba4da70..d0a99a554a6 100644
--- a/src/test/regress/expected/truncate.out
+++ b/src/test/regress/expected/truncate.out
@@ -141,10 +141,10 @@ SELECT * FROM trunc_e;
(0 rows)
DROP TABLE truncate_a,trunc_c,trunc_b,trunc_d,trunc_e CASCADE;
-NOTICE: drop cascades to constraint trunc_e_a_fkey on table trunc_e
NOTICE: drop cascades to constraint trunc_b_a_fkey on table trunc_b
-NOTICE: drop cascades to constraint trunc_e_b_fkey on table trunc_e
+NOTICE: drop cascades to constraint trunc_e_a_fkey on table trunc_e
NOTICE: drop cascades to constraint trunc_d_a_fkey on table trunc_d
+NOTICE: drop cascades to constraint trunc_e_b_fkey on table trunc_e
-- Test ON TRUNCATE triggers
CREATE TABLE trunc_trigger_test (f1 int, f2 text, f3 text);
CREATE TABLE trunc_trigger_log (tgop text, tglevel text, tgwhen text,
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 607232f5353..6337798c625 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -65,9 +65,9 @@ ERROR: tablespace "nosuchspace" does not exist
DROP TABLESPACE testspace;
ERROR: tablespace "testspace" is not empty
DROP SCHEMA testschema CASCADE;
-NOTICE: drop cascades to table testschema.atable
-NOTICE: drop cascades to table testschema.asexecute
-NOTICE: drop cascades to table testschema.asselect
NOTICE: drop cascades to table testschema.foo
+NOTICE: drop cascades to table testschema.asselect
+NOTICE: drop cascades to table testschema.asexecute
+NOTICE: drop cascades to table testschema.atable
-- Should succeed
DROP TABLESPACE testspace;