OSDN Git Service

Dept. of second thoughts: rejigger the TRUNCATE ... CASCADE patch so that
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 3 Mar 2006 18:25:14 +0000 (18:25 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 3 Mar 2006 18:25:14 +0000 (18:25 +0000)
relations are still checked for permissions etc as soon as they are
opened.  The original form of the patch could hold exclusive lock for a
long time on relations that the user doesn't even have permissions to
access, let alone truncate.

src/backend/commands/tablecmds.c

index 4a3c146..14ec0c8 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.178 2006/03/03 03:30:52 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.179 2006/03/03 18:25:14 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -156,6 +156,7 @@ typedef struct NewColumnValue
 } NewColumnValue;
 
 
+static void truncate_check_rel(Relation rel);
 static List *MergeAttributes(List *schema, List *supers, bool istemp,
                                List **supOids, List **supconstr, int *supOidCount);
 static bool change_varattnos_of_a_node(Node *node, const AttrNumber *newattno);
@@ -539,33 +540,33 @@ void
 ExecuteTruncate(TruncateStmt *stmt)
 {
        List       *rels = NIL;
-       List       *directRelids = NIL;
+       List       *relids = NIL;
        ListCell   *cell;
-       Oid                     relid;
-       Relation        rel;
 
        /*
-        * Open and exclusive-lock all the explicitly-specified relations
+        * Open, exclusive-lock, and check all the explicitly-specified relations
         */
        foreach(cell, stmt->relations)
        {
                RangeVar   *rv = lfirst(cell);
+               Relation        rel;
 
                rel = heap_openrv(rv, AccessExclusiveLock);
+               truncate_check_rel(rel);
                rels = lappend(rels, rel);
-               directRelids = lappend_oid(directRelids, RelationGetRelid(rel));
+               relids = lappend_oid(relids, RelationGetRelid(rel));
        }
 
        /*
         * In CASCADE mode, suck in all referencing relations as well.  This
         * requires multiple iterations to find indirectly-dependent relations.
         * At each phase, we need to exclusive-lock new rels before looking
-        * for their dependencies, else we might miss something.
+        * for their dependencies, else we might miss something.  Also, we
+        * check each rel as soon as we open it, to avoid a faux pas such as
+        * holding lock for a long time on a rel we have no permissions for.
         */
        if (stmt->behavior == DROP_CASCADE)
        {
-               List   *relids = list_copy(directRelids);
-
                for (;;)
                {
                        List   *newrelids;
@@ -576,70 +577,20 @@ ExecuteTruncate(TruncateStmt *stmt)
 
                        foreach(cell, newrelids)
                        {
-                               relid = lfirst_oid(cell);
+                               Oid             relid = lfirst_oid(cell);
+                               Relation        rel;
+
                                rel = heap_open(relid, AccessExclusiveLock);
+                               ereport(NOTICE,
+                                               (errmsg("truncate cascades to table \"%s\"",
+                                                               RelationGetRelationName(rel))));
+                               truncate_check_rel(rel);
                                rels = lappend(rels, rel);
                                relids = lappend_oid(relids, relid);
                        }
                }
        }
 
-       /* now check all involved relations */
-       foreach(cell, rels)
-       {
-               rel = (Relation) lfirst(cell);
-               relid = RelationGetRelid(rel);
-
-               /*
-                * If this table was added to the command by CASCADE, report it.
-                * We don't do this earlier because if we error out on one of the
-                * tables, it'd be confusing to list subsequently-added tables.
-                */
-               if (stmt->behavior == DROP_CASCADE &&
-                       !list_member_oid(directRelids, relid))
-                       ereport(NOTICE,
-                                       (errmsg("truncate cascades to table \"%s\"",
-                                                       RelationGetRelationName(rel))));
-
-               /* Only allow truncate on regular tables */
-               if (rel->rd_rel->relkind != RELKIND_RELATION)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                                        errmsg("\"%s\" is not a table",
-                                                       RelationGetRelationName(rel))));
-
-               /* Permissions checks */
-               if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
-                       aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
-                                                  RelationGetRelationName(rel));
-
-               if (!allowSystemTableMods && IsSystemRelation(rel))
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                                        errmsg("permission denied: \"%s\" is a system catalog",
-                                                       RelationGetRelationName(rel))));
-
-               /*
-                * We can never allow truncation of shared or nailed-in-cache
-                * relations, because we can't support changing their relfilenode
-                * values.
-                */
-               if (rel->rd_rel->relisshared || rel->rd_isnailed)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                        errmsg("cannot truncate system relation \"%s\"",
-                                                       RelationGetRelationName(rel))));
-
-               /*
-                * Don't allow truncate on temp tables of other backends ... their
-                * local buffer manager is not going to cope.
-                */
-               if (isOtherTempNamespace(RelationGetNamespace(rel)))
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                         errmsg("cannot truncate temporary tables of other sessions")));
-       }
-
        /*
         * Check foreign key references.  In CASCADE mode, this should be
         * unnecessary since we just pulled in all the references; but as
@@ -657,11 +608,10 @@ ExecuteTruncate(TruncateStmt *stmt)
         */
        foreach(cell, rels)
        {
+               Relation        rel = (Relation) lfirst(cell);
                Oid                     heap_relid;
                Oid                     toast_relid;
 
-               rel = (Relation) lfirst(cell);
-
                /*
                 * Create a new empty storage file for the relation, and assign it as
                 * the relfilenode value.       The old storage file is scheduled for
@@ -691,6 +641,51 @@ ExecuteTruncate(TruncateStmt *stmt)
        }
 }
 
+/*
+ * Check that a given rel is safe to truncate.  Subroutine for ExecuteTruncate
+ */
+static void
+truncate_check_rel(Relation rel)
+{
+       /* Only allow truncate on regular tables */
+       if (rel->rd_rel->relkind != RELKIND_RELATION)
+               ereport(ERROR,
+                               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                                errmsg("\"%s\" is not a table",
+                                               RelationGetRelationName(rel))));
+
+       /* Permissions checks */
+       if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
+               aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
+                                          RelationGetRelationName(rel));
+
+       if (!allowSystemTableMods && IsSystemRelation(rel))
+               ereport(ERROR,
+                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                errmsg("permission denied: \"%s\" is a system catalog",
+                                               RelationGetRelationName(rel))));
+
+       /*
+        * We can never allow truncation of shared or nailed-in-cache
+        * relations, because we can't support changing their relfilenode
+        * values.
+        */
+       if (rel->rd_rel->relisshared || rel->rd_isnailed)
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("cannot truncate system relation \"%s\"",
+                                               RelationGetRelationName(rel))));
+
+       /*
+        * Don't allow truncate on temp tables of other backends ... their
+        * local buffer manager is not going to cope.
+        */
+       if (isOtherTempNamespace(RelationGetNamespace(rel)))
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("cannot truncate temporary tables of other sessions")));
+}
+
 /*----------
  * MergeAttributes
  *             Returns new schema given initial schema and superclasses.