Disallow foreign-key references from temp tables to permanent tables.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 19 Sep 2003 21:04:20 +0000 (21:04 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 19 Sep 2003 21:04:20 +0000 (21:04 +0000)
Per recent discussion, this does not work because other backends can't
reliably see tuples in a temp table and so cannot run the RI checks
correctly.  Seems better to disallow this case than go back to accessing
temp tables through shared buffers.  Also, disallow FK references to
ON COMMIT DELETE ROWS tables.  We already caught this problem for normal
TRUNCATE, but the path used by ON COMMIT didn't check.

doc/src/sgml/ref/truncate.sgml
src/backend/catalog/heap.c
src/backend/commands/tablecmds.c
src/include/catalog/heap.h
src/test/regress/expected/truncate.out

index ac33c263148bf557d782a5fa6f65b24adea9cfbe..69acbb4e462c0993088704dbf2d06d5529d42ff5 100644 (file)
@@ -50,6 +50,16 @@ TRUNCATE [ TABLE ] <replaceable class="PARAMETER">name</replaceable>
   </variablelist>
  </refsect1>
 
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   <command>TRUNCATE</> cannot be used if there are foreign-key references
+   to the table from other tables.  Checking validity in such cases would
+   require table scans, and the whole point is not to do one.
+  </para>
+ </refsect1>
+
  <refsect1>
   <title>Examples</title>
 
index f6a353b770bc8e6fdb1e3448c1835558852ed065..d708a28b5edc257b7c457e089e54ec5eec83b637 100644 (file)
@@ -1966,9 +1966,11 @@ RelationTruncateIndexes(Oid heapId)
 /*
  *      heap_truncate
  *
- *      This routine is used to truncate the data from the
- *      storage manager of any data within the relation handed
- *      to this routine.  This is not transaction-safe!
+ *      This routine deletes all data within the specified relation.
+ *
+ * This is not transaction-safe!  There is another, transaction-safe
+ * implementation in commands/cluster.c.  We now use this only for
+ * ON COMMIT truncation of temporary tables, where it doesn't matter.
  */
 void
 heap_truncate(Oid rid)
@@ -1979,6 +1981,9 @@ heap_truncate(Oid rid)
        /* Open relation for processing, and grab exclusive access on it. */
        rel = heap_open(rid, AccessExclusiveLock);
 
+       /* Don't allow truncate on tables that are referenced by foreign keys */
+       heap_truncate_check_FKs(rel);
+
        /*
         * Release any buffers associated with this relation.  If they're
         * dirty, they're just dropped without bothering to flush to disk.
@@ -2003,3 +2008,61 @@ heap_truncate(Oid rid)
         */
        heap_close(rel, NoLock);
 }
+
+/*
+ * heap_truncate_check_FKs
+ *             Check for foreign keys referencing a relation that's to be truncated
+ *
+ * We disallow such FKs (except self-referential ones) since the whole point
+ * of TRUNCATE is to not scan the individual rows to be thrown away.
+ *
+ * This is split out so it can be shared by both implementations of truncate.
+ * Caller should already hold a suitable lock on the relation.
+ */
+void
+heap_truncate_check_FKs(Relation rel)
+{
+       Oid                     relid = RelationGetRelid(rel);
+       ScanKeyData key;
+       Relation        fkeyRel;
+       SysScanDesc fkeyScan;
+       HeapTuple       tuple;
+
+       /*
+        * Fast path: if the relation has no triggers, it surely has no FKs
+        * either.
+        */
+       if (rel->rd_rel->reltriggers == 0)
+               return;
+
+       /*
+        * Otherwise, must scan pg_constraint.  Right now, this is a seqscan
+        * because there is no available index on confrelid.
+        */
+       fkeyRel = heap_openr(ConstraintRelationName, AccessShareLock);
+
+       ScanKeyEntryInitialize(&key, 0,
+                                                  Anum_pg_constraint_confrelid,
+                                                  F_OIDEQ,
+                                                  ObjectIdGetDatum(relid));
+
+       fkeyScan = systable_beginscan(fkeyRel, NULL, false,
+                                                                 SnapshotNow, 1, &key);
+
+       while (HeapTupleIsValid(tuple = systable_getnext(fkeyScan)))
+       {
+               Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
+
+               if (con->contype == CONSTRAINT_FOREIGN && con->conrelid != relid)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("cannot truncate a table referenced in a foreign key constraint"),
+                                        errdetail("Table \"%s\" references \"%s\" via foreign key constraint \"%s\".",
+                                                          get_rel_name(con->conrelid),
+                                                          RelationGetRelationName(rel),
+                                                          NameStr(con->conname))));
+       }
+
+       systable_endscan(fkeyScan);
+       heap_close(fkeyRel, AccessShareLock);
+}
index 948d9bb1e2d9c8c515df0d57e1d82c169444d6f7..6589a06ac6e0926d6279a785d41e281d577d38d1 100644 (file)
@@ -365,15 +365,9 @@ void
 TruncateRelation(const RangeVar *relation)
 {
        Relation        rel;
-       Oid                     relid;
-       ScanKeyData key;
-       Relation        fkeyRel;
-       SysScanDesc fkeyScan;
-       HeapTuple       tuple;
 
        /* Grab exclusive lock in preparation for truncate */
        rel = heap_openrv(relation, AccessExclusiveLock);
-       relid = RelationGetRelid(rel);
 
        /* Only allow truncate on regular tables */
        if (rel->rd_rel->relkind != RELKIND_RELATION)
@@ -383,7 +377,7 @@ TruncateRelation(const RangeVar *relation)
                                                RelationGetRelationName(rel))));
 
        /* Permissions checks */
