Disallow foreign-key references from temp tables to permanent tables.

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.
This commit is contained in:
Tom Lane 2003-09-19 21:04:20 +00:00
parent fc6b7c550d
commit a13b018530
5 changed files with 107 additions and 49 deletions

View File

@ -1,5 +1,5 @@
<!--
$Header: /cvsroot/pgsql/doc/src/sgml/ref/truncate.sgml,v 1.13 2003/09/12 00:12:47 tgl Exp $
$Header: /cvsroot/pgsql/doc/src/sgml/ref/truncate.sgml,v 1.14 2003/09/19 21:04:19 tgl Exp $
PostgreSQL documentation
-->
@ -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>

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/catalog/heap.c,v 1.251 2003/08/04 02:39:58 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/catalog/heap.c,v 1.252 2003/09/19 21:04:19 tgl Exp $
*
*
* INTERFACE ROUTINES
@ -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);
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/tablecmds.c,v 1.81 2003/09/15 00:26:31 petere Exp $
* $Header: /cvsroot/pgsql/src/backend/commands/tablecmds.c,v 1.82 2003/09/19 21:04:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -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

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: heap.h,v 1.61 2003/08/04 02:40:10 momjian Exp $
* $Id: heap.h,v 1.62 2003/09/19 21:04:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -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);

View 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
------