diff options
Diffstat (limited to 'src/backend/commands')
-rw-r--r-- | src/backend/commands/cluster.c | 8 | ||||
-rw-r--r-- | src/backend/commands/tablecmds.c | 50 | ||||
-rw-r--r-- | src/backend/commands/trigger.c | 45 |
3 files changed, 100 insertions, 3 deletions
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 3256b172206..19ea0cac1ae 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/cluster.c,v 1.116.2.3 2007/09/12 15:16:24 alvherre Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/cluster.c,v 1.116.2.4 2008/05/27 21:14:00 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -398,6 +398,12 @@ cluster_rel(RelToCluster *rvtc, bool recheck) errmsg("\"%s\" is a system catalog", RelationGetRelationName(OldHeap)))); + /* + * Also check for active uses of the relation in the current transaction, + * including open scans and pending AFTER trigger events. + */ + CheckTableNotInUse(OldHeap, "CLUSTER"); + /* Drop relcache refcnt on OldIndex, but keep lock */ index_close(OldIndex); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 97caafbb8e1..ccc25c88f4a 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/tablecmds.c,v 1.91.2.3 2008/05/09 22:38:05 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/tablecmds.c,v 1.91.2.4 2008/05/27 21:14:00 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -398,6 +398,12 @@ TruncateRelation(const RangeVar *relation) errmsg("cannot truncate temporary tables of other sessions"))); /* + * Also check for active uses of the relation in the current + * transaction, including open scans and pending AFTER trigger events. + */ + CheckTableNotInUse(rel, "TRUNCATE"); + + /* * Don't allow truncate on tables which are referenced by foreign keys */ heap_truncate_check_FKs(rel); @@ -1592,6 +1598,48 @@ update_ri_trigger_args(Oid relid, CommandCounterIncrement(); } +/* + * Disallow TRUNCATE (and similar commands) when the current backend has + * any open reference to the target table besides the one just acquired by + * the calling command; this implies there's an open cursor or active plan. + * We need this check because our AccessExclusiveLock doesn't protect us + * against stomping on our own foot, only other people's feet! + * + * We also reject these commands if there are any pending AFTER trigger events + * for the rel. This is certainly necessary for CLUSTER, because it does not + * preserve tuple TIDs and so the pending events would try to fetch the wrong + * tuples. It might be overly cautious in other cases, but again it seems + * better to err on the side of paranoia. + * + * REINDEX calls this with "rel" referencing the index to be rebuilt; here + * we are worried about active indexscans on the index. The trigger-event + * check can be skipped, since we are doing no damage to the parent table. + * + * The statement name (eg, "TRUNCATE") is passed for use in error messages. + */ +void +CheckTableNotInUse(Relation rel, const char *stmt) +{ + int expected_refcnt; + + expected_refcnt = rel->rd_isnailed ? 2 : 1; + if (rel->rd_refcnt != expected_refcnt) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_IN_USE), + /* translator: first %s is a SQL command, eg ALTER TABLE */ + errmsg("cannot %s \"%s\" because " + "it is being used by active queries in this session", + stmt, RelationGetRelationName(rel)))); + + if (rel->rd_rel->relkind != RELKIND_INDEX && + AfterTriggerPendingOnRel(RelationGetRelid(rel))) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_IN_USE), + /* translator: first %s is a SQL command, eg ALTER TABLE */ + errmsg("cannot %s \"%s\" because " + "it has pending trigger events", + stmt, RelationGetRelationName(rel)))); +} /* ---------------- * AlterTableAddColumn diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 4b9d5efea98..9d2b2b64e98 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.159.2.2 2006/01/12 21:49:31 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.159.2.3 2008/05/27 21:14:00 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2365,6 +2365,49 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt) deferredTriggers->deftrig_events_imm = NULL; } +/* ---------- + * AfterTriggerPendingOnRel() + * Test to see if there are any pending after-trigger events for rel. + * + * This is used by TRUNCATE, CLUSTER, ALTER TABLE, etc to detect whether + * it is unsafe to perform major surgery on a relation. Note that only + * local pending events are examined. We assume that having exclusive lock + * on a rel guarantees there are no unserviced events in other backends --- + * but having a lock does not prevent there being such events in our own. + * + * In some scenarios it'd be reasonable to remove pending events (more + * specifically, mark them DONE by the current subxact) but without a lot + * of knowledge of the trigger semantics we can't do this in general. + * ---------- + */ +bool +AfterTriggerPendingOnRel(Oid relid) +{ + DeferredTriggerEvent event; + + /* No-op if we aren't in a transaction. (Shouldn't happen?) */ + if (deferredTriggers == NULL) + return false; + + /* Scan queued events */ + for (event = deferredTriggers->deftrig_events; + event != NULL; + event = event->dte_next) + { + /* + * We can ignore completed events. + */ + if (event->dte_event & (TRIGGER_DEFERRED_DONE | + TRIGGER_DEFERRED_CANCELED)) + continue; + + if (event->dte_relid == relid) + return true; + } + + return false; +} + /* ---------- * DeferredTriggerSaveEvent() |