-       if (!pg_class_ownercheck(relid, GetUserId()))
+       if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
                aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
                                           RelationGetRelationName(rel));
 
@@ -405,35 +399,7 @@ TruncateRelation(const RangeVar *relation)
        /*
         * Don't allow truncate on tables which are referenced by foreign keys
         */
-       fkeyRel = heap_openr(ConstraintRelationName, AccessShareLock);
-
-       ScanKeyEntryInitialize(&key, 0,
-                                                  Anum_pg_constraint_confrelid,
-                                                  F_OIDEQ,
-                                                  ObjectIdGetDatum(relid));
-
-       fkeyScan = systable_beginscan(fkeyRel, 0, false,
-                                                                 SnapshotNow, 1, &key);
-
-       /*
-        * First foreign key found with us as the reference should throw an
-        * error.
-        */
-       while (HeapTupleIsValid(tuple = systable_getnext(fkeyScan)))
-       {
-               Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
-
-               if (con->contype == 'f' && con->conrelid != relid)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                        errmsg("cannot truncate a table referenced in a foreign key constraint"),
-                                        errdetail("Table \"%s\" references this one via foreign key constraint \"%s\".",
-                                                          get_rel_name(con->conrelid),
-                                                          NameStr(con->conname))));
-       }
-
-       systable_endscan(fkeyScan);
-       heap_close(fkeyRel, AccessShareLock);
+       heap_truncate_check_FKs(rel);
 
        /*
         * Do the real work using the same technique as cluster, but without
@@ -3137,11 +3103,28 @@ AlterTableAddForeignKeyConstraint(Relation rel, FkConstraint *fkconstraint)
                aclcheck_error(aclresult, ACL_KIND_CLASS,
                                           RelationGetRelationName(rel));
 
-       if (isTempNamespace(RelationGetNamespace(pkrel)) &&
-               !isTempNamespace(RelationGetNamespace(rel)))
-               ereport(ERROR,
-                               (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-                                errmsg("cannot reference temporary table from permanent table constraint")));
+       /*
+        * Disallow reference from permanent table to temp table or vice versa.
+        * (The ban on perm->temp is for fairly obvious reasons.  The ban on
+        * temp->perm is because other backends might need to run the RI triggers
+        * on the perm table, but they can't reliably see tuples the owning
+        * backend has created in the temp table, because non-shared buffers
+        * are used for temp tables.)
+        */
+       if (isTempNamespace(RelationGetNamespace(pkrel)))
+       {
+               if (!isTempNamespace(RelationGetNamespace(rel)))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+                                        errmsg("cannot reference temporary table from permanent table constraint")));
+       }
+       else
+       {
+               if (isTempNamespace(RelationGetNamespace(rel)))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+                                        errmsg("cannot reference permanent table from temporary table constraint")));
+       }
 
        /*
         * Look up the referencing attributes to make sure they exist, and
index 7cf37a56f6e23c689cb1964ab006832e6bb38b17..bb4c4a915a7ed5497b1175ad53c847f6563dceb6 100644 (file)
@@ -48,6 +48,8 @@ extern void heap_drop_with_catalog(Oid rid);
 
 extern void heap_truncate(Oid rid);
 
+extern void heap_truncate_check_FKs(Relation rel);
+
 extern void AddRelationRawConstraints(Relation rel,
                                                  List *rawColDefaults,
                                                  List *rawConstraints);
index 91ea40b7929cca531e412c0c2bf5f31aff1caad4..2cd41f83ba063613592f05e665c82e1293143e50 100644 (file)
@@ -42,7 +42,7 @@ SELECT * FROM truncate_a;
 
 TRUNCATE truncate_a;
 ERROR:  cannot truncate a table referenced in a foreign key constraint
-DETAIL:  Table "truncate_b" references this one via foreign key constraint "$1".
+DETAIL:  Table "truncate_b" references "truncate_a" via foreign key constraint "$1".
 SELECT * FROM truncate_a;
  col1 
 ------