Dept. of second thoughts: rejigger the TRUNCATE ... CASCADE patch so that

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.
This commit is contained in:
Tom Lane 2006-03-03 18:25:14 +00:00
parent a6add72ac3
commit 4e086f7cb5
1 changed files with 63 additions and 68 deletions

View 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,31 +577,76 @@ 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);
/*
* Check foreign key references. In CASCADE mode, this should be
* unnecessary since we just pulled in all the references; but as
* a cross-check, do it anyway if in an Assert-enabled build.
*/
#ifdef USE_ASSERT_CHECKING
heap_truncate_check_FKs(rels, false);
#else
if (stmt->behavior == DROP_RESTRICT)
heap_truncate_check_FKs(rels, false);
#endif
/*
* 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.
* OK, truncate each table.
*/
if (stmt->behavior == DROP_CASCADE &&
!list_member_oid(directRelids, relid))
ereport(NOTICE,
(errmsg("truncate cascades to table \"%s\"",
RelationGetRelationName(rel))));
foreach(cell, rels)
{
Relation rel = (Relation) lfirst(cell);
Oid heap_relid;
Oid toast_relid;
/*
* Create a new empty storage file for the relation, and assign it as
* the relfilenode value. The old storage file is scheduled for
* deletion at commit.
*/
setNewRelfilenode(rel);
heap_relid = RelationGetRelid(rel);
toast_relid = rel->rd_rel->reltoastrelid;
heap_close(rel, NoLock);
/*
* The same for the toast table, if any.
*/
if (OidIsValid(toast_relid))
{
rel = relation_open(toast_relid, AccessExclusiveLock);
setNewRelfilenode(rel);
heap_close(rel, NoLock);
}
/*
* Reconstruct the indexes to match, and we're done.
*/
reindex_relation(heap_relid, true);
}
}
/*
* 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,
@ -638,57 +684,6 @@ ExecuteTruncate(TruncateStmt *stmt)
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
* a cross-check, do it anyway if in an Assert-enabled build.
*/
#ifdef USE_ASSERT_CHECKING
heap_truncate_check_FKs(rels, false);
#else
if (stmt->behavior == DROP_RESTRICT)
heap_truncate_check_FKs(rels, false);
#endif
/*
* OK, truncate each table.
*/
foreach(cell, rels)
{
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
* deletion at commit.
*/
setNewRelfilenode(rel);
heap_relid = RelationGetRelid(rel);
toast_relid = rel->rd_rel->reltoastrelid;
heap_close(rel, NoLock);
/*
* The same for the toast table, if any.
*/
if (OidIsValid(toast_relid))
{
rel = relation_open(toast_relid, AccessExclusiveLock);
setNewRelfilenode(rel);
heap_close(rel, NoLock);
}
/*
* Reconstruct the indexes to match, and we're done.
*/
reindex_relation(heap_relid, true);
}
}
/*----------